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