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