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