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