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