1 package org.eclipse.aether.internal.impl;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Map;
26 import static java.util.Objects.requireNonNull;
27 import java.util.Properties;
28 import java.util.Set;
29 import java.util.TreeSet;
30 import java.util.concurrent.ConcurrentHashMap;
31
32 import javax.inject.Inject;
33 import javax.inject.Named;
34 import javax.inject.Singleton;
35
36 import org.eclipse.aether.RepositorySystemSession;
37 import org.eclipse.aether.SessionData;
38 import org.eclipse.aether.artifact.Artifact;
39 import org.eclipse.aether.impl.UpdateCheck;
40 import org.eclipse.aether.impl.UpdateCheckManager;
41 import org.eclipse.aether.impl.UpdatePolicyAnalyzer;
42 import org.eclipse.aether.metadata.Metadata;
43 import org.eclipse.aether.repository.AuthenticationDigest;
44 import org.eclipse.aether.repository.Proxy;
45 import org.eclipse.aether.repository.RemoteRepository;
46 import org.eclipse.aether.resolution.ResolutionErrorPolicy;
47 import org.eclipse.aether.spi.locator.Service;
48 import org.eclipse.aether.spi.locator.ServiceLocator;
49 import org.eclipse.aether.transfer.ArtifactNotFoundException;
50 import org.eclipse.aether.transfer.ArtifactTransferException;
51 import org.eclipse.aether.transfer.MetadataNotFoundException;
52 import org.eclipse.aether.transfer.MetadataTransferException;
53 import org.eclipse.aether.util.ConfigUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57
58
59 @Singleton
60 @Named
61 public class DefaultUpdateCheckManager
62 implements UpdateCheckManager, Service
63 {
64
65 private static final Logger LOGGER = LoggerFactory.getLogger( DefaultUpdatePolicyAnalyzer.class );
66
67 private TrackingFileManager trackingFileManager;
68
69 private UpdatePolicyAnalyzer updatePolicyAnalyzer;
70
71 private static final String UPDATED_KEY_SUFFIX = ".lastUpdated";
72
73 private static final String ERROR_KEY_SUFFIX = ".error";
74
75 private static final String NOT_FOUND = "";
76
77 private static final String SESSION_CHECKS = "updateCheckManager.checks";
78
79 static final String CONFIG_PROP_SESSION_STATE = "aether.updateCheckManager.sessionState";
80
81 private static final int STATE_ENABLED = 0;
82
83 private static final int STATE_BYPASS = 1;
84
85 private static final int STATE_DISABLED = 2;
86
87 public DefaultUpdateCheckManager()
88 {
89
90 }
91
92 @Inject
93 DefaultUpdateCheckManager( TrackingFileManager trackingFileManager, UpdatePolicyAnalyzer updatePolicyAnalyzer )
94 {
95 setTrackingFileManager( trackingFileManager );
96 setUpdatePolicyAnalyzer( updatePolicyAnalyzer );
97 }
98
99 public void initService( ServiceLocator locator )
100 {
101 setTrackingFileManager( locator.getService( TrackingFileManager.class ) );
102 setUpdatePolicyAnalyzer( locator.getService( UpdatePolicyAnalyzer.class ) );
103 }
104
105 public DefaultUpdateCheckManager setTrackingFileManager( TrackingFileManager trackingFileManager )
106 {
107 this.trackingFileManager = requireNonNull( trackingFileManager );
108 return this;
109 }
110
111 public DefaultUpdateCheckManager setUpdatePolicyAnalyzer( UpdatePolicyAnalyzer updatePolicyAnalyzer )
112 {
113 this.updatePolicyAnalyzer = requireNonNull( updatePolicyAnalyzer, "update policy analyzer cannot be null" );
114 return this;
115 }
116
117 public void checkArtifact( RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check )
118 {
119 if ( check.getLocalLastUpdated() != 0
120 && !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) )
121 {
122 LOGGER.debug( "Skipped remote request for {}, locally installed artifact up-to-date", check.getItem() );
123
124 check.setRequired( false );
125 return;
126 }
127
128 Artifact artifact = check.getItem();
129 RemoteRepository repository = check.getRepository();
130
131 File artifactFile = requireNonNull( check.getFile(), String.format( "The artifact '%s' has no file attached",
132 artifact ) );
133
134 boolean fileExists = check.isFileValid() && artifactFile.exists();
135
136 File touchFile = getArtifactTouchFile( artifactFile );
137 Properties props = read( touchFile );
138
139 String updateKey = getUpdateKey( session, artifactFile, repository );
140 String dataKey = getDataKey( repository );
141
142 String error = getError( props, dataKey );
143
144 long lastUpdated;
145 if ( error == null )
146 {
147 if ( fileExists )
148 {
149
150 lastUpdated = artifactFile.lastModified();
151 }
152 else
153 {
154
155 lastUpdated = 0L;
156 }
157 }
158 else if ( error.length() <= 0 )
159 {
160
161 lastUpdated = getLastUpdated( props, dataKey );
162 }
163 else
164 {
165
166 String transferKey = getTransferKey( session, repository );
167 lastUpdated = getLastUpdated( props, transferKey );
168 }
169
170 if ( lastUpdated == 0L )
171 {
172 check.setRequired( true );
173 }
174 else if ( isAlreadyUpdated( session, updateKey ) )
175 {
176 LOGGER.debug( "Skipped remote request for {}, already updated during this session", check.getItem() );
177
178 check.setRequired( false );
179 if ( error != null )
180 {
181 check.setException( newException( error, artifact, repository ) );
182 }
183 }
184 else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) )
185 {
186 check.setRequired( true );
187 }
188 else if ( fileExists )
189 {
190 LOGGER.debug( "Skipped remote request for {}, locally cached artifact up-to-date", check.getItem() );
191
192 check.setRequired( false );
193 }
194 else
195 {
196 int errorPolicy = Utils.getPolicy( session, artifact, repository );
197 int cacheFlag = getCacheFlag( error );
198 if ( ( errorPolicy & cacheFlag ) != 0 )
199 {
200 check.setRequired( false );
201 check.setException( newException( error, artifact, repository ) );
202 }
203 else
204 {
205 check.setRequired( true );
206 }
207 }
208 }
209
210 private static int getCacheFlag( String error )
211 {
212 if ( error == null || error.length() <= 0 )
213 {
214 return ResolutionErrorPolicy.CACHE_NOT_FOUND;
215 }
216 else
217 {
218 return ResolutionErrorPolicy.CACHE_TRANSFER_ERROR;
219 }
220 }
221
222 private ArtifactTransferException newException( String error, Artifact artifact, RemoteRepository repository )
223 {
224 if ( error == null || error.length() <= 0 )
225 {
226 return new ArtifactNotFoundException( artifact, repository, artifact
227 + " was not found in " + repository.getUrl() + " during a previous attempt. This failure was"
228 + " cached in the local repository and"
229 + " resolution is not reattempted until the update interval of " + repository.getId()
230 + " has elapsed or updates are forced", true );
231 }
232 else
233 {
234 return new ArtifactTransferException( artifact, repository, artifact + " failed to transfer from "
235 + repository.getUrl() + " during a previous attempt. This failure"
236 + " was cached in the local repository and"
237 + " resolution is not reattempted until the update interval of " + repository.getId()
238 + " has elapsed or updates are forced. Original error: " + error, true );
239 }
240 }
241
242 public void checkMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check )
243 {
244 if ( check.getLocalLastUpdated() != 0
245 && !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) )
246 {
247 LOGGER.debug( "Skipped remote request for {} locally installed metadata up-to-date", check.getItem() );
248
249 check.setRequired( false );
250 return;
251 }
252
253 Metadata metadata = check.getItem();
254 RemoteRepository repository = check.getRepository();
255
256 File metadataFile = requireNonNull( check.getFile(), String.format( "The metadata '%s' has no file attached",
257 metadata ) );
258
259 boolean fileExists = check.isFileValid() && metadataFile.exists();
260
261 File touchFile = getMetadataTouchFile( metadataFile );
262 Properties props = read( touchFile );
263
264 String updateKey = getUpdateKey( session, metadataFile, repository );
265 String dataKey = getDataKey( metadataFile );
266
267 String error = getError( props, dataKey );
268
269 long lastUpdated;
270 if ( error == null )
271 {
272 if ( fileExists )
273 {
274
275 lastUpdated = getLastUpdated( props, dataKey );
276 }
277 else
278 {
279
280 lastUpdated = 0L;
281 }
282 }
283 else if ( error.length() <= 0 )
284 {
285
286 lastUpdated = getLastUpdated( props, dataKey );
287 }
288 else
289 {
290
291 String transferKey = getTransferKey( session, metadataFile, repository );
292 lastUpdated = getLastUpdated( props, transferKey );
293 }
294
295 if ( lastUpdated == 0L )
296 {
297 check.setRequired( true );
298 }
299 else if ( isAlreadyUpdated( session, updateKey ) )
300 {
301 LOGGER.debug( "Skipped remote request for {}, already updated during this session", check.getItem() );
302
303 check.setRequired( false );
304 if ( error != null )
305 {
306 check.setException( newException( error, metadata, repository ) );
307 }
308 }
309 else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) )
310 {
311 check.setRequired( true );
312 }
313 else if ( fileExists )
314 {
315 LOGGER.debug( "Skipped remote request for {}, locally cached metadata up-to-date", check.getItem() );
316
317 check.setRequired( false );
318 }
319 else
320 {
321 int errorPolicy = Utils.getPolicy( session, metadata, repository );
322 int cacheFlag = getCacheFlag( error );
323 if ( ( errorPolicy & cacheFlag ) != 0 )
324 {
325 check.setRequired( false );
326 check.setException( newException( error, metadata, repository ) );
327 }
328 else
329 {
330 check.setRequired( true );
331 }
332 }
333 }
334
335 private MetadataTransferException newException( String error, Metadata metadata, RemoteRepository repository )
336 {
337 if ( error == null || error.length() <= 0 )
338 {
339 return new MetadataNotFoundException( metadata, repository, metadata + " was not found in "
340 + repository.getUrl() + " during a previous attempt."
341 + " This failure was cached in the local repository and"
342 + " resolution is not be reattempted until the update interval of " + repository.getId()
343 + " has elapsed or updates are forced", true );
344 }
345 else
346 {
347 return new MetadataTransferException( metadata, repository, metadata + " failed to transfer from "
348 + repository.getUrl() + " during a previous attempt."
349 + " This failure was cached in the local repository and"
350 + " resolution will not be reattempted until the update interval of " + repository.getId()
351 + " has elapsed or updates are forced. Original error: " + error, true );
352 }
353 }
354
355 private long getLastUpdated( Properties props, String key )
356 {
357 String value = props.getProperty( key + UPDATED_KEY_SUFFIX, "" );
358 try
359 {
360 return ( value.length() > 0 ) ? Long.parseLong( value ) : 1;
361 }
362 catch ( NumberFormatException e )
363 {
364 LOGGER.debug( "Cannot parse last updated date {}, ignoring it", value, e );
365 return 1;
366 }
367 }
368
369 private String getError( Properties props, String key )
370 {
371 return props.getProperty( key + ERROR_KEY_SUFFIX );
372 }
373
374 private File getArtifactTouchFile( File artifactFile )
375 {
376 return new File( artifactFile.getPath() + UPDATED_KEY_SUFFIX );
377 }
378
379 private File getMetadataTouchFile( File metadataFile )
380 {
381 return new File( metadataFile.getParent(), "resolver-status.properties" );
382 }
383
384 private String getDataKey( RemoteRepository repository )
385 {
386 Set<String> mirroredUrls = Collections.emptySet();
387 if ( repository.isRepositoryManager() )
388 {
389 mirroredUrls = new TreeSet<>();
390 for ( RemoteRepository mirroredRepository : repository.getMirroredRepositories() )
391 {
392 mirroredUrls.add( normalizeRepoUrl( mirroredRepository.getUrl() ) );
393 }
394 }
395
396 StringBuilder buffer = new StringBuilder( 1024 );
397
398 buffer.append( normalizeRepoUrl( repository.getUrl() ) );
399 for ( String mirroredUrl : mirroredUrls )
400 {
401 buffer.append( '+' ).append( mirroredUrl );
402 }
403
404 return buffer.toString();
405 }
406
407 private String getTransferKey( RepositorySystemSession session, RemoteRepository repository )
408 {
409 return getRepoKey( session, repository );
410 }
411
412 private String getDataKey( File metadataFile )
413 {
414 return metadataFile.getName();
415 }
416
417 private String getTransferKey( RepositorySystemSession session, File metadataFile,
418 RemoteRepository repository )
419 {
420 return metadataFile.getName() + '/' + getRepoKey( session, repository );
421 }
422
423 private String getRepoKey( RepositorySystemSession session, RemoteRepository repository )
424 {
425 StringBuilder buffer = new StringBuilder( 128 );
426
427 Proxy proxy = repository.getProxy();
428 if ( proxy != null )
429 {
430 buffer.append( AuthenticationDigest.forProxy( session, repository ) ).append( '@' );
431 buffer.append( proxy.getHost() ).append( ':' ).append( proxy.getPort() ).append( '>' );
432 }
433
434 buffer.append( AuthenticationDigest.forRepository( session, repository ) ).append( '@' );
435
436 buffer.append( repository.getContentType() ).append( '-' );
437 buffer.append( repository.getId() ).append( '-' );
438 buffer.append( normalizeRepoUrl( repository.getUrl() ) );
439
440 return buffer.toString();
441 }
442
443 private String normalizeRepoUrl( String url )
444 {
445 String result = url;
446 if ( url != null && url.length() > 0 && !url.endsWith( "/" ) )
447 {
448 result = url + '/';
449 }
450 return result;
451 }
452
453 private String getUpdateKey( RepositorySystemSession session, File file, RemoteRepository repository )
454 {
455 return file.getAbsolutePath() + '|' + getRepoKey( session, repository );
456 }
457
458 private int getSessionState( RepositorySystemSession session )
459 {
460 String mode = ConfigUtils.getString( session, "enabled", CONFIG_PROP_SESSION_STATE );
461 if ( Boolean.parseBoolean( mode ) || "enabled".equalsIgnoreCase( mode ) )
462 {
463
464 return STATE_ENABLED;
465 }
466 else if ( "bypass".equalsIgnoreCase( mode ) )
467 {
468
469 return STATE_BYPASS;
470 }
471 else
472 {
473
474 return STATE_DISABLED;
475 }
476 }
477
478 private boolean isAlreadyUpdated( RepositorySystemSession session, Object updateKey )
479 {
480 if ( getSessionState( session ) >= STATE_BYPASS )
481 {
482 return false;
483 }
484 SessionData data = session.getData();
485 Object checkedFiles = data.get( SESSION_CHECKS );
486 if ( !( checkedFiles instanceof Map ) )
487 {
488 return false;
489 }
490 return ( (Map<?, ?>) checkedFiles ).containsKey( updateKey );
491 }
492
493 @SuppressWarnings( "unchecked" )
494 private void setUpdated( RepositorySystemSession session, Object updateKey )
495 {
496 if ( getSessionState( session ) >= STATE_DISABLED )
497 {
498 return;
499 }
500 SessionData data = session.getData();
501 Object checkedFiles = data.get( SESSION_CHECKS );
502 while ( !( checkedFiles instanceof Map ) )
503 {
504 Object old = checkedFiles;
505 checkedFiles = new ConcurrentHashMap<>( 256 );
506 if ( data.set( SESSION_CHECKS, old, checkedFiles ) )
507 {
508 break;
509 }
510 checkedFiles = data.get( SESSION_CHECKS );
511 }
512 ( (Map<Object, Boolean>) checkedFiles ).put( updateKey, Boolean.TRUE );
513 }
514
515 private boolean isUpdatedRequired( RepositorySystemSession session, long lastModified, String policy )
516 {
517 return updatePolicyAnalyzer.isUpdatedRequired( session, lastModified, policy );
518 }
519
520 private Properties read( File touchFile )
521 {
522 Properties props = trackingFileManager.read( touchFile );
523 return ( props != null ) ? props : new Properties();
524 }
525
526 public void touchArtifact( RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check )
527 {
528 File artifactFile = check.getFile();
529 File touchFile = getArtifactTouchFile( artifactFile );
530
531 String updateKey = getUpdateKey( session, artifactFile, check.getRepository() );
532 String dataKey = getDataKey( check.getAuthoritativeRepository() );
533 String transferKey = getTransferKey( session, check.getRepository() );
534
535 setUpdated( session, updateKey );
536 Properties props = write( touchFile, dataKey, transferKey, check.getException() );
537
538 if ( artifactFile.exists() && !hasErrors( props ) )
539 {
540 touchFile.delete();
541 }
542 }
543
544 private boolean hasErrors( Properties props )
545 {
546 for ( Object key : props.keySet() )
547 {
548 if ( key.toString().endsWith( ERROR_KEY_SUFFIX ) )
549 {
550 return true;
551 }
552 }
553 return false;
554 }
555
556 public void touchMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check )
557 {
558 File metadataFile = check.getFile();
559 File touchFile = getMetadataTouchFile( metadataFile );
560
561 String updateKey = getUpdateKey( session, metadataFile, check.getRepository() );
562 String dataKey = getDataKey( metadataFile );
563 String transferKey = getTransferKey( session, metadataFile, check.getRepository() );
564
565 setUpdated( session, updateKey );
566 write( touchFile, dataKey, transferKey, check.getException() );
567 }
568
569 private Properties write( File touchFile, String dataKey, String transferKey, Exception error )
570 {
571 Map<String, String> updates = new HashMap<>();
572
573 String timestamp = Long.toString( System.currentTimeMillis() );
574
575 if ( error == null )
576 {
577 updates.put( dataKey + ERROR_KEY_SUFFIX, null );
578 updates.put( dataKey + UPDATED_KEY_SUFFIX, timestamp );
579 updates.put( transferKey + UPDATED_KEY_SUFFIX, null );
580 }
581 else if ( error instanceof ArtifactNotFoundException || error instanceof MetadataNotFoundException )
582 {
583 updates.put( dataKey + ERROR_KEY_SUFFIX, NOT_FOUND );
584 updates.put( dataKey + UPDATED_KEY_SUFFIX, timestamp );
585 updates.put( transferKey + UPDATED_KEY_SUFFIX, null );
586 }
587 else
588 {
589 String msg = error.getMessage();
590 if ( msg == null || msg.length() <= 0 )
591 {
592 msg = error.getClass().getSimpleName();
593 }
594 updates.put( dataKey + ERROR_KEY_SUFFIX, msg );
595 updates.put( dataKey + UPDATED_KEY_SUFFIX, null );
596 updates.put( transferKey + UPDATED_KEY_SUFFIX, timestamp );
597 }
598
599 return trackingFileManager.update( touchFile, updates );
600 }
601
602 }