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