blob: 244fc5a9d9b51f72d28ba838d00ac405c55498a4 [file] [log] [blame]
package org.eclipse.aether.internal.impl;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositoryEvent.EventType;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.RequestTrace;
import org.eclipse.aether.SyncContext;
import org.eclipse.aether.impl.MetadataResolver;
import org.eclipse.aether.impl.OfflineController;
import org.eclipse.aether.impl.RemoteRepositoryManager;
import org.eclipse.aether.impl.RepositoryConnectorProvider;
import org.eclipse.aether.impl.RepositoryEventDispatcher;
import org.eclipse.aether.impl.SyncContextFactory;
import org.eclipse.aether.impl.UpdateCheck;
import org.eclipse.aether.impl.UpdateCheckManager;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.repository.LocalMetadataRegistration;
import org.eclipse.aether.repository.LocalMetadataRequest;
import org.eclipse.aether.repository.LocalMetadataResult;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.MetadataRequest;
import org.eclipse.aether.resolution.MetadataResult;
import org.eclipse.aether.spi.connector.MetadataDownload;
import org.eclipse.aether.spi.connector.RepositoryConnector;
import org.eclipse.aether.spi.locator.Service;
import org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.transfer.MetadataNotFoundException;
import org.eclipse.aether.transfer.MetadataTransferException;
import org.eclipse.aether.transfer.NoRepositoryConnectorException;
import org.eclipse.aether.transfer.RepositoryOfflineException;
import org.eclipse.aether.util.ConfigUtils;
import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
import org.eclipse.aether.util.concurrency.WorkerThreadFactory;
/**
*/
@Named
public class DefaultMetadataResolver
implements MetadataResolver, Service
{
private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
private RepositoryEventDispatcher repositoryEventDispatcher;
private UpdateCheckManager updateCheckManager;
private RepositoryConnectorProvider repositoryConnectorProvider;
private RemoteRepositoryManager remoteRepositoryManager;
private SyncContextFactory syncContextFactory;
private OfflineController offlineController;
public DefaultMetadataResolver()
{
// enables default constructor
}
@Inject
DefaultMetadataResolver( RepositoryEventDispatcher repositoryEventDispatcher,
UpdateCheckManager updateCheckManager,
RepositoryConnectorProvider repositoryConnectorProvider,
RemoteRepositoryManager remoteRepositoryManager, SyncContextFactory syncContextFactory,
OfflineController offlineController )
{
setRepositoryEventDispatcher( repositoryEventDispatcher );
setUpdateCheckManager( updateCheckManager );
setRepositoryConnectorProvider( repositoryConnectorProvider );
setRemoteRepositoryManager( remoteRepositoryManager );
setSyncContextFactory( syncContextFactory );
setOfflineController( offlineController );
}
public void initService( ServiceLocator locator )
{
setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
setUpdateCheckManager( locator.getService( UpdateCheckManager.class ) );
setRepositoryConnectorProvider( locator.getService( RepositoryConnectorProvider.class ) );
setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) );
setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
setOfflineController( locator.getService( OfflineController.class ) );
}
public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
{
this.repositoryEventDispatcher = requireNonNull( repositoryEventDispatcher, "repository event dispatcher cannot be null" );
return this;
}
public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager )
{
this.updateCheckManager = requireNonNull( updateCheckManager, "update check manager cannot be null" );
return this;
}
public DefaultMetadataResolver setRepositoryConnectorProvider( RepositoryConnectorProvider repositoryConnectorProvider )
{
this.repositoryConnectorProvider = requireNonNull( repositoryConnectorProvider, "repository connector provider cannot be null" );
return this;
}
public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
{
this.remoteRepositoryManager = requireNonNull( remoteRepositoryManager, "remote repository provider cannot be null" );
return this;
}
public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
{
this.syncContextFactory = requireNonNull( syncContextFactory, "sync context factory cannot be null" );
return this;
}
public DefaultMetadataResolver setOfflineController( OfflineController offlineController )
{
this.offlineController = requireNonNull( offlineController, "offline controller cannot be null" );
return this;
}
public List<MetadataResult> resolveMetadata( RepositorySystemSession session,
Collection<? extends MetadataRequest> requests )
{
SyncContext syncContext = syncContextFactory.newInstance( session, false );
try
{
Collection<Metadata> metadata = new ArrayList<Metadata>( requests.size() );
for ( MetadataRequest request : requests )
{
metadata.add( request.getMetadata() );
}
syncContext.acquire( null, metadata );
return resolve( session, requests );
}
finally
{
syncContext.close();
}
}
private List<MetadataResult> resolve( RepositorySystemSession session,
Collection<? extends MetadataRequest> requests )
{
List<MetadataResult> results = new ArrayList<MetadataResult>( requests.size() );
List<ResolveTask> tasks = new ArrayList<ResolveTask>( requests.size() );
Map<File, Long> localLastUpdates = new HashMap<File, Long>();
for ( MetadataRequest request : requests )
{
RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
MetadataResult result = new MetadataResult( request );
results.add( result );
Metadata metadata = request.getMetadata();
RemoteRepository repository = request.getRepository();
if ( repository == null )
{
LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
metadataResolving( session, trace, metadata, localRepo );
File localFile = getLocalFile( session, metadata );
if ( localFile != null )
{
metadata = metadata.setFile( localFile );
result.setMetadata( metadata );
}
else
{
result.setException( new MetadataNotFoundException( metadata, localRepo ) );
}
metadataResolved( session, trace, metadata, localRepo, result.getException() );
continue;
}
List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() );
if ( repositories.isEmpty() )
{
continue;
}
metadataResolving( session, trace, metadata, repository );
LocalRepositoryManager lrm = session.getLocalRepositoryManager();
LocalMetadataRequest localRequest =
new LocalMetadataRequest( metadata, repository, request.getRequestContext() );
LocalMetadataResult lrmResult = lrm.find( session, localRequest );
File metadataFile = lrmResult.getFile();
try
{
Utils.checkOffline( session, offlineController, repository );
}
catch ( RepositoryOfflineException e )
{
if ( metadataFile != null )
{
metadata = metadata.setFile( metadataFile );
result.setMetadata( metadata );
}
else
{
String msg =
"Cannot access " + repository.getId() + " (" + repository.getUrl()
+ ") in offline mode and the metadata " + metadata
+ " has not been downloaded from it before";
result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) );
}
metadataResolved( session, trace, metadata, repository, result.getException() );
continue;
}
Long localLastUpdate = null;
if ( request.isFavorLocalRepository() )
{
File localFile = getLocalFile( session, metadata );
localLastUpdate = localLastUpdates.get( localFile );
if ( localLastUpdate == null )
{
localLastUpdate = localFile != null ? localFile.lastModified() : 0;
localLastUpdates.put( localFile, localLastUpdate );
}
}
List<UpdateCheck<Metadata, MetadataTransferException>> checks =
new ArrayList<UpdateCheck<Metadata, MetadataTransferException>>();
Exception exception = null;
for ( RemoteRepository repo : repositories )
{
UpdateCheck<Metadata, MetadataTransferException> check =
new UpdateCheck<Metadata, MetadataTransferException>();
check.setLocalLastUpdated( ( localLastUpdate != null ) ? localLastUpdate : 0 );
check.setItem( metadata );
// use 'main' installation file for the check (-> use requested repository)
File checkFile =
new File(
session.getLocalRepository().getBasedir(),
session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata, repository,
request.getRequestContext() ) );
check.setFile( checkFile );
check.setRepository( repository );
check.setAuthoritativeRepository( repo );
check.setPolicy( getPolicy( session, repo, metadata.getNature() ).getUpdatePolicy() );
if ( lrmResult.isStale() )
{
checks.add( check );
}
else
{
updateCheckManager.checkMetadata( session, check );
if ( check.isRequired() )
{
checks.add( check );
}
else if ( exception == null )
{
exception = check.getException();
}
}
}
if ( !checks.isEmpty() )
{
RepositoryPolicy policy = getPolicy( session, repository, metadata.getNature() );
// install path may be different from lookup path
File installFile =
new File(
session.getLocalRepository().getBasedir(),
session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata,
request.getRepository(),
request.getRequestContext() ) );
ResolveTask task =
new ResolveTask( session, trace, result, installFile, checks, policy.getChecksumPolicy() );
tasks.add( task );
}
else
{
result.setException( exception );
if ( metadataFile != null )
{
metadata = metadata.setFile( metadataFile );
result.setMetadata( metadata );
}
metadataResolved( session, trace, metadata, repository, result.getException() );
}
}
if ( !tasks.isEmpty() )
{
int threads = ConfigUtils.getInteger( session, 4, CONFIG_PROP_THREADS );
Executor executor = getExecutor( Math.min( tasks.size(), threads ) );
try
{
RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
for ( ResolveTask task : tasks )
{
executor.execute( errorForwarder.wrap( task ) );
}
errorForwarder.await();
for ( ResolveTask task : tasks )
{
task.result.setException( task.exception );
}
}
finally
{
shutdown( executor );
}
for ( ResolveTask task : tasks )
{
Metadata metadata = task.request.getMetadata();
// re-lookup metadata for resolve
LocalMetadataRequest localRequest =
new LocalMetadataRequest( metadata, task.request.getRepository(), task.request.getRequestContext() );
File metadataFile = session.getLocalRepositoryManager().find( session, localRequest ).getFile();
if ( metadataFile != null )
{
metadata = metadata.setFile( metadataFile );
task.result.setMetadata( metadata );
}
if ( task.result.getException() == null )
{
task.result.setUpdated( true );
}
metadataResolved( session, task.trace, metadata, task.request.getRepository(),
task.result.getException() );
}
}
return results;
}
private File getLocalFile( RepositorySystemSession session, Metadata metadata )
{
LocalRepositoryManager lrm = session.getLocalRepositoryManager();
LocalMetadataResult localResult = lrm.find( session, new LocalMetadataRequest( metadata, null, null ) );
File localFile = localResult.getFile();
return localFile;
}
private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature )
{
List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
if ( repository.isRepositoryManager() )
{
for ( RemoteRepository repo : repository.getMirroredRepositories() )
{
if ( isEnabled( repo, nature ) )
{
repositories.add( repo );
}
}
}
else if ( isEnabled( repository, nature ) )
{
repositories.add( repository );
}
return repositories;
}
private boolean isEnabled( RemoteRepository repository, Metadata.Nature nature )
{
if ( !Metadata.Nature.SNAPSHOT.equals( nature ) && repository.getPolicy( false ).isEnabled() )
{
return true;
}
if ( !Metadata.Nature.RELEASE.equals( nature ) && repository.getPolicy( true ).isEnabled() )
{
return true;
}
return false;
}
private RepositoryPolicy getPolicy( RepositorySystemSession session, RemoteRepository repository,
Metadata.Nature nature )
{
boolean releases = !Metadata.Nature.SNAPSHOT.equals( nature );
boolean snapshots = !Metadata.Nature.RELEASE.equals( nature );
return remoteRepositoryManager.getPolicy( session, repository, releases, snapshots );
}
private void metadataResolving( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
ArtifactRepository repository )
{
RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVING );
event.setTrace( trace );
event.setMetadata( metadata );
event.setRepository( repository );
repositoryEventDispatcher.dispatch( event.build() );
}
private void metadataResolved( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
ArtifactRepository repository, Exception exception )
{
RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVED );
event.setTrace( trace );
event.setMetadata( metadata );
event.setRepository( repository );
event.setException( exception );
event.setFile( metadata.getFile() );
repositoryEventDispatcher.dispatch( event.build() );
}
private void metadataDownloading( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
ArtifactRepository repository )
{
RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADING );
event.setTrace( trace );
event.setMetadata( metadata );
event.setRepository( repository );
repositoryEventDispatcher.dispatch( event.build() );
}
private void metadataDownloaded( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
ArtifactRepository repository, File file, Exception exception )
{
RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADED );
event.setTrace( trace );
event.setMetadata( metadata );
event.setRepository( repository );
event.setException( exception );
event.setFile( file );
repositoryEventDispatcher.dispatch( event.build() );
}
private Executor getExecutor( int threads )
{
if ( threads <= 1 )
{
return new Executor()
{
public void execute( Runnable command )
{
command.run();
}
};
}
else
{
return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
new WorkerThreadFactory( null ) );
}
}
private void shutdown( Executor executor )
{
if ( executor instanceof ExecutorService )
{
( (ExecutorService) executor ).shutdown();
}
}
class ResolveTask
implements Runnable
{
final RepositorySystemSession session;
final RequestTrace trace;
final MetadataResult result;
final MetadataRequest request;
final File metadataFile;
final String policy;
final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
volatile MetadataTransferException exception;
ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
String policy )
{
this.session = session;
this.trace = trace;
this.result = result;
this.request = result.getRequest();
this.metadataFile = metadataFile;
this.policy = policy;
this.checks = checks;
}
public void run()
{
Metadata metadata = request.getMetadata();
RemoteRepository requestRepository = request.getRepository();
metadataDownloading( session, trace, metadata, requestRepository );
try
{
List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
{
repositories.add( check.getAuthoritativeRepository() );
}
MetadataDownload download = new MetadataDownload();
download.setMetadata( metadata );
download.setRequestContext( request.getRequestContext() );
download.setFile( metadataFile );
download.setChecksumPolicy( policy );
download.setRepositories( repositories );
download.setListener( SafeTransferListener.wrap( session ) );
download.setTrace( trace );
RepositoryConnector connector =
repositoryConnectorProvider.newRepositoryConnector( session, requestRepository );
try
{
connector.get( null, Arrays.asList( download ) );
}
finally
{
connector.close();
}
exception = download.getException();
if ( exception == null )
{
List<String> contexts = Collections.singletonList( request.getRequestContext() );
LocalMetadataRegistration registration =
new LocalMetadataRegistration( metadata, requestRepository, contexts );
session.getLocalRepositoryManager().add( session, registration );
}
else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
{
download.getFile().delete();
}
}
catch ( NoRepositoryConnectorException e )
{
exception = new MetadataTransferException( metadata, requestRepository, e );
}
/*
* NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
* "already updated" via session data when actual update to local repo is still pending.
*/
for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
{
updateCheckManager.touchMetadata( session, check.setException( exception ) );
}
metadataDownloaded( session, trace, metadata, requestRepository, metadataFile, exception );
}
}
}