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