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 
173         try ( SyncContext syncContext = syncContextFactory.newInstance( session, false ) )
174         {
175             Collection<Metadata> metadata = new ArrayList<>( requests.size() );
176             for ( MetadataRequest request : requests )
177             {
178                 metadata.add( request.getMetadata() );
179             }
180 
181             syncContext.acquire( null, metadata );
182 
183             return resolve( session, requests );
184         }
185     }
186 
187     @SuppressWarnings( "checkstyle:methodlength" )
188     private List<MetadataResult> resolve( RepositorySystemSession session,
189                                           Collection<? extends MetadataRequest> requests )
190     {
191         List<MetadataResult> results = new ArrayList<>( requests.size() );
192 
193         List<ResolveTask> tasks = new ArrayList<>( requests.size() );
194 
195         Map<File, Long> localLastUpdates = new HashMap<>();
196 
197         for ( MetadataRequest request : requests )
198         {
199             RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
200 
201             MetadataResult result = new MetadataResult( request );
202             results.add( result );
203 
204             Metadata metadata = request.getMetadata();
205             RemoteRepository repository = request.getRepository();
206 
207             if ( repository == null )
208             {
209                 LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
210 
211                 metadataResolving( session, trace, metadata, localRepo );
212 
213                 File localFile = getLocalFile( session, metadata );
214 
215                 if ( localFile != null )
216                 {
217                     metadata = metadata.setFile( localFile );
218                     result.setMetadata( metadata );
219                 }
220                 else
221                 {
222                     result.setException( new MetadataNotFoundException( metadata, localRepo ) );
223                 }
224 
225                 metadataResolved( session, trace, metadata, localRepo, result.getException() );
226                 continue;
227             }
228 
229             List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() );
230 
231             if ( repositories.isEmpty() )
232             {
233                 continue;
234             }
235 
236             metadataResolving( session, trace, metadata, repository );
237             LocalRepositoryManager lrm = session.getLocalRepositoryManager();
238             LocalMetadataRequest localRequest =
239                 new LocalMetadataRequest( metadata, repository, request.getRequestContext() );
240             LocalMetadataResult lrmResult = lrm.find( session, localRequest );
241 
242             File metadataFile = lrmResult.getFile();
243 
244             try
245             {
246                 Utils.checkOffline( session, offlineController, repository );
247             }
248             catch ( RepositoryOfflineException e )
249             {
250                 if ( metadataFile != null )
251                 {
252                     metadata = metadata.setFile( metadataFile );
253                     result.setMetadata( metadata );
254                 }
255                 else
256                 {
257                     String msg =
258                         "Cannot access " + repository.getId() + " (" + repository.getUrl()
259                             + ") in offline mode and the metadata " + metadata
260                             + " has not been downloaded from it before";
261                     result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) );
262                 }
263 
264                 metadataResolved( session, trace, metadata, repository, result.getException() );
265                 continue;
266             }
267 
268             Long localLastUpdate = null;
269             if ( request.isFavorLocalRepository() )
270             {
271                 File localFile = getLocalFile( session, metadata );
272                 localLastUpdate = localLastUpdates.get( localFile );
273                 if ( localLastUpdate == null )
274                 {
275                     localLastUpdate = localFile != null ? localFile.lastModified() : 0;
276                     localLastUpdates.put( localFile, localLastUpdate );
277                 }
278             }
279 
280             List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>();
281             Exception exception = null;
282             for ( RemoteRepository repo : repositories )
283             {
284                 UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
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 = new File(
290                         session.getLocalRepository().getBasedir(),
291                         session.getLocalRepositoryManager()
292                                 .getPathForRemoteMetadata( metadata, repository, request.getRequestContext() ) );
293                 check.setFile( checkFile );
294                 check.setRepository( repository );
295                 check.setAuthoritativeRepository( repo );
296                 check.setPolicy( getPolicy( session, repo, metadata.getNature() ).getUpdatePolicy() );
297 
298                 if ( lrmResult.isStale() )
299                 {
300                     checks.add( check );
301                 }
302                 else
303                 {
304                     updateCheckManager.checkMetadata( session, check );
305                     if ( check.isRequired() )
306                     {
307                         checks.add( check );
308                     }
309                     else if ( exception == null )
310                     {
311                         exception = check.getException();
312                     }
313                 }
314             }
315 
316             if ( !checks.isEmpty() )
317             {
318                 RepositoryPolicy policy = getPolicy( session, repository, metadata.getNature() );
319 
320                 // install path may be different from lookup path
321                 File installFile = new File(
322                         session.getLocalRepository().getBasedir(),
323                         session.getLocalRepositoryManager().getPathForRemoteMetadata(
324                                 metadata, request.getRepository(), request.getRequestContext() ) );
325 
326                 metadataDownloading(
327                         session, trace, result.getRequest().getMetadata(), result.getRequest().getRepository() );
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                     /*
363                      * NOTE: Touch after registration with local repo to ensure concurrent resolution is not
364                      * rejected with "already updated" via session data when actual update to local repo is
365                      * still pending.
366                      */
367                     for ( UpdateCheck<Metadata, MetadataTransferException> check : task.checks )
368                     {
369                         updateCheckManager.touchMetadata( task.session, check.setException( task.exception ) );
370                     }
371 
372                     metadataDownloaded( session, task.trace, task.request.getMetadata(), task.request.getRepository(),
373                             task.metadataFile, task.exception );
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 = new LocalMetadataRequest(
387                         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         return localResult.getFile();
411     }
412 
413     private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature )
414     {
415         List<RemoteRepository> repositories = new ArrayList<>();
416 
417         if ( repository.isRepositoryManager() )
418         {
419             for ( RemoteRepository repo : repository.getMirroredRepositories() )
420             {
421                 if ( isEnabled( repo, nature ) )
422                 {
423                     repositories.add( repo );
424                 }
425             }
426         }
427         else if ( isEnabled( repository, nature ) )
428         {
429             repositories.add( repository );
430         }
431 
432         return repositories;
433     }
434 
435     private boolean isEnabled( RemoteRepository repository, Metadata.Nature nature )
436     {
437         if ( !Metadata.Nature.SNAPSHOT.equals( nature ) && repository.getPolicy( false ).isEnabled() )
438         {
439             return true;
440         }
441         if ( !Metadata.Nature.RELEASE.equals( nature ) && repository.getPolicy( true ).isEnabled() )
442         {
443             return true;
444         }
445         return false;
446     }
447 
448     private RepositoryPolicy getPolicy( RepositorySystemSession session, RemoteRepository repository,
449                                         Metadata.Nature nature )
450     {
451         boolean releases = !Metadata.Nature.SNAPSHOT.equals( nature );
452         boolean snapshots = !Metadata.Nature.RELEASE.equals( nature );
453         return remoteRepositoryManager.getPolicy( session, repository, releases, snapshots );
454     }
455 
456     private void metadataResolving( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
457                                     ArtifactRepository repository )
458     {
459         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVING );
460         event.setTrace( trace );
461         event.setMetadata( metadata );
462         event.setRepository( repository );
463 
464         repositoryEventDispatcher.dispatch( event.build() );
465     }
466 
467     private void metadataResolved( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
468                                    ArtifactRepository repository, Exception exception )
469     {
470         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVED );
471         event.setTrace( trace );
472         event.setMetadata( metadata );
473         event.setRepository( repository );
474         event.setException( exception );
475         event.setFile( metadata.getFile() );
476 
477         repositoryEventDispatcher.dispatch( event.build() );
478     }
479 
480     private void metadataDownloading( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
481                                       ArtifactRepository repository )
482     {
483         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADING );
484         event.setTrace( trace );
485         event.setMetadata( metadata );
486         event.setRepository( repository );
487 
488         repositoryEventDispatcher.dispatch( event.build() );
489     }
490 
491     private void metadataDownloaded( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
492                                      ArtifactRepository repository, File file, Exception exception )
493     {
494         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADED );
495         event.setTrace( trace );
496         event.setMetadata( metadata );
497         event.setRepository( repository );
498         event.setException( exception );
499         event.setFile( file );
500 
501         repositoryEventDispatcher.dispatch( event.build() );
502     }
503 
504     private Executor getExecutor( int threads )
505     {
506         if ( threads <= 1 )
507         {
508             return new Executor()
509             {
510                 public void execute( Runnable command )
511                 {
512                     command.run();
513                 }
514             };
515         }
516         else
517         {
518             return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
519                                            new WorkerThreadFactory( null ) );
520         }
521     }
522 
523     private void shutdown( Executor executor )
524     {
525         if ( executor instanceof ExecutorService )
526         {
527             ( (ExecutorService) executor ).shutdown();
528         }
529     }
530 
531     class ResolveTask
532         implements Runnable
533     {
534         final RepositorySystemSession session;
535 
536         final RequestTrace trace;
537 
538         final MetadataResult result;
539 
540         final MetadataRequest request;
541 
542         final File metadataFile;
543 
544         final String policy;
545 
546         final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
547 
548         volatile MetadataTransferException exception;
549 
550         ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
551                             File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
552                             String policy )
553         {
554             this.session = session;
555             this.trace = trace;
556             this.result = result;
557             this.request = result.getRequest();
558             this.metadataFile = metadataFile;
559             this.policy = policy;
560             this.checks = checks;
561         }
562 
563         public void run()
564         {
565             Metadata metadata = request.getMetadata();
566             RemoteRepository requestRepository = request.getRepository();
567 
568             try
569             {
570                 List<RemoteRepository> repositories = new ArrayList<>();
571                 for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
572                 {
573                     repositories.add( check.getAuthoritativeRepository() );
574                 }
575 
576                 MetadataDownload download = new MetadataDownload();
577                 download.setMetadata( metadata );
578                 download.setRequestContext( request.getRequestContext() );
579                 download.setFile( metadataFile );
580                 download.setChecksumPolicy( policy );
581                 download.setRepositories( repositories );
582                 download.setListener( SafeTransferListener.wrap( session ) );
583                 download.setTrace( trace );
584 
585                 try ( RepositoryConnector connector =
586                               repositoryConnectorProvider.newRepositoryConnector( session, requestRepository ) )
587                 {
588                     connector.get( null, Arrays.asList( download ) );
589                 }
590 
591                 exception = download.getException();
592 
593                 if ( exception == null )
594                 {
595 
596                     List<String> contexts = Collections.singletonList( request.getRequestContext() );
597                     LocalMetadataRegistration registration =
598                         new LocalMetadataRegistration( metadata, requestRepository, contexts );
599 
600                     session.getLocalRepositoryManager().add( session, registration );
601                 }
602                 else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
603                 {
604                     download.getFile().delete();
605                 }
606             }
607             catch ( NoRepositoryConnectorException e )
608             {
609                 exception = new MetadataTransferException( metadata, requestRepository, e );
610             }
611         }
612     }
613 }