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