View Javadoc
1   package org.eclipse.aether.internal.impl;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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          // enables default constructor
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                 // last update was successful
137                 lastUpdated = artifactFile.lastModified();
138             }
139             else
140             {
141                 // this is the first attempt ever
142                 lastUpdated = 0L;
143             }
144         }
145         else if ( error.length() <= 0 )
146         {
147             // artifact did not exist
148             lastUpdated = getLastUpdated( props, dataKey );
149         }
150         else
151         {
152             // artifact could not be transferred
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                 // last update was successful
263                 lastUpdated = getLastUpdated( props, dataKey );
264             }
265             else
266             {
267                 // this is the first attempt ever
268                 lastUpdated = 0L;
269             }
270         }
271         else if ( error.length() <= 0 )
272         {
273             // metadata did not exist
274             lastUpdated = getLastUpdated( props, dataKey );
275         }
276         else
277         {
278             // metadata could not be transferred
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             // perform update check at most once per session, regardless of update policy
451             return STATE_ENABLED;
452         }
453         else if ( "bypass".equalsIgnoreCase( mode ) )
454         {
455             // evaluate update policy but record update in session to prevent potential future checks
456             return STATE_BYPASS;
457         }
458         else
459         {
460             // no session state at all, always evaluate update policy
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 }