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