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 if ( LOGGER.isDebugEnabled() )
165 {
166 LOGGER.debug( "Skipped remote request for " + check.getItem()
167 + ", already updated during this session." );
168 }
169
170 check.setRequired( false );
171 if ( error != null )
172 {
173 check.setException( newException( error, artifact, repository ) );
174 }
175 }
176 else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) )
177 {
178 check.setRequired( true );
179 }
180 else if ( fileExists )
181 {
182 LOGGER.debug( "Skipped remote request for {}, locally cached artifact up-to-date.", check.getItem() );
183
184 check.setRequired( false );
185 }
186 else
187 {
188 int errorPolicy = Utils.getPolicy( session, artifact, repository );
189 int cacheFlag = getCacheFlag( error );
190 if ( ( errorPolicy & cacheFlag ) != 0 )
191 {
192 check.setRequired( false );
193 check.setException( newException( error, artifact, repository ) );
194 }
195 else
196 {
197 check.setRequired( true );
198 }
199 }
200 }
201
202 private static int getCacheFlag( String error )
203 {
204 if ( error == null || error.length() <= 0 )
205 {
206 return ResolutionErrorPolicy.CACHE_NOT_FOUND;
207 }
208 else
209 {
210 return ResolutionErrorPolicy.CACHE_TRANSFER_ERROR;
211 }
212 }
213
214 private ArtifactTransferException newException( String error, Artifact artifact, RemoteRepository repository )
215 {
216 if ( error == null || error.length() <= 0 )
217 {
218 return new ArtifactNotFoundException( artifact, repository, "Failure to find " + artifact + " in "
219 + repository.getUrl() + " was cached in the local repository, "
220 + "resolution will not be reattempted until the update interval of " + repository.getId()
221 + " has elapsed or updates are forced", true );
222 }
223 else
224 {
225 return new ArtifactTransferException( artifact, repository, "Failure to transfer " + artifact + " from "
226 + repository.getUrl() + " was cached in the local repository, "
227 + "resolution will not be reattempted until the update interval of " + repository.getId()
228 + " has elapsed or updates are forced. Original error: " + error, true );
229 }
230 }
231
232 public void checkMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check )
233 {
234 if ( check.getLocalLastUpdated() != 0
235 && !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) )
236 {
237 LOGGER.debug( "Skipped remote request for {} locally installed metadata up-to-date.", check.getItem() );
238
239 check.setRequired( false );
240 return;
241 }
242
243 Metadata metadata = check.getItem();
244 RemoteRepository repository = check.getRepository();
245
246 File metadataFile = requireNonNull( check.getFile(), String.format( "The metadata '%s' has no file attached",
247 metadata ) );
248
249 boolean fileExists = check.isFileValid() && metadataFile.exists();
250
251 File touchFile = getTouchFile( metadata, metadataFile );
252 Properties props = read( touchFile );
253
254 String updateKey = getUpdateKey( session, metadataFile, repository );
255 String dataKey = getDataKey( metadata, metadataFile, check.getAuthoritativeRepository() );
256
257 String error = getError( props, dataKey );
258
259 long lastUpdated;
260 if ( error == null )
261 {
262 if ( fileExists )
263 {
264
265 lastUpdated = getLastUpdated( props, dataKey );
266 }
267 else
268 {
269
270 lastUpdated = 0L;
271 }
272 }
273 else if ( error.length() <= 0 )
274 {
275
276 lastUpdated = getLastUpdated( props, dataKey );
277 }
278 else
279 {
280
281 String transferKey = getTransferKey( session, metadata, metadataFile, repository );
282 lastUpdated = getLastUpdated( props, transferKey );
283 }
284
285 if ( lastUpdated == 0L )
286 {
287 check.setRequired( true );
288 }
289 else if ( isAlreadyUpdated( session, updateKey ) )
290 {
291 LOGGER.debug( "Skipped remote request for {}, already updated during this session.", check.getItem() );
292
293 check.setRequired( false );
294 if ( error != null )
295 {
296 check.setException( newException( error, metadata, repository ) );
297 }
298 }
299 else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) )
300 {
301 check.setRequired( true );
302 }
303 else if ( fileExists )
304 {
305 LOGGER.debug( "Skipped remote request for {}, locally cached metadata up-to-date.", check.getItem() );
306
307 check.setRequired( false );
308 }
309 else
310 {
311 int errorPolicy = Utils.getPolicy( session, metadata, repository );
312 int cacheFlag = getCacheFlag( error );
313 if ( ( errorPolicy & cacheFlag ) != 0 )
314 {
315 check.setRequired( false );
316 check.setException( newException( error, metadata, repository ) );
317 }
318 else
319 {
320 check.setRequired( true );
321 }
322 }
323 }
324
325 private MetadataTransferException newException( String error, Metadata metadata, RemoteRepository repository )
326 {
327 if ( error == null || error.length() <= 0 )
328 {
329 return new MetadataNotFoundException( metadata, repository, "Failure to find " + metadata + " in "
330 + repository.getUrl() + " was cached in the local repository, "
331 + "resolution will not be reattempted until the update interval of " + repository.getId()
332 + " has elapsed or updates are forced", true );
333 }
334 else
335 {
336 return new MetadataTransferException( metadata, repository, "Failure to transfer " + metadata + " from "
337 + repository.getUrl() + " was cached in the local repository, "
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 lastUpdated date: \'{}\'. Ignoring.", 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, "true", CONFIG_PROP_SESSION_STATE );
450 if ( Boolean.parseBoolean( 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 }