001package org.eclipse.aether.internal.impl;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import static java.util.Objects.requireNonNull;
031import java.util.concurrent.Executor;
032import java.util.concurrent.ExecutorService;
033import java.util.concurrent.LinkedBlockingQueue;
034import java.util.concurrent.ThreadPoolExecutor;
035import java.util.concurrent.TimeUnit;
036
037import javax.inject.Inject;
038import javax.inject.Named;
039
040import org.eclipse.aether.RepositoryEvent;
041import org.eclipse.aether.RepositoryEvent.EventType;
042import org.eclipse.aether.RepositorySystemSession;
043import org.eclipse.aether.RequestTrace;
044import org.eclipse.aether.SyncContext;
045import org.eclipse.aether.impl.MetadataResolver;
046import org.eclipse.aether.impl.OfflineController;
047import org.eclipse.aether.impl.RemoteRepositoryManager;
048import org.eclipse.aether.impl.RepositoryConnectorProvider;
049import org.eclipse.aether.impl.RepositoryEventDispatcher;
050import org.eclipse.aether.impl.SyncContextFactory;
051import org.eclipse.aether.impl.UpdateCheck;
052import org.eclipse.aether.impl.UpdateCheckManager;
053import org.eclipse.aether.metadata.Metadata;
054import org.eclipse.aether.repository.ArtifactRepository;
055import org.eclipse.aether.repository.LocalMetadataRegistration;
056import org.eclipse.aether.repository.LocalMetadataRequest;
057import org.eclipse.aether.repository.LocalMetadataResult;
058import org.eclipse.aether.repository.LocalRepository;
059import org.eclipse.aether.repository.LocalRepositoryManager;
060import org.eclipse.aether.repository.RemoteRepository;
061import org.eclipse.aether.repository.RepositoryPolicy;
062import org.eclipse.aether.resolution.MetadataRequest;
063import org.eclipse.aether.resolution.MetadataResult;
064import org.eclipse.aether.spi.connector.MetadataDownload;
065import org.eclipse.aether.spi.connector.RepositoryConnector;
066import org.eclipse.aether.spi.locator.Service;
067import org.eclipse.aether.spi.locator.ServiceLocator;
068import org.eclipse.aether.spi.log.Logger;
069import org.eclipse.aether.spi.log.LoggerFactory;
070import org.eclipse.aether.spi.log.NullLoggerFactory;
071import org.eclipse.aether.transfer.MetadataNotFoundException;
072import org.eclipse.aether.transfer.MetadataTransferException;
073import org.eclipse.aether.transfer.NoRepositoryConnectorException;
074import org.eclipse.aether.transfer.RepositoryOfflineException;
075import org.eclipse.aether.util.ConfigUtils;
076import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
077import org.eclipse.aether.util.concurrency.WorkerThreadFactory;
078
079/**
080 */
081@Named
082public class DefaultMetadataResolver
083    implements MetadataResolver, Service
084{
085
086    private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
087
088    private Logger logger = NullLoggerFactory.LOGGER;
089
090    private RepositoryEventDispatcher repositoryEventDispatcher;
091
092    private UpdateCheckManager updateCheckManager;
093
094    private RepositoryConnectorProvider repositoryConnectorProvider;
095
096    private RemoteRepositoryManager remoteRepositoryManager;
097
098    private SyncContextFactory syncContextFactory;
099
100    private OfflineController offlineController;
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, LoggerFactory loggerFactory )
113    {
114        setRepositoryEventDispatcher( repositoryEventDispatcher );
115        setUpdateCheckManager( updateCheckManager );
116        setRepositoryConnectorProvider( repositoryConnectorProvider );
117        setRemoteRepositoryManager( remoteRepositoryManager );
118        setSyncContextFactory( syncContextFactory );
119        setOfflineController( offlineController );
120        setLoggerFactory( loggerFactory );
121    }
122
123    public void initService( ServiceLocator locator )
124    {
125        setLoggerFactory( locator.getService( LoggerFactory.class ) );
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    }
133
134    public DefaultMetadataResolver setLoggerFactory( LoggerFactory loggerFactory )
135    {
136        this.logger = NullLoggerFactory.getSafeLogger( loggerFactory, getClass() );
137        return this;
138    }
139
140    public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
141    {
142        this.repositoryEventDispatcher = requireNonNull( repositoryEventDispatcher, "repository event dispatcher cannot be null" );
143        return this;
144    }
145
146    public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager )
147    {
148        this.updateCheckManager = requireNonNull( updateCheckManager, "update check manager cannot be null" );
149        return this;
150    }
151
152    public DefaultMetadataResolver setRepositoryConnectorProvider( RepositoryConnectorProvider repositoryConnectorProvider )
153    {
154        this.repositoryConnectorProvider = requireNonNull( repositoryConnectorProvider, "repository connector provider cannot be null" );
155        return this;
156    }
157
158    public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
159    {
160        this.remoteRepositoryManager = requireNonNull( remoteRepositoryManager, "remote repository provider cannot be null" );
161        return this;
162    }
163
164    public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
165    {
166        this.syncContextFactory = requireNonNull( syncContextFactory, "sync context factory cannot be null" );
167        return this;
168    }
169
170    public DefaultMetadataResolver setOfflineController( OfflineController offlineController )
171    {
172        this.offlineController = requireNonNull( offlineController, "offline controller cannot be null" );
173        return this;
174    }
175
176    public List<MetadataResult> resolveMetadata( RepositorySystemSession session,
177                                                 Collection<? extends MetadataRequest> requests )
178    {
179        SyncContext syncContext = syncContextFactory.newInstance( session, false );
180
181        try
182        {
183            Collection<Metadata> metadata = new ArrayList<Metadata>( requests.size() );
184            for ( MetadataRequest request : requests )
185            {
186                metadata.add( request.getMetadata() );
187            }
188
189            syncContext.acquire( null, metadata );
190
191            return resolve( session, requests );
192        }
193        finally
194        {
195            syncContext.close();
196        }
197    }
198
199    private List<MetadataResult> resolve( RepositorySystemSession session,
200                                          Collection<? extends MetadataRequest> requests )
201    {
202        List<MetadataResult> results = new ArrayList<MetadataResult>( requests.size() );
203
204        List<ResolveTask> tasks = new ArrayList<ResolveTask>( requests.size() );
205
206        Map<File, Long> localLastUpdates = new HashMap<File, Long>();
207
208        for ( MetadataRequest request : requests )
209        {
210            RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
211
212            MetadataResult result = new MetadataResult( request );
213            results.add( result );
214
215            Metadata metadata = request.getMetadata();
216            RemoteRepository repository = request.getRepository();
217
218            if ( repository == null )
219            {
220                LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
221
222                metadataResolving( session, trace, metadata, localRepo );
223
224                File localFile = getLocalFile( session, metadata );
225
226                if ( localFile != null )
227                {
228                    metadata = metadata.setFile( localFile );
229                    result.setMetadata( metadata );
230                }
231                else
232                {
233                    result.setException( new MetadataNotFoundException( metadata, localRepo ) );
234                }
235
236                metadataResolved( session, trace, metadata, localRepo, result.getException() );
237                continue;
238            }
239
240            List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() );
241
242            if ( repositories.isEmpty() )
243            {
244                continue;
245            }
246
247            metadataResolving( session, trace, metadata, repository );
248            LocalRepositoryManager lrm = session.getLocalRepositoryManager();
249            LocalMetadataRequest localRequest =
250                new LocalMetadataRequest( metadata, repository, request.getRequestContext() );
251            LocalMetadataResult lrmResult = lrm.find( session, localRequest );
252
253            File metadataFile = lrmResult.getFile();
254
255            try
256            {
257                Utils.checkOffline( session, offlineController, repository );
258            }
259            catch ( RepositoryOfflineException e )
260            {
261                if ( metadataFile != null )
262                {
263                    metadata = metadata.setFile( metadataFile );
264                    result.setMetadata( metadata );
265                }
266                else
267                {
268                    String msg =
269                        "Cannot access " + repository.getId() + " (" + repository.getUrl()
270                            + ") in offline mode and the metadata " + metadata
271                            + " has not been downloaded from it before";
272                    result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) );
273                }
274
275                metadataResolved( session, trace, metadata, repository, result.getException() );
276                continue;
277            }
278
279            Long localLastUpdate = null;
280            if ( request.isFavorLocalRepository() )
281            {
282                File localFile = getLocalFile( session, metadata );
283                localLastUpdate = localLastUpdates.get( localFile );
284                if ( localLastUpdate == null )
285                {
286                    localLastUpdate = localFile != null ? localFile.lastModified() : 0;
287                    localLastUpdates.put( localFile, localLastUpdate );
288                }
289            }
290
291            List<UpdateCheck<Metadata, MetadataTransferException>> checks =
292                new ArrayList<UpdateCheck<Metadata, MetadataTransferException>>();
293            Exception exception = null;
294            for ( RemoteRepository repo : repositories )
295            {
296                UpdateCheck<Metadata, MetadataTransferException> check =
297                    new UpdateCheck<Metadata, MetadataTransferException>();
298                check.setLocalLastUpdated( ( localLastUpdate != null ) ? localLastUpdate : 0 );
299                check.setItem( metadata );
300
301                // use 'main' installation file for the check (-> use requested repository)
302                File checkFile =
303                    new File(
304                              session.getLocalRepository().getBasedir(),
305                              session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata, repository,
306                                                                                            request.getRequestContext() ) );
307                check.setFile( checkFile );
308                check.setRepository( repository );
309                check.setAuthoritativeRepository( repo );
310                check.setPolicy( getPolicy( session, repo, metadata.getNature() ).getUpdatePolicy() );
311
312                if ( lrmResult.isStale() )
313                {
314                    checks.add( check );
315                }
316                else
317                {
318                    updateCheckManager.checkMetadata( session, check );
319                    if ( check.isRequired() )
320                    {
321                        checks.add( check );
322                    }
323                    else if ( exception == null )
324                    {
325                        exception = check.getException();
326                    }
327                }
328            }
329
330            if ( !checks.isEmpty() )
331            {
332                RepositoryPolicy policy = getPolicy( session, repository, metadata.getNature() );
333
334                // install path may be different from lookup path
335                File installFile =
336                    new File(
337                              session.getLocalRepository().getBasedir(),
338                              session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata,
339                                                                                            request.getRepository(),
340                                                                                            request.getRequestContext() ) );
341
342                ResolveTask task =
343                    new ResolveTask( session, trace, result, installFile, checks, policy.getChecksumPolicy() );
344                tasks.add( task );
345            }
346            else
347            {
348                result.setException( exception );
349                if ( metadataFile != null )
350                {
351                    metadata = metadata.setFile( metadataFile );
352                    result.setMetadata( metadata );
353                }
354                metadataResolved( session, trace, metadata, repository, result.getException() );
355            }
356        }
357
358        if ( !tasks.isEmpty() )
359        {
360            int threads = ConfigUtils.getInteger( session, 4, CONFIG_PROP_THREADS );
361            Executor executor = getExecutor( Math.min( tasks.size(), threads ) );
362            try
363            {
364                RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
365
366                for ( ResolveTask task : tasks )
367                {
368                    executor.execute( errorForwarder.wrap( task ) );
369                }
370
371                errorForwarder.await();
372
373                for ( ResolveTask task : tasks )
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 =
387                    new LocalMetadataRequest( 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        File localFile = localResult.getFile();
411        return localFile;
412    }
413
414    private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature )
415    {
416        List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
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 new Executor()
510            {
511                public void execute( Runnable command )
512                {
513                    command.run();
514                }
515            };
516        }
517        else
518        {
519            return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
520                                           new WorkerThreadFactory( null ) );
521        }
522    }
523
524    private void shutdown( Executor executor )
525    {
526        if ( executor instanceof ExecutorService )
527        {
528            ( (ExecutorService) executor ).shutdown();
529        }
530    }
531
532    class ResolveTask
533        implements Runnable
534    {
535
536        final RepositorySystemSession session;
537
538        final RequestTrace trace;
539
540        final MetadataResult result;
541
542        final MetadataRequest request;
543
544        final File metadataFile;
545
546        final String policy;
547
548        final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
549
550        volatile MetadataTransferException exception;
551
552        public ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
553                            File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
554                            String policy )
555        {
556            this.session = session;
557            this.trace = trace;
558            this.result = result;
559            this.request = result.getRequest();
560            this.metadataFile = metadataFile;
561            this.policy = policy;
562            this.checks = checks;
563        }
564
565        public void run()
566        {
567            Metadata metadata = request.getMetadata();
568            RemoteRepository requestRepository = request.getRepository();
569
570            metadataDownloading( session, trace, metadata, requestRepository );
571
572            try
573            {
574                List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
575                for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
576                {
577                    repositories.add( check.getAuthoritativeRepository() );
578                }
579
580                MetadataDownload download = new MetadataDownload();
581                download.setMetadata( metadata );
582                download.setRequestContext( request.getRequestContext() );
583                download.setFile( metadataFile );
584                download.setChecksumPolicy( policy );
585                download.setRepositories( repositories );
586                download.setListener( SafeTransferListener.wrap( session, logger ) );
587                download.setTrace( trace );
588
589                RepositoryConnector connector =
590                    repositoryConnectorProvider.newRepositoryConnector( session, requestRepository );
591                try
592                {
593                    connector.get( null, Arrays.asList( download ) );
594                }
595                finally
596                {
597                    connector.close();
598                }
599
600                exception = download.getException();
601
602                if ( exception == null )
603                {
604
605                    List<String> contexts = Collections.singletonList( request.getRequestContext() );
606                    LocalMetadataRegistration registration =
607                        new LocalMetadataRegistration( metadata, requestRepository, contexts );
608
609                    session.getLocalRepositoryManager().add( session, registration );
610                }
611                else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
612                {
613                    download.getFile().delete();
614                }
615            }
616            catch ( NoRepositoryConnectorException e )
617            {
618                exception = new MetadataTransferException( metadata, requestRepository, e );
619            }
620
621            /*
622             * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
623             * "already updated" via session data when actual update to local repo is still pending.
624             */
625            for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
626            {
627                updateCheckManager.touchMetadata( session, check.setException( exception ) );
628            }
629
630            metadataDownloaded( session, trace, metadata, requestRepository, metadataFile, exception );
631        }
632
633    }
634
635}