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.RemoteRepositoryManager;
48  import org.eclipse.aether.impl.RepositoryConnectorProvider;
49  import org.eclipse.aether.impl.RepositoryEventDispatcher;
50  import org.eclipse.aether.spi.synccontext.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  @Singleton
79  @Named
80  public class DefaultMetadataResolver
81      implements MetadataResolver, Service
82  {
83  
84      private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
85  
86      private RepositoryEventDispatcher repositoryEventDispatcher;
87  
88      private UpdateCheckManager updateCheckManager;
89  
90      private RepositoryConnectorProvider repositoryConnectorProvider;
91  
92      private RemoteRepositoryManager remoteRepositoryManager;
93  
94      private SyncContextFactory syncContextFactory;
95  
96      private OfflineController offlineController;
97  
98      public DefaultMetadataResolver()
99      {
100         // enables default constructor
101     }
102 
103     @Inject
104     DefaultMetadataResolver( RepositoryEventDispatcher repositoryEventDispatcher,
105                              UpdateCheckManager updateCheckManager,
106                              RepositoryConnectorProvider repositoryConnectorProvider,
107                              RemoteRepositoryManager remoteRepositoryManager, SyncContextFactory syncContextFactory,
108                              OfflineController offlineController )
109     {
110         setRepositoryEventDispatcher( repositoryEventDispatcher );
111         setUpdateCheckManager( updateCheckManager );
112         setRepositoryConnectorProvider( repositoryConnectorProvider );
113         setRemoteRepositoryManager( remoteRepositoryManager );
114         setSyncContextFactory( syncContextFactory );
115         setOfflineController( offlineController );
116     }
117 
118     public void initService( ServiceLocator locator )
119     {
120         setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
121         setUpdateCheckManager( locator.getService( UpdateCheckManager.class ) );
122         setRepositoryConnectorProvider( locator.getService( RepositoryConnectorProvider.class ) );
123         setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) );
124         setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
125         setOfflineController( locator.getService( OfflineController.class ) );
126     }
127 
128     public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
129     {
130         this.repositoryEventDispatcher = requireNonNull(
131                 repositoryEventDispatcher, "repository event dispatcher cannot be null" );
132         return this;
133     }
134 
135     public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager )
136     {
137         this.updateCheckManager = requireNonNull( updateCheckManager, "update check manager cannot be null" );
138         return this;
139     }
140 
141     public DefaultMetadataResolver setRepositoryConnectorProvider(
142             RepositoryConnectorProvider repositoryConnectorProvider )
143     {
144         this.repositoryConnectorProvider = requireNonNull(
145                 repositoryConnectorProvider, "repository connector provider cannot be null" );
146         return this;
147     }
148 
149     public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
150     {
151         this.remoteRepositoryManager = requireNonNull(
152                 remoteRepositoryManager, "remote repository provider cannot be null" );
153         return this;
154     }
155 
156     public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
157     {
158         this.syncContextFactory = requireNonNull( syncContextFactory, "sync context factory cannot be null" );
159         return this;
160     }
161 
162     public DefaultMetadataResolver setOfflineController( OfflineController offlineController )
163     {
164         this.offlineController = requireNonNull( offlineController, "offline controller cannot be null" );
165         return this;
166     }
167 
168     public List<MetadataResult> resolveMetadata( RepositorySystemSession session,
169                                                  Collection<? extends MetadataRequest> requests )
170     {
171         requireNonNull( session, "session cannot be null" );
172         requireNonNull( requests, "requests cannot be null" );
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 command -> command.run();
509         }
510         else
511         {
512             return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
513                                            new WorkerThreadFactory( null ) );
514         }
515     }
516 
517     private void shutdown( Executor executor )
518     {
519         if ( executor instanceof ExecutorService )
520         {
521             ( (ExecutorService) executor ).shutdown();
522         }
523     }
524 
525     class ResolveTask
526         implements Runnable
527     {
528         final RepositorySystemSession session;
529 
530         final RequestTrace trace;
531 
532         final MetadataResult result;
533 
534         final MetadataRequest request;
535 
536         final File metadataFile;
537 
538         final String policy;
539 
540         final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
541 
542         volatile MetadataTransferException exception;
543 
544         ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
545                             File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
546                             String policy )
547         {
548             this.session = session;
549             this.trace = trace;
550             this.result = result;
551             this.request = result.getRequest();
552             this.metadataFile = metadataFile;
553             this.policy = policy;
554             this.checks = checks;
555         }
556 
557         public void run()
558         {
559             Metadata metadata = request.getMetadata();
560             RemoteRepository requestRepository = request.getRepository();
561 
562             try
563             {
564                 List<RemoteRepository> repositories = new ArrayList<>();
565                 for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
566                 {
567                     repositories.add( check.getAuthoritativeRepository() );
568                 }
569 
570                 MetadataDownload download = new MetadataDownload();
571                 download.setMetadata( metadata );
572                 download.setRequestContext( request.getRequestContext() );
573                 download.setFile( metadataFile );
574                 download.setChecksumPolicy( policy );
575                 download.setRepositories( repositories );
576                 download.setListener( SafeTransferListener.wrap( session ) );
577                 download.setTrace( trace );
578 
579                 try ( RepositoryConnector connector =
580                               repositoryConnectorProvider.newRepositoryConnector( session, requestRepository ) )
581                 {
582                     connector.get( null, Collections.singletonList( download ) );
583                 }
584 
585                 exception = download.getException();
586 
587                 if ( exception == null )
588                 {
589 
590                     List<String> contexts = Collections.singletonList( request.getRequestContext() );
591                     LocalMetadataRegistration registration =
592                         new LocalMetadataRegistration( metadata, requestRepository, contexts );
593 
594                     session.getLocalRepositoryManager().add( session, registration );
595                 }
596                 else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
597                 {
598                     download.getFile().delete();
599                 }
600             }
601             catch ( NoRepositoryConnectorException e )
602             {
603                 exception = new MetadataTransferException( metadata, requestRepository, e );
604             }
605         }
606     }
607 }