blob: 2b0890b15ac15a19c6461be577289bf50e7d565c [file] [log] [blame]
package org.apache.maven.repository.internal;
/*
* 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 org.apache.commons.lang3.Validate;
import org.apache.maven.artifact.repository.metadata.Snapshot;
import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.RepositoryCache;
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.artifact.Artifact;
import org.eclipse.aether.impl.MetadataResolver;
import org.eclipse.aether.impl.RepositoryEventDispatcher;
import org.eclipse.aether.impl.SyncContextFactory;
import org.eclipse.aether.impl.VersionResolver;
import org.eclipse.aether.internal.impl.CacheUtils;
import org.eclipse.aether.metadata.DefaultMetadata;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.repository.WorkspaceRepository;
import org.eclipse.aether.resolution.MetadataRequest;
import org.eclipse.aether.resolution.MetadataResult;
import org.eclipse.aether.resolution.VersionRequest;
import org.eclipse.aether.resolution.VersionResolutionException;
import org.eclipse.aether.resolution.VersionResult;
import org.eclipse.aether.spi.locator.Service;
import org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.spi.log.Logger;
import org.eclipse.aether.spi.log.LoggerFactory;
import org.eclipse.aether.spi.log.NullLoggerFactory;
import org.eclipse.aether.util.ConfigUtils;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Benjamin Bentmann
*/
@Named
@Component( role = VersionResolver.class )
public class DefaultVersionResolver
implements VersionResolver, Service
{
private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
private static final String RELEASE = "RELEASE";
private static final String LATEST = "LATEST";
private static final String SNAPSHOT = "SNAPSHOT";
@SuppressWarnings( "unused" )
@Requirement( role = LoggerFactory.class )
private Logger logger = NullLoggerFactory.LOGGER;
@Requirement
private MetadataResolver metadataResolver;
@Requirement
private SyncContextFactory syncContextFactory;
@Requirement
private RepositoryEventDispatcher repositoryEventDispatcher;
public DefaultVersionResolver()
{
// enable no-arg constructor
}
@Inject
DefaultVersionResolver( MetadataResolver metadataResolver, SyncContextFactory syncContextFactory,
RepositoryEventDispatcher repositoryEventDispatcher, LoggerFactory loggerFactory )
{
setMetadataResolver( metadataResolver );
setSyncContextFactory( syncContextFactory );
setLoggerFactory( loggerFactory );
setRepositoryEventDispatcher( repositoryEventDispatcher );
}
public void initService( ServiceLocator locator )
{
setLoggerFactory( locator.getService( LoggerFactory.class ) );
setMetadataResolver( locator.getService( MetadataResolver.class ) );
setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
}
public DefaultVersionResolver setLoggerFactory( LoggerFactory loggerFactory )
{
this.logger = NullLoggerFactory.getSafeLogger( loggerFactory, getClass() );
return this;
}
void setLogger( LoggerFactory loggerFactory )
{
// plexus support
setLoggerFactory( loggerFactory );
}
public DefaultVersionResolver setMetadataResolver( MetadataResolver metadataResolver )
{
this.metadataResolver = Validate.notNull( metadataResolver, "metadataResolver cannot be null" );
return this;
}
public DefaultVersionResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
{
this.syncContextFactory = Validate.notNull( syncContextFactory, "syncContextFactory cannot be null" );
return this;
}
public DefaultVersionResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
{
this.repositoryEventDispatcher = Validate.notNull( repositoryEventDispatcher,
"repositoryEventDispatcher cannot be null" );
return this;
}
public VersionResult resolveVersion( RepositorySystemSession session, VersionRequest request )
throws VersionResolutionException
{
RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
Artifact artifact = request.getArtifact();
String version = artifact.getVersion();
VersionResult result = new VersionResult( request );
Key cacheKey = null;
RepositoryCache cache = session.getCache();
if ( cache != null && !ConfigUtils.getBoolean( session, false, "aether.versionResolver.noCache" ) )
{
cacheKey = new Key( session, request );
Object obj = cache.get( session, cacheKey );
if ( obj instanceof Record )
{
Record record = (Record) obj;
result.setVersion( record.version );
result.setRepository(
CacheUtils.getRepository( session, request.getRepositories(), record.repoClass, record.repoId ) );
return result;
}
}
Metadata metadata;
if ( RELEASE.equals( version ) )
{
metadata = new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
Metadata.Nature.RELEASE );
}
else if ( LATEST.equals( version ) )
{
metadata = new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
Metadata.Nature.RELEASE_OR_SNAPSHOT );
}
else if ( version.endsWith( SNAPSHOT ) )
{
WorkspaceReader workspace = session.getWorkspaceReader();
if ( workspace != null && workspace.findVersions( artifact ).contains( version ) )
{
metadata = null;
result.setRepository( workspace.getRepository() );
}
else
{
metadata =
new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), version, MAVEN_METADATA_XML,
Metadata.Nature.SNAPSHOT );
}
}
else
{
metadata = null;
}
if ( metadata == null )
{
result.setVersion( version );
}
else
{
List<MetadataRequest> metadataReqs = new ArrayList<>( request.getRepositories().size() );
metadataReqs.add( new MetadataRequest( metadata, null, request.getRequestContext() ) );
for ( RemoteRepository repository : request.getRepositories() )
{
MetadataRequest metadataRequest =
new MetadataRequest( metadata, repository, request.getRequestContext() );
metadataRequest.setDeleteLocalCopyIfMissing( true );
metadataRequest.setFavorLocalRepository( true );
metadataRequest.setTrace( trace );
metadataReqs.add( metadataRequest );
}
List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataReqs );
Map<String, VersionInfo> infos = new HashMap<>();
for ( MetadataResult metadataResult : metadataResults )
{
result.addException( metadataResult.getException() );
ArtifactRepository repository = metadataResult.getRequest().getRepository();
if ( repository == null )
{
repository = session.getLocalRepository();
}
Versioning v = readVersions( session, trace, metadataResult.getMetadata(), repository, result );
merge( artifact, infos, v, repository );
}
if ( RELEASE.equals( version ) )
{
resolve( result, infos, RELEASE );
}
else if ( LATEST.equals( version ) )
{
if ( !resolve( result, infos, LATEST ) )
{
resolve( result, infos, RELEASE );
}
if ( result.getVersion() != null && result.getVersion().endsWith( SNAPSHOT ) )
{
VersionRequest subRequest = new VersionRequest();
subRequest.setArtifact( artifact.setVersion( result.getVersion() ) );
if ( result.getRepository() instanceof RemoteRepository )
{
RemoteRepository r = (RemoteRepository) result.getRepository();
subRequest.setRepositories( Collections.singletonList( r ) );
}
else
{
subRequest.setRepositories( request.getRepositories() );
}
VersionResult subResult = resolveVersion( session, subRequest );
result.setVersion( subResult.getVersion() );
result.setRepository( subResult.getRepository() );
for ( Exception exception : subResult.getExceptions() )
{
result.addException( exception );
}
}
}
else
{
String key = SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() );
merge( infos, SNAPSHOT, key );
if ( !resolve( result, infos, key ) )
{
result.setVersion( version );
}
}
if ( StringUtils.isEmpty( result.getVersion() ) )
{
throw new VersionResolutionException( result );
}
}
if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) )
{
cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) );
}
return result;
}
private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key )
{
VersionInfo info = infos.get( key );
if ( info != null )
{
result.setVersion( info.version );
result.setRepository( info.repository );
}
return info != null;
}
private Versioning readVersions( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
ArtifactRepository repository, VersionResult result )
{
Versioning versioning = null;
FileInputStream fis = null;
try
{
if ( metadata != null )
{
try ( SyncContext syncContext = syncContextFactory.newInstance( session, true ) )
{
syncContext.acquire( null, Collections.singleton( metadata ) );
if ( metadata.getFile() != null && metadata.getFile().exists() )
{
fis = new FileInputStream( metadata.getFile() );
org.apache.maven.artifact.repository.metadata.Metadata m =
new MetadataXpp3Reader().read( fis, false );
versioning = m.getVersioning();
/*
* NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata
* of the local repository. This is especially troublesome during snapshot resolution so we try
* to handle that gracefully.
*/
if ( versioning != null && repository instanceof LocalRepository )
{
if ( versioning.getSnapshot() != null && versioning.getSnapshot().getBuildNumber() > 0 )
{
Versioning repaired = new Versioning();
repaired.setLastUpdated( versioning.getLastUpdated() );
Snapshot snapshot = new Snapshot();
snapshot.setLocalCopy( true );
repaired.setSnapshot( snapshot );
versioning = repaired;
throw new IOException( "Snapshot information corrupted with remote repository data"
+ ", please verify that no remote repository uses the id '"
+ repository.getId() + "'" );
}
}
}
}
}
}
catch ( Exception e )
{
invalidMetadata( session, trace, metadata, repository, e );
result.addException( e );
}
finally
{
IOUtil.close( fis );
}
return ( versioning != null ) ? versioning : new Versioning();
}
private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
ArtifactRepository repository, Exception exception )
{
RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_INVALID );
event.setTrace( trace );
event.setMetadata( metadata );
event.setException( exception );
event.setRepository( repository );
repositoryEventDispatcher.dispatch( event.build() );
}
private void merge( Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning,
ArtifactRepository repository )
{
if ( StringUtils.isNotEmpty( versioning.getRelease() ) )
{
merge( RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository );
}
if ( StringUtils.isNotEmpty( versioning.getLatest() ) )
{
merge( LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository );
}
for ( SnapshotVersion sv : versioning.getSnapshotVersions() )
{
if ( StringUtils.isNotEmpty( sv.getVersion() ) )
{
String key = getKey( sv.getClassifier(), sv.getExtension() );
merge( SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository );
}
}
Snapshot snapshot = versioning.getSnapshot();
if ( snapshot != null && versioning.getSnapshotVersions().isEmpty() )
{
String version = artifact.getVersion();
if ( snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0 )
{
String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber();
version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier;
}
merge( SNAPSHOT, infos, versioning.getLastUpdated(), version, repository );
}
}
private void merge( String key, Map<String, VersionInfo> infos, String timestamp, String version,
ArtifactRepository repository )
{
VersionInfo info = infos.get( key );
if ( info == null )
{
info = new VersionInfo( timestamp, version, repository );
infos.put( key, info );
}
else if ( info.isOutdated( timestamp ) )
{
info.version = version;
info.repository = repository;
info.timestamp = timestamp;
}
}
private void merge( Map<String, VersionInfo> infos, String srcKey, String dstKey )
{
VersionInfo srcInfo = infos.get( srcKey );
VersionInfo dstInfo = infos.get( dstKey );
if ( dstInfo == null || ( srcInfo != null && dstInfo.isOutdated( srcInfo.timestamp )
&& srcInfo.repository != dstInfo.repository ) )
{
infos.put( dstKey, srcInfo );
}
}
private String getKey( String classifier, String extension )
{
return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension );
}
private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact )
{
/*
* The workspace/reactor is in flux so we better not assume definitive information for any of its
* artifacts/projects.
*/
WorkspaceReader workspace = session.getWorkspaceReader();
if ( workspace == null )
{
return true;
}
Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact( artifact );
return workspace.findArtifact( pomArtifact ) == null;
}
private static class VersionInfo
{
String timestamp;
String version;
ArtifactRepository repository;
public VersionInfo( String timestamp, String version, ArtifactRepository repository )
{
this.timestamp = ( timestamp != null ) ? timestamp : "";
this.version = version;
this.repository = repository;
}
public boolean isOutdated( String timestamp )
{
return timestamp != null && timestamp.compareTo( this.timestamp ) > 0;
}
}
private static class Key
{
private final String groupId;
private final String artifactId;
private final String classifier;
private final String extension;
private final String version;
private final String context;
private final File localRepo;
private final WorkspaceRepository workspace;
private final List<RemoteRepository> repositories;
private final int hashCode;
public Key( RepositorySystemSession session, VersionRequest request )
{
Artifact artifact = request.getArtifact();
groupId = artifact.getGroupId();
artifactId = artifact.getArtifactId();
classifier = artifact.getClassifier();
extension = artifact.getExtension();
version = artifact.getVersion();
localRepo = session.getLocalRepository().getBasedir();
workspace = CacheUtils.getWorkspace( session );
repositories = new ArrayList<>( request.getRepositories().size() );
boolean repoMan = false;
for ( RemoteRepository repository : request.getRepositories() )
{
if ( repository.isRepositoryManager() )
{
repoMan = true;
repositories.addAll( repository.getMirroredRepositories() );
}
else
{
repositories.add( repository );
}
}
context = repoMan ? request.getRequestContext() : "";
int hash = 17;
hash = hash * 31 + groupId.hashCode();
hash = hash * 31 + artifactId.hashCode();
hash = hash * 31 + classifier.hashCode();
hash = hash * 31 + extension.hashCode();
hash = hash * 31 + version.hashCode();
hash = hash * 31 + localRepo.hashCode();
hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories );
hashCode = hash;
}
@Override
public boolean equals( Object obj )
{
if ( obj == this )
{
return true;
}
else if ( obj == null || !getClass().equals( obj.getClass() ) )
{
return false;
}
Key that = (Key) obj;
return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId ) && classifier.equals(
that.classifier ) && extension.equals( that.extension ) && version.equals( that.version )
&& context.equals( that.context ) && localRepo.equals( that.localRepo )
&& CacheUtils.eq( workspace, that.workspace )
&& CacheUtils.repositoriesEquals( repositories, that.repositories );
}
@Override
public int hashCode()
{
return hashCode;
}
}
private static class Record
{
final String version;
final String repoId;
final Class<?> repoClass;
public Record( String version, ArtifactRepository repository )
{
this.version = version;
if ( repository != null )
{
repoId = repository.getId();
repoClass = repository.getClass();
}
else
{
repoId = null;
repoClass = null;
}
}
}
}