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.transfer.MetadataNotFoundException;
069import org.eclipse.aether.transfer.MetadataTransferException;
070import org.eclipse.aether.transfer.NoRepositoryConnectorException;
071import org.eclipse.aether.transfer.RepositoryOfflineException;
072import org.eclipse.aether.util.ConfigUtils;
073import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
074import org.eclipse.aether.util.concurrency.WorkerThreadFactory;
075
076/**
077 */
078@Named
079public class DefaultMetadataResolver
080    implements MetadataResolver, Service
081{
082
083    private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
084
085    private RepositoryEventDispatcher repositoryEventDispatcher;
086
087    private UpdateCheckManager updateCheckManager;
088
089    private RepositoryConnectorProvider repositoryConnectorProvider;
090
091    private RemoteRepositoryManager remoteRepositoryManager;
092
093    private SyncContextFactory syncContextFactory;
094
095    private OfflineController offlineController;
096
097    public DefaultMetadataResolver()
098    {
099        // enables default constructor
100    }
101
102    @Inject
103    DefaultMetadataResolver( RepositoryEventDispatcher repositoryEventDispatcher,
104                             UpdateCheckManager updateCheckManager,
105                             RepositoryConnectorProvider repositoryConnectorProvider,
106                             RemoteRepositoryManager remoteRepositoryManager, SyncContextFactory syncContextFactory,
107                             OfflineController offlineController )
108    {
109        setRepositoryEventDispatcher( repositoryEventDispatcher );
110        setUpdateCheckManager( updateCheckManager );
111        setRepositoryConnectorProvider( repositoryConnectorProvider );
112        setRemoteRepositoryManager( remoteRepositoryManager );
113        setSyncContextFactory( syncContextFactory );
114        setOfflineController( offlineController );
115    }
116
117    public void initService( ServiceLocator locator )
118    {
119        setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
120        setUpdateCheckManager( locator.getService( UpdateCheckManager.class ) );
121        setRepositoryConnectorProvider( locator.getService( RepositoryConnectorProvider.class ) );
122        setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) );
123        setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
124        setOfflineController( locator.getService( OfflineController.class ) );
125    }
126
127    public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
128    {
129        this.repositoryEventDispatcher = requireNonNull( repositoryEventDispatcher, "repository event dispatcher cannot be null" );
130        return this;
131    }
132
133    public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager )
134    {
135        this.updateCheckManager = requireNonNull( updateCheckManager, "update check manager cannot be null" );
136        return this;
137    }
138
139    public DefaultMetadataResolver setRepositoryConnectorProvider( RepositoryConnectorProvider repositoryConnectorProvider )
140    {
141        this.repositoryConnectorProvider = requireNonNull( repositoryConnectorProvider, "repository connector provider cannot be null" );
142        return this;
143    }
144
145    public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
146    {
147        this.remoteRepositoryManager = requireNonNull( remoteRepositoryManager, "remote repository provider cannot be null" );
148        return this;
149    }
150
151    public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
152    {
153        this.syncContextFactory = requireNonNull( syncContextFactory, "sync context factory cannot be null" );
154        return this;
155    }
156
157    public DefaultMetadataResolver setOfflineController( OfflineController offlineController )
158    {
159        this.offlineController = requireNonNull( offlineController, "offline controller cannot be null" );
160        return this;
161    }
162
163    public List<MetadataResult> resolveMetadata( RepositorySystemSession session,
164                                                 Collection<? extends MetadataRequest> requests )
165    {
166        SyncContext syncContext = syncContextFactory.newInstance( session, false );
167
168        try
169        {
170            Collection<Metadata> metadata = new ArrayList<Metadata>( requests.size() );
171            for ( MetadataRequest request : requests )
172            {
173                metadata.add( request.getMetadata() );
174            }
175
176            syncContext.acquire( null, metadata );
177
178            return resolve( session, requests );
179        }
180        finally
181        {
182            syncContext.close();
183        }
184    }
185
186    private List<MetadataResult> resolve( RepositorySystemSession session,
187                                          Collection<? extends MetadataRequest> requests )
188    {
189        List<MetadataResult> results = new ArrayList<MetadataResult>( requests.size() );
190
191        List<ResolveTask> tasks = new ArrayList<ResolveTask>( requests.size() );
192
193        Map<File, Long> localLastUpdates = new HashMap<File, Long>();
194
195        for ( MetadataRequest request : requests )
196        {
197            RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
198
199            MetadataResult result = new MetadataResult( request );
200            results.add( result );
201
202            Metadata metadata = request.getMetadata();
203            RemoteRepository repository = request.getRepository();
204
205            if ( repository == null )
206            {
207                LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
208
209                metadataResolving( session, trace, metadata, localRepo );
210
211                File localFile = getLocalFile( session, metadata );
212
213                if ( localFile != null )
214                {
215                    metadata = metadata.setFile( localFile );
216                    result.setMetadata( metadata );
217                }
218                else
219                {
220                    result.setException( new MetadataNotFoundException( metadata, localRepo ) );
221                }
222
223                metadataResolved( session, trace, metadata, localRepo, result.getException() );
224                continue;
225            }
226
227            List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() );
228
229            if ( repositories.isEmpty() )
230            {
231                continue;
232            }
233
234            metadataResolving( session, trace, metadata, repository );
235            LocalRepositoryManager lrm = session.getLocalRepositoryManager();
236            LocalMetadataRequest localRequest =
237                new LocalMetadataRequest( metadata, repository, request.getRequestContext() );
238            LocalMetadataResult lrmResult = lrm.find( session, localRequest );
239
240            File metadataFile = lrmResult.getFile();
241
242            try
243            {
244                Utils.checkOffline( session, offlineController, repository );
245            }
246            catch ( RepositoryOfflineException e )
247            {
248                if ( metadataFile != null )
249                {
250                    metadata = metadata.setFile( metadataFile );
251                    result.setMetadata( metadata );
252                }
253                else
254                {
255                    String msg =
256                        "Cannot access " + repository.getId() + " (" + repository.getUrl()
257                            + ") in offline mode and the metadata " + metadata
258                            + " has not been downloaded from it before";
259                    result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) );
260                }
261
262                metadataResolved( session, trace, metadata, repository, result.getException() );
263                continue;
264            }
265
266            Long localLastUpdate = null;
267            if ( request.isFavorLocalRepository() )
268            {
269                File localFile = getLocalFile( session, metadata );
270                localLastUpdate = localLastUpdates.get( localFile );
271                if ( localLastUpdate == null )
272                {
273                    localLastUpdate = localFile != null ? localFile.lastModified() : 0;
274                    localLastUpdates.put( localFile, localLastUpdate );
275                }
276            }
277
278            List<UpdateCheck<Metadata, MetadataTransferException>> checks =
279                new ArrayList<UpdateCheck<Metadata, MetadataTransferException>>();
280            Exception exception = null;
281            for ( RemoteRepository repo : repositories )
282            {
283                UpdateCheck<Metadata, MetadataTransferException> check =
284                    new UpdateCheck<Metadata, MetadataTransferException>();
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 =
290                    new File(
291                              session.getLocalRepository().getBasedir(),
292                              session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata, repository,
293                                                                                            request.getRequestContext() ) );
294                check.setFile( checkFile );
295                check.setRepository( repository );
296                check.setAuthoritativeRepository( repo );
297                check.setPolicy( getPolicy( session, repo, metadata.getNature() ).getUpdatePolicy() );
298
299                if ( lrmResult.isStale() )
300                {
301                    checks.add( check );
302                }
303                else
304                {
305                    updateCheckManager.checkMetadata( session, check );
306                    if ( check.isRequired() )
307                    {
308                        checks.add( check );
309                    }
310                    else if ( exception == null )
311                    {
312                        exception = check.getException();
313                    }
314                }
315            }
316
317            if ( !checks.isEmpty() )
318            {
319                RepositoryPolicy policy = getPolicy( session, repository, metadata.getNature() );
320
321                // install path may be different from lookup path
322                File installFile =
323                    new File(
324                              session.getLocalRepository().getBasedir(),
325                              session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata,
326                                                                                            request.getRepository(),
327                                                                                            request.getRequestContext() ) );
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                    task.result.setException( task.exception );
363                }
364            }
365            finally
366            {
367                shutdown( executor );
368            }
369            for ( ResolveTask task : tasks )
370            {
371                Metadata metadata = task.request.getMetadata();
372                // re-lookup metadata for resolve
373                LocalMetadataRequest localRequest =
374                    new LocalMetadataRequest( metadata, task.request.getRepository(), task.request.getRequestContext() );
375                File metadataFile = session.getLocalRepositoryManager().find( session, localRequest ).getFile();
376                if ( metadataFile != null )
377                {
378                    metadata = metadata.setFile( metadataFile );
379                    task.result.setMetadata( metadata );
380                }
381                if ( task.result.getException() == null )
382                {
383                    task.result.setUpdated( true );
384                }
385                metadataResolved( session, task.trace, metadata, task.request.getRepository(),
386                                  task.result.getException() );
387            }
388        }
389
390        return results;
391    }
392
393    private File getLocalFile( RepositorySystemSession session, Metadata metadata )
394    {
395        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
396        LocalMetadataResult localResult = lrm.find( session, new LocalMetadataRequest( metadata, null, null ) );
397        File localFile = localResult.getFile();
398        return localFile;
399    }
400
401    private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature )
402    {
403        List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
404
405        if ( repository.isRepositoryManager() )
406        {
407            for ( RemoteRepository repo : repository.getMirroredRepositories() )
408            {
409                if ( isEnabled( repo, nature ) )
410                {
411                    repositories.add( repo );
412                }
413            }
414        }
415        else if ( isEnabled( repository, nature ) )
416        {
417            repositories.add( repository );
418        }
419
420        return repositories;
421    }
422
423    private boolean isEnabled( RemoteRepository repository, Metadata.Nature nature )
424    {
425        if ( !Metadata.Nature.SNAPSHOT.equals( nature ) && repository.getPolicy( false ).isEnabled() )
426        {
427            return true;
428        }
429        if ( !Metadata.Nature.RELEASE.equals( nature ) && repository.getPolicy( true ).isEnabled() )
430        {
431            return true;
432        }
433        return false;
434    }
435
436    private RepositoryPolicy getPolicy( RepositorySystemSession session, RemoteRepository repository,
437                                        Metadata.Nature nature )
438    {
439        boolean releases = !Metadata.Nature.SNAPSHOT.equals( nature );
440        boolean snapshots = !Metadata.Nature.RELEASE.equals( nature );
441        return remoteRepositoryManager.getPolicy( session, repository, releases, snapshots );
442    }
443
444    private void metadataResolving( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
445                                    ArtifactRepository repository )
446    {
447        RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVING );
448        event.setTrace( trace );
449        event.setMetadata( metadata );
450        event.setRepository( repository );
451
452        repositoryEventDispatcher.dispatch( event.build() );
453    }
454
455    private void metadataResolved( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
456                                   ArtifactRepository repository, Exception exception )
457    {
458        RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVED );
459        event.setTrace( trace );
460        event.setMetadata( metadata );
461        event.setRepository( repository );
462        event.setException( exception );
463        event.setFile( metadata.getFile() );
464
465        repositoryEventDispatcher.dispatch( event.build() );
466    }
467
468    private void metadataDownloading( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
469                                      ArtifactRepository repository )
470    {
471        RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADING );
472        event.setTrace( trace );
473        event.setMetadata( metadata );
474        event.setRepository( repository );
475
476        repositoryEventDispatcher.dispatch( event.build() );
477    }
478
479    private void metadataDownloaded( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
480                                     ArtifactRepository repository, File file, Exception exception )
481    {
482        RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADED );
483        event.setTrace( trace );
484        event.setMetadata( metadata );
485        event.setRepository( repository );
486        event.setException( exception );
487        event.setFile( file );
488
489        repositoryEventDispatcher.dispatch( event.build() );
490    }
491
492    private Executor getExecutor( int threads )
493    {
494        if ( threads <= 1 )
495        {
496            return new Executor()
497            {
498                public void execute( Runnable command )
499                {
500                    command.run();
501                }
502            };
503        }
504        else
505        {
506            return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
507                                           new WorkerThreadFactory( null ) );
508        }
509    }
510
511    private void shutdown( Executor executor )
512    {
513        if ( executor instanceof ExecutorService )
514        {
515            ( (ExecutorService) executor ).shutdown();
516        }
517    }
518
519    class ResolveTask
520        implements Runnable
521    {
522
523        final RepositorySystemSession session;
524
525        final RequestTrace trace;
526
527        final MetadataResult result;
528
529        final MetadataRequest request;
530
531        final File metadataFile;
532
533        final String policy;
534
535        final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
536
537        volatile MetadataTransferException exception;
538
539        ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
540                            File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
541                            String policy )
542        {
543            this.session = session;
544            this.trace = trace;
545            this.result = result;
546            this.request = result.getRequest();
547            this.metadataFile = metadataFile;
548            this.policy = policy;
549            this.checks = checks;
550        }
551
552        public void run()
553        {
554            Metadata metadata = request.getMetadata();
555            RemoteRepository requestRepository = request.getRepository();
556
557            metadataDownloading( session, trace, metadata, requestRepository );
558
559            try
560            {
561                List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
562                for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
563                {
564                    repositories.add( check.getAuthoritativeRepository() );
565                }
566
567                MetadataDownload download = new MetadataDownload();
568                download.setMetadata( metadata );
569                download.setRequestContext( request.getRequestContext() );
570                download.setFile( metadataFile );
571                download.setChecksumPolicy( policy );
572                download.setRepositories( repositories );
573                download.setListener( SafeTransferListener.wrap( session ) );
574                download.setTrace( trace );
575
576                RepositoryConnector connector =
577                    repositoryConnectorProvider.newRepositoryConnector( session, requestRepository );
578                try
579                {
580                    connector.get( null, Arrays.asList( download ) );
581                }
582                finally
583                {
584                    connector.close();
585                }
586
587                exception = download.getException();
588
589                if ( exception == null )
590                {
591
592                    List<String> contexts = Collections.singletonList( request.getRequestContext() );
593                    LocalMetadataRegistration registration =
594                        new LocalMetadataRegistration( metadata, requestRepository, contexts );
595
596                    session.getLocalRepositoryManager().add( session, registration );
597                }
598                else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
599                {
600                    download.getFile().delete();
601                }
602            }
603            catch ( NoRepositoryConnectorException e )
604            {
605                exception = new MetadataTransferException( metadata, requestRepository, e );
606            }
607
608            /*
609             * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
610             * "already updated" via session data when actual update to local repo is still pending.
611             */
612            for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
613            {
614                updateCheckManager.touchMetadata( session, check.setException( exception ) );
615            }
616
617            metadataDownloaded( session, trace, metadata, requestRepository, metadataFile, exception );
618        }
619
620    }
621
622}