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.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.concurrent.Executor;
31  import java.util.concurrent.ExecutorService;
32  import java.util.concurrent.LinkedBlockingQueue;
33  import java.util.concurrent.ThreadPoolExecutor;
34  import java.util.concurrent.TimeUnit;
35  
36  import javax.inject.Inject;
37  import javax.inject.Named;
38  
39  import org.eclipse.aether.RepositoryEvent;
40  import org.eclipse.aether.RepositoryEvent.EventType;
41  import org.eclipse.aether.RepositorySystemSession;
42  import org.eclipse.aether.RequestTrace;
43  import org.eclipse.aether.SyncContext;
44  import org.eclipse.aether.impl.MetadataResolver;
45  import org.eclipse.aether.impl.OfflineController;
46  import org.eclipse.aether.impl.RemoteRepositoryManager;
47  import org.eclipse.aether.impl.RepositoryConnectorProvider;
48  import org.eclipse.aether.impl.RepositoryEventDispatcher;
49  import org.eclipse.aether.impl.SyncContextFactory;
50  import org.eclipse.aether.impl.UpdateCheck;
51  import org.eclipse.aether.impl.UpdateCheckManager;
52  import org.eclipse.aether.metadata.Metadata;
53  import org.eclipse.aether.repository.ArtifactRepository;
54  import org.eclipse.aether.repository.LocalMetadataRegistration;
55  import org.eclipse.aether.repository.LocalMetadataRequest;
56  import org.eclipse.aether.repository.LocalMetadataResult;
57  import org.eclipse.aether.repository.LocalRepository;
58  import org.eclipse.aether.repository.LocalRepositoryManager;
59  import org.eclipse.aether.repository.RemoteRepository;
60  import org.eclipse.aether.repository.RepositoryPolicy;
61  import org.eclipse.aether.resolution.MetadataRequest;
62  import org.eclipse.aether.resolution.MetadataResult;
63  import org.eclipse.aether.spi.connector.MetadataDownload;
64  import org.eclipse.aether.spi.connector.RepositoryConnector;
65  import org.eclipse.aether.spi.locator.Service;
66  import org.eclipse.aether.spi.locator.ServiceLocator;
67  import org.eclipse.aether.spi.log.Logger;
68  import org.eclipse.aether.spi.log.LoggerFactory;
69  import org.eclipse.aether.spi.log.NullLoggerFactory;
70  import org.eclipse.aether.transfer.MetadataNotFoundException;
71  import org.eclipse.aether.transfer.MetadataTransferException;
72  import org.eclipse.aether.transfer.NoRepositoryConnectorException;
73  import org.eclipse.aether.transfer.RepositoryOfflineException;
74  import org.eclipse.aether.util.ConfigUtils;
75  import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
76  import org.eclipse.aether.util.concurrency.WorkerThreadFactory;
77  
78  /**
79   */
80  @Named
81  public class DefaultMetadataResolver
82      implements MetadataResolver, Service
83  {
84  
85      private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
86  
87      private Logger logger = NullLoggerFactory.LOGGER;
88  
89      private RepositoryEventDispatcher repositoryEventDispatcher;
90  
91      private UpdateCheckManager updateCheckManager;
92  
93      private RepositoryConnectorProvider repositoryConnectorProvider;
94  
95      private RemoteRepositoryManager remoteRepositoryManager;
96  
97      private SyncContextFactory syncContextFactory;
98  
99      private OfflineController offlineController;
100 
101     public DefaultMetadataResolver()
102     {
103         // enables default constructor
104     }
105 
106     @Inject
107     DefaultMetadataResolver( RepositoryEventDispatcher repositoryEventDispatcher,
108                              UpdateCheckManager updateCheckManager,
109                              RepositoryConnectorProvider repositoryConnectorProvider,
110                              RemoteRepositoryManager remoteRepositoryManager, SyncContextFactory syncContextFactory,
111                              OfflineController offlineController, LoggerFactory loggerFactory )
112     {
113         setRepositoryEventDispatcher( repositoryEventDispatcher );
114         setUpdateCheckManager( updateCheckManager );
115         setRepositoryConnectorProvider( repositoryConnectorProvider );
116         setRemoteRepositoryManager( remoteRepositoryManager );
117         setSyncContextFactory( syncContextFactory );
118         setOfflineController( offlineController );
119         setLoggerFactory( loggerFactory );
120     }
121 
122     public void initService( ServiceLocator locator )
123     {
124         setLoggerFactory( locator.getService( LoggerFactory.class ) );
125         setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
126         setUpdateCheckManager( locator.getService( UpdateCheckManager.class ) );
127         setRepositoryConnectorProvider( locator.getService( RepositoryConnectorProvider.class ) );
128         setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) );
129         setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
130         setOfflineController( locator.getService( OfflineController.class ) );
131     }
132 
133     public DefaultMetadataResolver setLoggerFactory( LoggerFactory loggerFactory )
134     {
135         this.logger = NullLoggerFactory.getSafeLogger( loggerFactory, getClass() );
136         return this;
137     }
138 
139     public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
140     {
141         if ( repositoryEventDispatcher == null )
142         {
143             throw new IllegalArgumentException( "repository event dispatcher has not been specified" );
144         }
145         this.repositoryEventDispatcher = repositoryEventDispatcher;
146         return this;
147     }
148 
149     public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager )
150     {
151         if ( updateCheckManager == null )
152         {
153             throw new IllegalArgumentException( "update check manager has not been specified" );
154         }
155         this.updateCheckManager = updateCheckManager;
156         return this;
157     }
158 
159     public DefaultMetadataResolver setRepositoryConnectorProvider( RepositoryConnectorProvider repositoryConnectorProvider )
160     {
161         if ( repositoryConnectorProvider == null )
162         {
163             throw new IllegalArgumentException( "repository connector provider has not been specified" );
164         }
165         this.repositoryConnectorProvider = repositoryConnectorProvider;
166         return this;
167     }
168 
169     public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
170     {
171         if ( remoteRepositoryManager == null )
172         {
173             throw new IllegalArgumentException( "remote repository manager has not been specified" );
174         }
175         this.remoteRepositoryManager = remoteRepositoryManager;
176         return this;
177     }
178 
179     public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
180     {
181         if ( syncContextFactory == null )
182         {
183             throw new IllegalArgumentException( "sync context factory has not been specified" );
184         }
185         this.syncContextFactory = syncContextFactory;
186         return this;
187     }
188 
189     public DefaultMetadataResolver setOfflineController( OfflineController offlineController )
190     {
191         if ( offlineController == null )
192         {
193             throw new IllegalArgumentException( "offline controller has not been specified" );
194         }
195         this.offlineController = offlineController;
196         return this;
197     }
198 
199     public List<MetadataResult> resolveMetadata( RepositorySystemSession session,
200                                                  Collection<? extends MetadataRequest> requests )
201     {
202         SyncContext syncContext = syncContextFactory.newInstance( session, false );
203 
204         try
205         {
206             Collection<Metadata> metadata = new ArrayList<Metadata>( requests.size() );
207             for ( MetadataRequest request : requests )
208             {
209                 metadata.add( request.getMetadata() );
210             }
211 
212             syncContext.acquire( null, metadata );
213 
214             return resolve( session, requests );
215         }
216         finally
217         {
218             syncContext.close();
219         }
220     }
221 
222     private List<MetadataResult> resolve( RepositorySystemSession session,
223                                           Collection<? extends MetadataRequest> requests )
224     {
225         List<MetadataResult> results = new ArrayList<MetadataResult>( requests.size() );
226 
227         List<ResolveTask> tasks = new ArrayList<ResolveTask>( requests.size() );
228 
229         Map<File, Long> localLastUpdates = new HashMap<File, Long>();
230 
231         for ( MetadataRequest request : requests )
232         {
233             RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
234 
235             MetadataResult result = new MetadataResult( request );
236             results.add( result );
237 
238             Metadata metadata = request.getMetadata();
239             RemoteRepository repository = request.getRepository();
240 
241             if ( repository == null )
242             {
243                 LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
244 
245                 metadataResolving( session, trace, metadata, localRepo );
246 
247                 File localFile = getLocalFile( session, metadata );
248 
249                 if ( localFile != null )
250                 {
251                     metadata = metadata.setFile( localFile );
252                     result.setMetadata( metadata );
253                 }
254                 else
255                 {
256                     result.setException( new MetadataNotFoundException( metadata, localRepo ) );
257                 }
258 
259                 metadataResolved( session, trace, metadata, localRepo, result.getException() );
260                 continue;
261             }
262 
263             List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() );
264 
265             if ( repositories.isEmpty() )
266             {
267                 continue;
268             }
269 
270             metadataResolving( session, trace, metadata, repository );
271             LocalRepositoryManager lrm = session.getLocalRepositoryManager();
272             LocalMetadataRequest localRequest =
273                 new LocalMetadataRequest( metadata, repository, request.getRequestContext() );
274             LocalMetadataResult lrmResult = lrm.find( session, localRequest );
275 
276             File metadataFile = lrmResult.getFile();
277 
278             try
279             {
280                 Utils.checkOffline( session, offlineController, repository );
281             }
282             catch ( RepositoryOfflineException e )
283             {
284                 if ( metadataFile != null )
285                 {
286                     metadata = metadata.setFile( metadataFile );
287                     result.setMetadata( metadata );
288                 }
289                 else
290                 {
291                     String msg =
292                         "Cannot access " + repository.getId() + " (" + repository.getUrl()
293                             + ") in offline mode and the metadata " + metadata
294                             + " has not been downloaded from it before";
295                     result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) );
296                 }
297 
298                 metadataResolved( session, trace, metadata, repository, result.getException() );
299                 continue;
300             }
301 
302             Long localLastUpdate = null;
303             if ( request.isFavorLocalRepository() )
304             {
305                 File localFile = getLocalFile( session, metadata );
306                 localLastUpdate = localLastUpdates.get( localFile );
307                 if ( localLastUpdate == null )
308                 {
309                     localLastUpdate = localFile != null ? localFile.lastModified() : 0;
310                     localLastUpdates.put( localFile, localLastUpdate );
311                 }
312             }
313 
314             List<UpdateCheck<Metadata, MetadataTransferException>> checks =
315                 new ArrayList<UpdateCheck<Metadata, MetadataTransferException>>();
316             Exception exception = null;
317             for ( RemoteRepository repo : repositories )
318             {
319                 UpdateCheck<Metadata, MetadataTransferException> check =
320                     new UpdateCheck<Metadata, MetadataTransferException>();
321                 check.setLocalLastUpdated( ( localLastUpdate != null ) ? localLastUpdate : 0 );
322                 check.setItem( metadata );
323 
324                 // use 'main' installation file for the check (-> use requested repository)
325                 File checkFile =
326                     new File(
327                               session.getLocalRepository().getBasedir(),
328                               session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata, repository,
329                                                                                             request.getRequestContext() ) );
330                 check.setFile( checkFile );
331                 check.setRepository( repository );
332                 check.setAuthoritativeRepository( repo );
333                 check.setPolicy( getPolicy( session, repo, metadata.getNature() ).getUpdatePolicy() );
334 
335                 if ( lrmResult.isStale() )
336                 {
337                     checks.add( check );
338                 }
339                 else
340                 {
341                     updateCheckManager.checkMetadata( session, check );
342                     if ( check.isRequired() )
343                     {
344                         checks.add( check );
345                     }
346                     else if ( exception == null )
347                     {
348                         exception = check.getException();
349                     }
350                 }
351             }
352 
353             if ( !checks.isEmpty() )
354             {
355                 RepositoryPolicy policy = getPolicy( session, repository, metadata.getNature() );
356 
357                 // install path may be different from lookup path
358                 File installFile =
359                     new File(
360                               session.getLocalRepository().getBasedir(),
361                               session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata,
362                                                                                             request.getRepository(),
363                                                                                             request.getRequestContext() ) );
364 
365                 ResolveTask task =
366                     new ResolveTask( session, trace, result, installFile, checks, policy.getChecksumPolicy() );
367                 tasks.add( task );
368             }
369             else
370             {
371                 result.setException( exception );
372                 if ( metadataFile != null )
373                 {
374                     metadata = metadata.setFile( metadataFile );
375                     result.setMetadata( metadata );
376                 }
377                 metadataResolved( session, trace, metadata, repository, result.getException() );
378             }
379         }
380 
381         if ( !tasks.isEmpty() )
382         {
383             int threads = ConfigUtils.getInteger( session, 4, CONFIG_PROP_THREADS );
384             Executor executor = getExecutor( Math.min( tasks.size(), threads ) );
385             try
386             {
387                 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
388 
389                 for ( ResolveTask task : tasks )
390                 {
391                     executor.execute( errorForwarder.wrap( task ) );
392                 }
393 
394                 errorForwarder.await();
395 
396                 for ( ResolveTask task : tasks )
397                 {
398                     task.result.setException( task.exception );
399                 }
400             }
401             finally
402             {
403                 shutdown( executor );
404             }
405             for ( ResolveTask task : tasks )
406             {
407                 Metadata metadata = task.request.getMetadata();
408                 // re-lookup metadata for resolve
409                 LocalMetadataRequest localRequest =
410                     new LocalMetadataRequest( metadata, task.request.getRepository(), task.request.getRequestContext() );
411                 File metadataFile = session.getLocalRepositoryManager().find( session, localRequest ).getFile();
412                 if ( metadataFile != null )
413                 {
414                     metadata = metadata.setFile( metadataFile );
415                     task.result.setMetadata( metadata );
416                 }
417                 if ( task.result.getException() == null )
418                 {
419                     task.result.setUpdated( true );
420                 }
421                 metadataResolved( session, task.trace, metadata, task.request.getRepository(),
422                                   task.result.getException() );
423             }
424         }
425 
426         return results;
427     }
428 
429     private File getLocalFile( RepositorySystemSession session, Metadata metadata )
430     {
431         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
432         LocalMetadataResult localResult = lrm.find( session, new LocalMetadataRequest( metadata, null, null ) );
433         File localFile = localResult.getFile();
434         return localFile;
435     }
436 
437     private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature )
438     {
439         List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
440 
441         if ( repository.isRepositoryManager() )
442         {
443             for ( RemoteRepository repo : repository.getMirroredRepositories() )
444             {
445                 if ( isEnabled( repo, nature ) )
446                 {
447                     repositories.add( repo );
448                 }
449             }
450         }
451         else if ( isEnabled( repository, nature ) )
452         {
453             repositories.add( repository );
454         }
455 
456         return repositories;
457     }
458 
459     private boolean isEnabled( RemoteRepository repository, Metadata.Nature nature )
460     {
461         if ( !Metadata.Nature.SNAPSHOT.equals( nature ) && repository.getPolicy( false ).isEnabled() )
462         {
463             return true;
464         }
465         if ( !Metadata.Nature.RELEASE.equals( nature ) && repository.getPolicy( true ).isEnabled() )
466         {
467             return true;
468         }
469         return false;
470     }
471 
472     private RepositoryPolicy getPolicy( RepositorySystemSession session, RemoteRepository repository,
473                                         Metadata.Nature nature )
474     {
475         boolean releases = !Metadata.Nature.SNAPSHOT.equals( nature );
476         boolean snapshots = !Metadata.Nature.RELEASE.equals( nature );
477         return remoteRepositoryManager.getPolicy( session, repository, releases, snapshots );
478     }
479 
480     private void metadataResolving( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
481                                     ArtifactRepository repository )
482     {
483         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVING );
484         event.setTrace( trace );
485         event.setMetadata( metadata );
486         event.setRepository( repository );
487 
488         repositoryEventDispatcher.dispatch( event.build() );
489     }
490 
491     private void metadataResolved( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
492                                    ArtifactRepository repository, Exception exception )
493     {
494         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVED );
495         event.setTrace( trace );
496         event.setMetadata( metadata );
497         event.setRepository( repository );
498         event.setException( exception );
499         event.setFile( metadata.getFile() );
500 
501         repositoryEventDispatcher.dispatch( event.build() );
502     }
503 
504     private void metadataDownloading( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
505                                       ArtifactRepository repository )
506     {
507         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADING );
508         event.setTrace( trace );
509         event.setMetadata( metadata );
510         event.setRepository( repository );
511 
512         repositoryEventDispatcher.dispatch( event.build() );
513     }
514 
515     private void metadataDownloaded( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
516                                      ArtifactRepository repository, File file, Exception exception )
517     {
518         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADED );
519         event.setTrace( trace );
520         event.setMetadata( metadata );
521         event.setRepository( repository );
522         event.setException( exception );
523         event.setFile( file );
524 
525         repositoryEventDispatcher.dispatch( event.build() );
526     }
527 
528     private Executor getExecutor( int threads )
529     {
530         if ( threads <= 1 )
531         {
532             return new Executor()
533             {
534                 public void execute( Runnable command )
535                 {
536                     command.run();
537                 }
538             };
539         }
540         else
541         {
542             return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
543                                            new WorkerThreadFactory( null ) );
544         }
545     }
546 
547     private void shutdown( Executor executor )
548     {
549         if ( executor instanceof ExecutorService )
550         {
551             ( (ExecutorService) executor ).shutdown();
552         }
553     }
554 
555     class ResolveTask
556         implements Runnable
557     {
558 
559         final RepositorySystemSession session;
560 
561         final RequestTrace trace;
562 
563         final MetadataResult result;
564 
565         final MetadataRequest request;
566 
567         final File metadataFile;
568 
569         final String policy;
570 
571         final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
572 
573         volatile MetadataTransferException exception;
574 
575         public ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
576                             File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
577                             String policy )
578         {
579             this.session = session;
580             this.trace = trace;
581             this.result = result;
582             this.request = result.getRequest();
583             this.metadataFile = metadataFile;
584             this.policy = policy;
585             this.checks = checks;
586         }
587 
588         public void run()
589         {
590             Metadata metadata = request.getMetadata();
591             RemoteRepository requestRepository = request.getRepository();
592 
593             metadataDownloading( session, trace, metadata, requestRepository );
594 
595             try
596             {
597                 List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
598                 for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
599                 {
600                     repositories.add( check.getAuthoritativeRepository() );
601                 }
602 
603                 MetadataDownload download = new MetadataDownload();
604                 download.setMetadata( metadata );
605                 download.setRequestContext( request.getRequestContext() );
606                 download.setFile( metadataFile );
607                 download.setChecksumPolicy( policy );
608                 download.setRepositories( repositories );
609                 download.setListener( SafeTransferListener.wrap( session, logger ) );
610                 download.setTrace( trace );
611 
612                 RepositoryConnector connector =
613                     repositoryConnectorProvider.newRepositoryConnector( session, requestRepository );
614                 try
615                 {
616                     connector.get( null, Arrays.asList( download ) );
617                 }
618                 finally
619                 {
620                     connector.close();
621                 }
622 
623                 exception = download.getException();
624 
625                 if ( exception == null )
626                 {
627 
628                     List<String> contexts = Collections.singletonList( request.getRequestContext() );
629                     LocalMetadataRegistration registration =
630                         new LocalMetadataRegistration( metadata, requestRepository, contexts );
631 
632                     session.getLocalRepositoryManager().add( session, registration );
633                 }
634                 else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
635                 {
636                     download.getFile().delete();
637                 }
638             }
639             catch ( NoRepositoryConnectorException e )
640             {
641                 exception = new MetadataTransferException( metadata, requestRepository, e );
642             }
643 
644             /*
645              * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
646              * "already updated" via session data when actual update to local repo is still pending.
647              */
648             for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
649             {
650                 updateCheckManager.touchMetadata( session, check.setException( exception ) );
651             }
652 
653             metadataDownloaded( session, trace, metadata, requestRepository, metadataFile, exception );
654         }
655 
656     }
657 
658 }