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  import javax.inject.Singleton;
40  
41  import org.eclipse.aether.RepositoryEvent;
42  import org.eclipse.aether.RepositoryEvent.EventType;
43  import org.eclipse.aether.RepositorySystemSession;
44  import org.eclipse.aether.RequestTrace;
45  import org.eclipse.aether.SyncContext;
46  import org.eclipse.aether.impl.MetadataResolver;
47  import org.eclipse.aether.impl.OfflineController;
48  import org.eclipse.aether.impl.RemoteRepositoryManager;
49  import org.eclipse.aether.impl.RepositoryConnectorProvider;
50  import org.eclipse.aether.impl.RepositoryEventDispatcher;
51  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
52  import org.eclipse.aether.impl.UpdateCheck;
53  import org.eclipse.aether.impl.UpdateCheckManager;
54  import org.eclipse.aether.metadata.Metadata;
55  import org.eclipse.aether.repository.ArtifactRepository;
56  import org.eclipse.aether.repository.LocalMetadataRegistration;
57  import org.eclipse.aether.repository.LocalMetadataRequest;
58  import org.eclipse.aether.repository.LocalMetadataResult;
59  import org.eclipse.aether.repository.LocalRepository;
60  import org.eclipse.aether.repository.LocalRepositoryManager;
61  import org.eclipse.aether.repository.RemoteRepository;
62  import org.eclipse.aether.repository.RepositoryPolicy;
63  import org.eclipse.aether.resolution.MetadataRequest;
64  import org.eclipse.aether.resolution.MetadataResult;
65  import org.eclipse.aether.spi.connector.MetadataDownload;
66  import org.eclipse.aether.spi.connector.RepositoryConnector;
67  import org.eclipse.aether.spi.locator.Service;
68  import org.eclipse.aether.spi.locator.ServiceLocator;
69  import org.eclipse.aether.transfer.MetadataNotFoundException;
70  import org.eclipse.aether.transfer.MetadataTransferException;
71  import org.eclipse.aether.transfer.NoRepositoryConnectorException;
72  import org.eclipse.aether.transfer.RepositoryOfflineException;
73  import org.eclipse.aether.util.ConfigUtils;
74  import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
75  import org.eclipse.aether.util.concurrency.WorkerThreadFactory;
76  
77  /**
78   */
79  @Singleton
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 RepositoryEventDispatcher repositoryEventDispatcher;
88  
89      private UpdateCheckManager updateCheckManager;
90  
91      private RepositoryConnectorProvider repositoryConnectorProvider;
92  
93      private RemoteRepositoryManager remoteRepositoryManager;
94  
95      private SyncContextFactory syncContextFactory;
96  
97      private OfflineController offlineController;
98  
99      public DefaultMetadataResolver()
100     {
101         // enables default constructor
102     }
103 
104     @Inject
105     DefaultMetadataResolver( RepositoryEventDispatcher repositoryEventDispatcher,
106                              UpdateCheckManager updateCheckManager,
107                              RepositoryConnectorProvider repositoryConnectorProvider,
108                              RemoteRepositoryManager remoteRepositoryManager, SyncContextFactory syncContextFactory,
109                              OfflineController offlineController )
110     {
111         setRepositoryEventDispatcher( repositoryEventDispatcher );
112         setUpdateCheckManager( updateCheckManager );
113         setRepositoryConnectorProvider( repositoryConnectorProvider );
114         setRemoteRepositoryManager( remoteRepositoryManager );
115         setSyncContextFactory( syncContextFactory );
116         setOfflineController( offlineController );
117     }
118 
119     public void initService( ServiceLocator locator )
120     {
121         setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
122         setUpdateCheckManager( locator.getService( UpdateCheckManager.class ) );
123         setRepositoryConnectorProvider( locator.getService( RepositoryConnectorProvider.class ) );
124         setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) );
125         setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
126         setOfflineController( locator.getService( OfflineController.class ) );
127     }
128 
129     public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
130     {
131         this.repositoryEventDispatcher = requireNonNull(
132                 repositoryEventDispatcher, "repository event dispatcher cannot be null" );
133         return this;
134     }
135 
136     public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager )
137     {
138         this.updateCheckManager = requireNonNull( updateCheckManager, "update check manager cannot be null" );
139         return this;
140     }
141 
142     public DefaultMetadataResolver setRepositoryConnectorProvider(
143             RepositoryConnectorProvider repositoryConnectorProvider )
144     {
145         this.repositoryConnectorProvider = requireNonNull(
146                 repositoryConnectorProvider, "repository connector provider cannot be null" );
147         return this;
148     }
149 
150     public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
151     {
152         this.remoteRepositoryManager = requireNonNull(
153                 remoteRepositoryManager, "remote repository provider cannot be null" );
154         return this;
155     }
156 
157     public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
158     {
159         this.syncContextFactory = requireNonNull( syncContextFactory, "sync context factory cannot be null" );
160         return this;
161     }
162 
163     public DefaultMetadataResolver setOfflineController( OfflineController offlineController )
164     {
165         this.offlineController = requireNonNull( offlineController, "offline controller cannot be null" );
166         return this;
167     }
168 
169     public List<MetadataResult> resolveMetadata( RepositorySystemSession session,
170                                                  Collection<? extends MetadataRequest> requests )
171     {
172         requireNonNull( session, "session cannot be null" );
173         requireNonNull( requests, "requests cannot be null" );
174         try ( SyncContext syncContext = syncContextFactory.newInstance( session, false ) )
175         {
176             Collection<Metadata> metadata = new ArrayList<>( requests.size() );
177             for ( MetadataRequest request : requests )
178             {
179                 metadata.add( request.getMetadata() );
180             }
181 
182             syncContext.acquire( null, metadata );
183 
184             return resolve( session, requests );
185         }
186     }
187 
188     @SuppressWarnings( "checkstyle:methodlength" )
189     private List<MetadataResult> resolve( RepositorySystemSession session,
190                                           Collection<? extends MetadataRequest> requests )
191     {
192         List<MetadataResult> results = new ArrayList<>( requests.size() );
193 
194         List<ResolveTask> tasks = new ArrayList<>( requests.size() );
195 
196         Map<File, Long> localLastUpdates = new HashMap<>();
197 
198         for ( MetadataRequest request : requests )
199         {
200             RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
201 
202             MetadataResult result = new MetadataResult( request );
203             results.add( result );
204 
205             Metadata metadata = request.getMetadata();
206             RemoteRepository repository = request.getRepository();
207 
208             if ( repository == null )
209             {
210                 LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
211 
212                 metadataResolving( session, trace, metadata, localRepo );
213 
214                 File localFile = getLocalFile( session, metadata );
215 
216                 if ( localFile != null )
217                 {
218                     metadata = metadata.setFile( localFile );
219                     result.setMetadata( metadata );
220                 }
221                 else
222                 {
223                     result.setException( new MetadataNotFoundException( metadata, localRepo ) );
224                 }
225 
226                 metadataResolved( session, trace, metadata, localRepo, result.getException() );
227                 continue;
228             }
229 
230             List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() );
231 
232             if ( repositories.isEmpty() )
233             {
234                 continue;
235             }
236 
237             metadataResolving( session, trace, metadata, repository );
238             LocalRepositoryManager lrm = session.getLocalRepositoryManager();
239             LocalMetadataRequest localRequest =
240                 new LocalMetadataRequest( metadata, repository, request.getRequestContext() );
241             LocalMetadataResult lrmResult = lrm.find( session, localRequest );
242 
243             File metadataFile = lrmResult.getFile();
244 
245             try
246             {
247                 Utils.checkOffline( session, offlineController, repository );
248             }
249             catch ( RepositoryOfflineException e )
250             {
251                 if ( metadataFile != null )
252                 {
253                     metadata = metadata.setFile( metadataFile );
254                     result.setMetadata( metadata );
255                 }
256                 else
257                 {
258                     String msg =
259                         "Cannot access " + repository.getId() + " (" + repository.getUrl()
260                             + ") in offline mode and the metadata " + metadata
261                             + " has not been downloaded from it before";
262                     result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) );
263                 }
264 
265                 metadataResolved( session, trace, metadata, repository, result.getException() );
266                 continue;
267             }
268 
269             Long localLastUpdate = null;
270             if ( request.isFavorLocalRepository() )
271             {
272                 File localFile = getLocalFile( session, metadata );
273                 localLastUpdate = localLastUpdates.get( localFile );
274                 if ( localLastUpdate == null )
275                 {
276                     localLastUpdate = localFile != null ? localFile.lastModified() : 0;
277                     localLastUpdates.put( localFile, localLastUpdate );
278                 }
279             }
280 
281             List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>();
282             Exception exception = null;
283             for ( RemoteRepository repo : repositories )
284             {
285                 UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
286                 check.setLocalLastUpdated( ( localLastUpdate != null ) ? localLastUpdate : 0 );
287                 check.setItem( metadata );
288 
289                 // use 'main' installation file for the check (-> use requested repository)
290                 File checkFile = new File(
291                         session.getLocalRepository().getBasedir(),
292                         session.getLocalRepositoryManager()
293                                 .getPathForRemoteMetadata( metadata, repository, 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 = new File(
323                         session.getLocalRepository().getBasedir(),
324                         session.getLocalRepositoryManager().getPathForRemoteMetadata(
325                                 metadata, request.getRepository(), request.getRequestContext() ) );
326 
327                 metadataDownloading(
328                         session, trace, result.getRequest().getMetadata(), result.getRequest().getRepository() );
329 
330                 ResolveTask task =
331                     new ResolveTask( session, trace, result, installFile, checks, policy.getChecksumPolicy() );
332                 tasks.add( task );
333             }
334             else
335             {
336                 result.setException( exception );
337                 if ( metadataFile != null )
338                 {
339                     metadata = metadata.setFile( metadataFile );
340                     result.setMetadata( metadata );
341                 }
342                 metadataResolved( session, trace, metadata, repository, result.getException() );
343             }
344         }
345 
346         if ( !tasks.isEmpty() )
347         {
348             int threads = ConfigUtils.getInteger( session, 4, CONFIG_PROP_THREADS );
349             Executor executor = getExecutor( Math.min( tasks.size(), threads ) );
350             try
351             {
352                 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
353 
354                 for ( ResolveTask task : tasks )
355                 {
356                     executor.execute( errorForwarder.wrap( task ) );
357                 }
358 
359                 errorForwarder.await();
360 
361                 for ( ResolveTask task : tasks )
362                 {
363                     /*
364                      * NOTE: Touch after registration with local repo to ensure concurrent resolution is not
365                      * rejected with "already updated" via session data when actual update to local repo is
366                      * still pending.
367                      */
368                     for ( UpdateCheck<Metadata, MetadataTransferException> check : task.checks )
369                     {
370                         updateCheckManager.touchMetadata( task.session, check.setException( task.exception ) );
371                     }
372 
373                     metadataDownloaded( session, task.trace, task.request.getMetadata(), task.request.getRepository(),
374                             task.metadataFile, task.exception );
375 
376                     task.result.setException( task.exception );
377                 }
378             }
379             finally
380             {
381                 shutdown( executor );
382             }
383             for ( ResolveTask task : tasks )
384             {
385                 Metadata metadata = task.request.getMetadata();
386                 // re-lookup metadata for resolve
387                 LocalMetadataRequest localRequest = new LocalMetadataRequest(
388                         metadata, task.request.getRepository(), task.request.getRequestContext() );
389                 File metadataFile = session.getLocalRepositoryManager().find( session, localRequest ).getFile();
390                 if ( metadataFile != null )
391                 {
392                     metadata = metadata.setFile( metadataFile );
393                     task.result.setMetadata( metadata );
394                 }
395                 if ( task.result.getException() == null )
396                 {
397                     task.result.setUpdated( true );
398                 }
399                 metadataResolved( session, task.trace, metadata, task.request.getRepository(),
400                                   task.result.getException() );
401             }
402         }
403 
404         return results;
405     }
406 
407     private File getLocalFile( RepositorySystemSession session, Metadata metadata )
408     {
409         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
410         LocalMetadataResult localResult = lrm.find( session, new LocalMetadataRequest( metadata, null, null ) );
411         return localResult.getFile();
412     }
413 
414     private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature )
415     {
416         List<RemoteRepository> repositories = new ArrayList<>();
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 command -> command.run();
510         }
511         else
512         {
513             return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
514                                            new WorkerThreadFactory( null ) );
515         }
516     }
517 
518     private void shutdown( Executor executor )
519     {
520         if ( executor instanceof ExecutorService )
521         {
522             ( (ExecutorService) executor ).shutdown();
523         }
524     }
525 
526     class ResolveTask
527         implements Runnable
528     {
529         final RepositorySystemSession session;
530 
531         final RequestTrace trace;
532 
533         final MetadataResult result;
534 
535         final MetadataRequest request;
536 
537         final File metadataFile;
538 
539         final String policy;
540 
541         final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
542 
543         volatile MetadataTransferException exception;
544 
545         ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
546                             File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
547                             String policy )
548         {
549             this.session = session;
550             this.trace = trace;
551             this.result = result;
552             this.request = result.getRequest();
553             this.metadataFile = metadataFile;
554             this.policy = policy;
555             this.checks = checks;
556         }
557 
558         public void run()
559         {
560             Metadata metadata = request.getMetadata();
561             RemoteRepository requestRepository = request.getRepository();
562 
563             try
564             {
565                 List<RemoteRepository> repositories = new ArrayList<>();
566                 for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
567                 {
568                     repositories.add( check.getAuthoritativeRepository() );
569                 }
570 
571                 MetadataDownload download = new MetadataDownload();
572                 download.setMetadata( metadata );
573                 download.setRequestContext( request.getRequestContext() );
574                 download.setFile( metadataFile );
575                 download.setChecksumPolicy( policy );
576                 download.setRepositories( repositories );
577                 download.setListener( SafeTransferListener.wrap( session ) );
578                 download.setTrace( trace );
579 
580                 try ( RepositoryConnector connector =
581                               repositoryConnectorProvider.newRepositoryConnector( session, requestRepository ) )
582                 {
583                     connector.get( null, Arrays.asList( download ) );
584                 }
585 
586                 exception = download.getException();
587 
588                 if ( exception == null )
589                 {
590 
591                     List<String> contexts = Collections.singletonList( request.getRequestContext() );
592                     LocalMetadataRegistration registration =
593                         new LocalMetadataRegistration( metadata, requestRepository, contexts );
594 
595                     session.getLocalRepositoryManager().add( session, registration );
596                 }
597                 else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
598                 {
599                     download.getFile().delete();
600                 }
601             }
602             catch ( NoRepositoryConnectorException e )
603             {
604                 exception = new MetadataTransferException( metadata, requestRepository, e );
605             }
606         }
607     }
608 }