diff --git a/apache-maven/NOTICE.txt b/apache-maven/NOTICE.txt
index c7a0185664..4675185cca 100644
--- a/apache-maven/NOTICE.txt
+++ b/apache-maven/NOTICE.txt
@@ -13,7 +13,7 @@ The Apache Software Foundation (http://www.apache.org/).
This product includes software (Plexus and Classworlds) developed by
The Codehaus Foundation (http://www.codehaus.org/).
-This product includes software (Spice, Plexus Ciper and Sec Dispatcher) developed by
+This product includes software (Aether, Spice, Plexus Ciper and Sec Dispatcher) developed by
Sonatype Inc. (http://www.sonatype.org/).
This product includes software (NekoHTML) developed by
diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml
index 24efbd782f..691873997d 100644
--- a/apache-maven/pom.xml
+++ b/apache-maven/pom.xml
@@ -59,6 +59,10 @@
org.apache.maven.wagon
wagon-file
+
+ org.sonatype.aether
+ aether-connector-wagon
+
diff --git a/build.xml b/build.xml
index cc47ac917f..5dfbef63fe 100644
--- a/build.xml
+++ b/build.xml
@@ -195,7 +195,7 @@ Do you want to continue?
-
+
diff --git a/maven-aether-provider/pom.xml b/maven-aether-provider/pom.xml
new file mode 100644
index 0000000000..222495bb5c
--- /dev/null
+++ b/maven-aether-provider/pom.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+ 4.0.0
+
+
+ org.apache.maven
+ maven
+ 3.0-SNAPSHOT
+
+
+ maven-aether-provider
+
+ Maven Aether Provider
+
+ This module provides extensions to Aether for utilizing the Maven POM and Maven metadata.
+
+
+
+
+ org.apache.maven
+ maven-model-builder
+
+
+ org.apache.maven
+ maven-repository-metadata
+
+
+ org.sonatype.aether
+ aether-api
+
+
+ org.sonatype.aether
+ aether-util
+
+
+ org.sonatype.aether
+ aether-impl
+
+
+ org.codehaus.plexus
+ plexus-component-annotations
+
+
+
+
+
+
+ org.codehaus.plexus
+ plexus-component-metadata
+
+
+
+
+
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java
new file mode 100644
index 0000000000..bb73846ca8
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java
@@ -0,0 +1,449 @@
+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 java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.model.DependencyManagement;
+import org.apache.maven.model.DistributionManagement;
+import org.apache.maven.model.License;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Relocation;
+import org.apache.maven.model.Repository;
+import org.apache.maven.model.building.DefaultModelBuilderFactory;
+import org.apache.maven.model.building.DefaultModelBuildingRequest;
+import org.apache.maven.model.building.FileModelSource;
+import org.apache.maven.model.building.ModelBuilder;
+import org.apache.maven.model.building.ModelBuildingException;
+import org.apache.maven.model.building.ModelBuildingRequest;
+import org.apache.maven.model.building.ModelProblem;
+import org.apache.maven.model.resolution.UnresolvableModelException;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.RepositoryListener;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.artifact.ArtifactType;
+import org.sonatype.aether.artifact.ArtifactTypeRegistry;
+import org.sonatype.aether.graph.Dependency;
+import org.sonatype.aether.graph.Exclusion;
+import org.sonatype.aether.impl.ArtifactDescriptorReader;
+import org.sonatype.aether.impl.ArtifactResolver;
+import org.sonatype.aether.impl.RemoteRepositoryManager;
+import org.sonatype.aether.impl.VersionResolver;
+import org.sonatype.aether.transfer.ArtifactNotFoundException;
+import org.sonatype.aether.util.artifact.ArtifactProperties;
+import org.sonatype.aether.util.artifact.DefaultArtifact;
+import org.sonatype.aether.util.artifact.DefaultArtifactType;
+import org.sonatype.aether.util.artifact.SubArtifact;
+import org.sonatype.aether.util.listener.DefaultRepositoryEvent;
+import org.sonatype.aether.repository.RemoteRepository;
+import org.sonatype.aether.repository.RepositoryPolicy;
+import org.sonatype.aether.repository.WorkspaceRepository;
+import org.sonatype.aether.resolution.ArtifactDescriptorException;
+import org.sonatype.aether.resolution.ArtifactDescriptorRequest;
+import org.sonatype.aether.resolution.ArtifactDescriptorResult;
+import org.sonatype.aether.resolution.ArtifactRequest;
+import org.sonatype.aether.resolution.ArtifactResolutionException;
+import org.sonatype.aether.resolution.ArtifactResult;
+import org.sonatype.aether.resolution.VersionRequest;
+import org.sonatype.aether.resolution.VersionResolutionException;
+import org.sonatype.aether.spi.locator.Service;
+import org.sonatype.aether.spi.locator.ServiceLocator;
+import org.sonatype.aether.spi.log.Logger;
+import org.sonatype.aether.spi.log.NullLogger;
+
+/**
+ * @author Benjamin Bentmann
+ */
+@Component( role = ArtifactDescriptorReader.class )
+public class DefaultArtifactDescriptorReader
+ implements ArtifactDescriptorReader, Service
+{
+
+ @Requirement
+ private Logger logger = NullLogger.INSTANCE;
+
+ @Requirement
+ private RemoteRepositoryManager remoteRepositoryManager;
+
+ @Requirement
+ private VersionResolver versionResolver;
+
+ @Requirement
+ private ArtifactResolver artifactResolver;
+
+ @Requirement
+ private ModelBuilder modelBuilder;
+
+ public void initService( ServiceLocator locator )
+ {
+ setLogger( locator.getService( Logger.class ) );
+ setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) );
+ setVersionResolver( locator.getService( VersionResolver.class ) );
+ setArtifactResolver( locator.getService( ArtifactResolver.class ) );
+ modelBuilder = locator.getService( ModelBuilder.class );
+ if ( modelBuilder == null )
+ {
+ setModelBuilder( new DefaultModelBuilderFactory().newInstance() );
+ }
+ }
+
+ public DefaultArtifactDescriptorReader setLogger( Logger logger )
+ {
+ this.logger = ( logger != null ) ? logger : NullLogger.INSTANCE;
+ return this;
+ }
+
+ public DefaultArtifactDescriptorReader setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
+ {
+ if ( remoteRepositoryManager == null )
+ {
+ throw new IllegalArgumentException( "remote repository manager has not been specified" );
+ }
+ this.remoteRepositoryManager = remoteRepositoryManager;
+ return this;
+ }
+
+ public DefaultArtifactDescriptorReader setVersionResolver( VersionResolver versionResolver )
+ {
+ if ( versionResolver == null )
+ {
+ throw new IllegalArgumentException( "version resolver has not been specified" );
+ }
+ this.versionResolver = versionResolver;
+ return this;
+ }
+
+ public DefaultArtifactDescriptorReader setArtifactResolver( ArtifactResolver artifactResolver )
+ {
+ if ( artifactResolver == null )
+ {
+ throw new IllegalArgumentException( "artifact resolver has not been specified" );
+ }
+ this.artifactResolver = artifactResolver;
+ return this;
+ }
+
+ public DefaultArtifactDescriptorReader setModelBuilder( ModelBuilder modelBuilder )
+ {
+ if ( modelBuilder == null )
+ {
+ throw new IllegalArgumentException( "model builder has not been specified" );
+ }
+ this.modelBuilder = modelBuilder;
+ return this;
+ }
+
+ public ArtifactDescriptorResult readArtifactDescriptor( RepositorySystemSession session,
+ ArtifactDescriptorRequest request )
+ throws ArtifactDescriptorException
+ {
+ ArtifactDescriptorResult result = new ArtifactDescriptorResult( request );
+
+ Model model = loadPom( session, request, result );
+
+ if ( model != null )
+ {
+ ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
+
+ for ( Repository r : model.getRepositories() )
+ {
+ result.addRepository( convert( r ) );
+ }
+
+ for ( org.apache.maven.model.Dependency dependency : model.getDependencies() )
+ {
+ result.addDependency( convert( dependency, stereotypes ) );
+ }
+
+ DependencyManagement mngt = model.getDependencyManagement();
+ if ( mngt != null )
+ {
+ for ( org.apache.maven.model.Dependency dependency : mngt.getDependencies() )
+ {
+ result.addManagedDependency( convert( dependency, stereotypes ) );
+ }
+ }
+
+ Map properties = new LinkedHashMap();
+
+ List licenses = model.getLicenses();
+ properties.put( "license.count", Integer.valueOf( licenses.size() ) );
+ for ( int i = 0; i < licenses.size(); i++ )
+ {
+ License license = licenses.get( i );
+ properties.put( "license." + i + ".name", license.getName() );
+ properties.put( "license." + i + ".url", license.getUrl() );
+ properties.put( "license." + i + ".comments", license.getComments() );
+ properties.put( "license." + i + ".distribution", license.getDistribution() );
+ }
+
+ result.setProperties( properties );
+ }
+
+ return result;
+ }
+
+ private Model loadPom( RepositorySystemSession session, ArtifactDescriptorRequest request,
+ ArtifactDescriptorResult result )
+ throws ArtifactDescriptorException
+ {
+ Set visited = new LinkedHashSet();
+ for ( Artifact artifact = request.getArtifact();; )
+ {
+ try
+ {
+ VersionRequest versionRequest =
+ new VersionRequest( artifact, request.getRepositories(), request.getRequestContext() );
+ versionResolver.resolveVersion( session, versionRequest );
+ }
+ catch ( VersionResolutionException e )
+ {
+ result.addException( e );
+ throw new ArtifactDescriptorException( result );
+ }
+
+ if ( !visited.add( artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getBaseVersion() ) )
+ {
+ RepositoryException exception =
+ new RepositoryException( "Artifact relocations form a cycle: " + visited );
+ invalidDescriptor( session, artifact, exception );
+ if ( session.isIgnoreInvalidArtifactDescriptor() )
+ {
+ return null;
+ }
+ result.addException( exception );
+ throw new ArtifactDescriptorException( result );
+ }
+
+ Artifact pomArtifact = artifact;
+ if ( pomArtifact.getClassifier().length() > 0 || !"pom".equals( pomArtifact.getExtension() ) )
+ {
+ pomArtifact = new SubArtifact( artifact, "", "pom" );
+ }
+
+ ArtifactResult resolveResult;
+ try
+ {
+ ArtifactRequest resolveRequest =
+ new ArtifactRequest( pomArtifact, request.getRepositories(), request.getRequestContext() );
+ resolveResult = artifactResolver.resolveArtifact( session, resolveRequest );
+ pomArtifact = resolveResult.getArtifact();
+ result.setRepository( resolveResult.getRepository() );
+ }
+ catch ( ArtifactResolutionException e )
+ {
+ if ( e.getCause() instanceof ArtifactNotFoundException )
+ {
+ missingDescriptor( session, artifact );
+ if ( session.isIgnoreMissingArtifactDescriptor() )
+ {
+ return null;
+ }
+ }
+ result.addException( e );
+ throw new ArtifactDescriptorException( result );
+ }
+
+ Model model;
+ try
+ {
+ ModelBuildingRequest modelRequest = new DefaultModelBuildingRequest();
+ modelRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
+ modelRequest.setProcessPlugins( false );
+ modelRequest.setTwoPhaseBuilding( false );
+ modelRequest.setSystemProperties( toProperties( session.getSystemProperties() ) );
+ modelRequest.setUserProperties( toProperties( session.getUserProperties() ) );
+ modelRequest.setModelCache( DefaultModelCache.newInstance( session ) );
+ modelRequest.setModelResolver( new DefaultModelResolver( session, request.getRequestContext(),
+ artifactResolver, remoteRepositoryManager,
+ request.getRepositories() ) );
+ if ( resolveResult.getRepository() instanceof WorkspaceRepository )
+ {
+ modelRequest.setPomFile( pomArtifact.getFile() );
+ }
+ else
+ {
+ modelRequest.setModelSource( new FileModelSource( pomArtifact.getFile() ) );
+ }
+
+ model = modelBuilder.build( modelRequest ).getEffectiveModel();
+ }
+ catch ( ModelBuildingException e )
+ {
+ for ( ModelProblem problem : e.getProblems() )
+ {
+ if ( problem.getException() instanceof UnresolvableModelException )
+ {
+ result.addException( problem.getException() );
+ throw new ArtifactDescriptorException( result );
+ }
+ }
+ invalidDescriptor( session, artifact, e );
+ if ( session.isIgnoreInvalidArtifactDescriptor() )
+ {
+ return null;
+ }
+ result.addException( e );
+ throw new ArtifactDescriptorException( result );
+ }
+
+ Relocation relocation = getRelocation( model );
+
+ if ( relocation != null )
+ {
+ result.addRelocation( artifact );
+ artifact =
+ new RelocatedArtifact( artifact, relocation.getGroupId(), relocation.getArtifactId(),
+ relocation.getVersion() );
+ result.setArtifact( artifact );
+ }
+ else
+ {
+ return model;
+ }
+ }
+ }
+
+ private Properties toProperties( Map map )
+ {
+ Properties props = new Properties();
+ if ( map != null )
+ {
+ props.putAll( map );
+ }
+ return props;
+ }
+
+ private Relocation getRelocation( Model model )
+ {
+ Relocation relocation = null;
+ DistributionManagement distMngt = model.getDistributionManagement();
+ if ( distMngt != null )
+ {
+ relocation = distMngt.getRelocation();
+ }
+ return relocation;
+ }
+
+ private Dependency convert( org.apache.maven.model.Dependency dependency, ArtifactTypeRegistry stereotypes )
+ {
+ ArtifactType stereotype = stereotypes.get( dependency.getType() );
+ if ( stereotype == null )
+ {
+ stereotype = new DefaultArtifactType( dependency.getType() );
+ }
+
+ boolean system = dependency.getSystemPath() != null && dependency.getSystemPath().length() > 0;
+
+ Map props = null;
+ if ( system )
+ {
+ props = Collections.singletonMap( ArtifactProperties.LACKS_DESCRIPTOR, Boolean.TRUE.toString() );
+ }
+
+ Artifact artifact =
+ new DefaultArtifact( dependency.getGroupId(), dependency.getArtifactId(), dependency.getClassifier(), null,
+ dependency.getVersion(), props, stereotype );
+
+ if ( system )
+ {
+ artifact = artifact.setFile( new File( dependency.getSystemPath() ) );
+ }
+
+ List exclusions = new ArrayList( dependency.getExclusions().size() );
+ for ( org.apache.maven.model.Exclusion exclusion : dependency.getExclusions() )
+ {
+ exclusions.add( convert( exclusion ) );
+ }
+
+ Dependency result = new Dependency( artifact, dependency.getScope(), dependency.isOptional(), exclusions );
+
+ return result;
+ }
+
+ private Exclusion convert( org.apache.maven.model.Exclusion exclusion )
+ {
+ return new Exclusion( exclusion.getGroupId(), exclusion.getArtifactId(), "*", "*" );
+ }
+
+ static RemoteRepository convert( Repository repository )
+ {
+ RemoteRepository result =
+ new RemoteRepository( repository.getId(), repository.getLayout(), repository.getUrl() );
+ result.setPolicy( true, convert( repository.getSnapshots() ) );
+ result.setPolicy( false, convert( repository.getReleases() ) );
+ return result;
+ }
+
+ private static RepositoryPolicy convert( org.apache.maven.model.RepositoryPolicy policy )
+ {
+ boolean enabled = true;
+ String checksums = RepositoryPolicy.CHECKSUM_POLICY_WARN;
+ String updates = RepositoryPolicy.UPDATE_POLICY_DAILY;
+
+ if ( policy != null )
+ {
+ enabled = policy.isEnabled();
+ if ( policy.getUpdatePolicy() != null )
+ {
+ updates = policy.getUpdatePolicy();
+ }
+ if ( policy.getChecksumPolicy() != null )
+ {
+ checksums = policy.getChecksumPolicy();
+ }
+ }
+
+ return new RepositoryPolicy( enabled, updates, checksums );
+ }
+
+ private void missingDescriptor( RepositorySystemSession session, Artifact artifact )
+ {
+ RepositoryListener listener = session.getRepositoryListener();
+ if ( listener != null )
+ {
+ DefaultRepositoryEvent event = new DefaultRepositoryEvent( session, artifact );
+ listener.artifactDescriptorMissing( event );
+ }
+ }
+
+ private void invalidDescriptor( RepositorySystemSession session, Artifact artifact, Exception exception )
+ {
+ RepositoryListener listener = session.getRepositoryListener();
+ if ( listener != null )
+ {
+ DefaultRepositoryEvent event = new DefaultRepositoryEvent( session, artifact );
+ event.setException( exception );
+ listener.artifactDescriptorInvalid( event );
+ }
+ }
+
+}
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultModelCache.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultModelCache.java
new file mode 100644
index 0000000000..25b24c2c3f
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultModelCache.java
@@ -0,0 +1,119 @@
+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.maven.model.building.ModelCache;
+import org.sonatype.aether.RepositoryCache;
+import org.sonatype.aether.RepositorySystemSession;
+
+/**
+ * A model builder cache backed by the repository system cache.
+ *
+ * @author Benjamin Bentmann
+ */
+class DefaultModelCache
+ implements ModelCache
+{
+
+ private final RepositorySystemSession session;
+
+ private final RepositoryCache cache;
+
+ public static ModelCache newInstance( RepositorySystemSession session )
+ {
+ if ( session.getCache() == null )
+ {
+ return null;
+ }
+ else
+ {
+ return new DefaultModelCache( session );
+ }
+ }
+
+ private DefaultModelCache( RepositorySystemSession session )
+ {
+ this.session = session;
+ this.cache = session.getCache();
+ }
+
+ public Object get( String groupId, String artifactId, String version, String tag )
+ {
+ return cache.get( session, new Key( groupId, artifactId, version, tag ) );
+ }
+
+ public void put( String groupId, String artifactId, String version, String tag, Object data )
+ {
+ cache.put( session, new Key( groupId, artifactId, version, tag ), data );
+ }
+
+ static class Key
+ {
+
+ private final String groupId;
+
+ private final String artifactId;
+
+ private final String version;
+
+ private final String tag;
+
+ private final int hash;
+
+ public Key( String groupId, String artifactId, String version, String tag )
+ {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ this.tag = tag;
+
+ int h = 17;
+ h = h * 31 + this.groupId.hashCode();
+ h = h * 31 + this.artifactId.hashCode();
+ h = h * 31 + this.version.hashCode();
+ h = h * 31 + this.tag.hashCode();
+ hash = h;
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ {
+ return true;
+ }
+ if ( null == obj || !getClass().equals( obj.getClass() ) )
+ {
+ return false;
+ }
+
+ Key that = (Key) obj;
+ return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId )
+ && version.equals( that.version ) && tag.equals( that.tag );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return hash;
+ }
+
+ }
+}
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultModelResolver.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultModelResolver.java
new file mode 100644
index 0000000000..e9138bb93a
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultModelResolver.java
@@ -0,0 +1,128 @@
+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 java.io.File;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.model.Repository;
+import org.apache.maven.model.building.FileModelSource;
+import org.apache.maven.model.building.ModelSource;
+import org.apache.maven.model.resolution.InvalidRepositoryException;
+import org.apache.maven.model.resolution.ModelResolver;
+import org.apache.maven.model.resolution.UnresolvableModelException;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.impl.ArtifactResolver;
+import org.sonatype.aether.impl.RemoteRepositoryManager;
+import org.sonatype.aether.repository.RemoteRepository;
+import org.sonatype.aether.resolution.ArtifactRequest;
+import org.sonatype.aether.resolution.ArtifactResolutionException;
+import org.sonatype.aether.util.artifact.DefaultArtifact;
+
+/**
+ * A model resolver to assist building of dependency POMs. This resolver gives priority to those repositories that have
+ * been initially specified and repositories discovered in dependency POMs are recessively merged into the search chain.
+ *
+ * @author Benjamin Bentmann
+ * @see DefaultArtifactDescriptorReader
+ */
+class DefaultModelResolver
+ implements ModelResolver
+{
+
+ private final RepositorySystemSession session;
+
+ private final String context;
+
+ private List repositories;
+
+ private final ArtifactResolver resolver;
+
+ private final RemoteRepositoryManager remoteRepositoryManager;
+
+ private final Set repositoryIds;
+
+ public DefaultModelResolver( RepositorySystemSession session, String context, ArtifactResolver resolver,
+ RemoteRepositoryManager remoteRepositoryManager, List repositories )
+ {
+ this.session = session;
+ this.context = context;
+ this.resolver = resolver;
+ this.remoteRepositoryManager = remoteRepositoryManager;
+ this.repositories = repositories;
+ this.repositoryIds = new HashSet();
+ }
+
+ private DefaultModelResolver( DefaultModelResolver original )
+ {
+ this.session = original.session;
+ this.context = original.context;
+ this.resolver = original.resolver;
+ this.remoteRepositoryManager = original.remoteRepositoryManager;
+ this.repositories = original.repositories;
+ this.repositoryIds = new HashSet( original.repositoryIds );
+ }
+
+ public void addRepository( Repository repository )
+ throws InvalidRepositoryException
+ {
+ if ( !repositoryIds.add( repository.getId() ) )
+ {
+ return;
+ }
+
+ List newRepositories =
+ Collections.singletonList( DefaultArtifactDescriptorReader.convert( repository ) );
+
+ this.repositories =
+ remoteRepositoryManager.aggregateRepositories( session, repositories, newRepositories, true );
+ }
+
+ public ModelResolver newCopy()
+ {
+ return new DefaultModelResolver( this );
+ }
+
+ public ModelSource resolveModel( String groupId, String artifactId, String version )
+ throws UnresolvableModelException
+ {
+ Artifact pomArtifact = new DefaultArtifact( groupId, artifactId, "", "pom", version );
+
+ try
+ {
+ ArtifactRequest request = new ArtifactRequest( pomArtifact, repositories, context );
+ pomArtifact = resolver.resolveArtifact( session, request ).getArtifact();
+ }
+ catch ( ArtifactResolutionException e )
+ {
+ throw new UnresolvableModelException( "Failed to resolve POM for " + groupId + ":" + artifactId + ":"
+ + version + " due to " + e.getMessage(), groupId, artifactId, version, e );
+ }
+
+ File pomFile = pomArtifact.getFile();
+
+ return new FileModelSource( pomFile );
+ }
+
+}
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultServiceLocator.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultServiceLocator.java
new file mode 100644
index 0000000000..f598590567
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultServiceLocator.java
@@ -0,0 +1,51 @@
+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.sonatype.aether.impl.ArtifactDescriptorReader;
+import org.sonatype.aether.impl.MetadataGeneratorFactory;
+import org.sonatype.aether.impl.VersionRangeResolver;
+import org.sonatype.aether.impl.VersionResolver;
+
+/**
+ * A simple service locator that is already setup with all components from this library. To acquire a complete
+ * repository system, clients need to add some repository connectors for remote transfers. Note: This component
+ * is meant to assists those clients that employ the repository systems outside of an IoC container, Maven plugins
+ * should instead always use regular dependency injection to acquire the repository system.
+ *
+ * @author Benjamin Bentmann
+ */
+public class DefaultServiceLocator
+ extends org.sonatype.aether.impl.internal.DefaultServiceLocator
+{
+
+ /**
+ * Creates a new service locator that already knows about all service implementations included this library.
+ */
+ public DefaultServiceLocator()
+ {
+ addService( ArtifactDescriptorReader.class, DefaultArtifactDescriptorReader.class );
+ addService( VersionResolver.class, DefaultVersionResolver.class );
+ addService( VersionRangeResolver.class, DefaultVersionRangeResolver.class );
+ addService( MetadataGeneratorFactory.class, SnapshotMetadataGeneratorFactory.class );
+ addService( MetadataGeneratorFactory.class, VersionsMetadataGeneratorFactory.class );
+ }
+
+}
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
new file mode 100644
index 0000000000..a84bf7cc53
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
@@ -0,0 +1,269 @@
+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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.sonatype.aether.RepositoryListener;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.util.listener.DefaultRepositoryEvent;
+import org.sonatype.aether.util.metadata.DefaultMetadata;
+import org.sonatype.aether.util.version.GenericVersionScheme;
+import org.sonatype.aether.version.InvalidVersionSpecificationException;
+import org.sonatype.aether.version.Version;
+import org.sonatype.aether.version.VersionConstraint;
+import org.sonatype.aether.version.VersionRange;
+import org.sonatype.aether.version.VersionScheme;
+import org.sonatype.aether.impl.MetadataResolver;
+import org.sonatype.aether.impl.VersionRangeResolver;
+import org.sonatype.aether.metadata.Metadata;
+import org.sonatype.aether.repository.ArtifactRepository;
+import org.sonatype.aether.repository.LocalRepositoryManager;
+import org.sonatype.aether.repository.RemoteRepository;
+import org.sonatype.aether.repository.WorkspaceReader;
+import org.sonatype.aether.resolution.MetadataRequest;
+import org.sonatype.aether.resolution.MetadataResult;
+import org.sonatype.aether.resolution.VersionRangeRequest;
+import org.sonatype.aether.resolution.VersionRangeResolutionException;
+import org.sonatype.aether.resolution.VersionRangeResult;
+import org.sonatype.aether.spi.locator.Service;
+import org.sonatype.aether.spi.locator.ServiceLocator;
+import org.sonatype.aether.spi.log.Logger;
+import org.sonatype.aether.spi.log.NullLogger;
+
+/**
+ * @author Benjamin Bentmann
+ */
+@Component( role = VersionRangeResolver.class )
+public class DefaultVersionRangeResolver
+ implements VersionRangeResolver, Service
+{
+
+ private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
+
+ @Requirement
+ private Logger logger = NullLogger.INSTANCE;
+
+ @Requirement
+ private MetadataResolver metadataResolver;
+
+ public void initService( ServiceLocator locator )
+ {
+ setLogger( locator.getService( Logger.class ) );
+ setMetadataResolver( locator.getService( MetadataResolver.class ) );
+ }
+
+ public DefaultVersionRangeResolver setLogger( Logger logger )
+ {
+ this.logger = ( logger != null ) ? logger : NullLogger.INSTANCE;
+ return this;
+ }
+
+ public DefaultVersionRangeResolver setMetadataResolver( MetadataResolver metadataResolver )
+ {
+ if ( metadataResolver == null )
+ {
+ throw new IllegalArgumentException( "metadata resolver has not been specified" );
+ }
+ this.metadataResolver = metadataResolver;
+ return this;
+ }
+
+ public VersionRangeResult resolveVersionRange( RepositorySystemSession session, VersionRangeRequest request )
+ throws VersionRangeResolutionException
+ {
+ VersionRangeResult result = new VersionRangeResult( request );
+
+ VersionScheme versionScheme = new GenericVersionScheme();
+
+ VersionConstraint versionConstraint;
+ try
+ {
+ versionConstraint = versionScheme.parseVersionConstraint( request.getArtifact().getVersion() );
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ result.addException( e );
+ throw new VersionRangeResolutionException( result );
+ }
+
+ result.setVersionConstraint( versionConstraint );
+
+ if ( versionConstraint.getRanges().isEmpty() )
+ {
+ result.addVersion( versionConstraint.getVersion() );
+ }
+ else
+ {
+ Map versionIndex =
+ getVersions( session, result, request, getNature( session, versionConstraint.getRanges() ) );
+
+ List versions = new ArrayList();
+ for ( Map.Entry v : versionIndex.entrySet() )
+ {
+ try
+ {
+ Version ver = versionScheme.parseVersion( v.getKey() );
+ if ( versionConstraint.containsVersion( ver ) )
+ {
+ versions.add( ver );
+ result.setRepository( ver, v.getValue() );
+ }
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ result.addException( e );
+ }
+ }
+
+ Collections.sort( versions );
+ result.setVersions( versions );
+ }
+
+ return result;
+ }
+
+ private Map getVersions( RepositorySystemSession session, VersionRangeResult result,
+ VersionRangeRequest request, Metadata.Nature nature )
+ {
+ Map versionIndex = new HashMap();
+
+ Metadata metadata =
+ new DefaultMetadata( request.getArtifact().getGroupId(), request.getArtifact().getArtifactId(),
+ MAVEN_METADATA_XML, nature );
+
+ List metadataRequests = new ArrayList( request.getRepositories().size() );
+ for ( RemoteRepository repository : request.getRepositories() )
+ {
+ MetadataRequest metadataRequest = new MetadataRequest( metadata, repository, request.getRequestContext() );
+ metadataRequest.setDeleteLocalCopyIfMissing( true );
+ metadataRequests.add( metadataRequest );
+ }
+ List metadataResults = metadataResolver.resolveMetadata( session, metadataRequests );
+
+ WorkspaceReader workspace = session.getWorkspaceReader();
+ if ( workspace != null )
+ {
+ List versions = workspace.findVersions( request.getArtifact() );
+ for ( String version : versions )
+ {
+ versionIndex.put( version, workspace.getRepository() );
+ }
+ }
+
+ LocalRepositoryManager lrm = session.getLocalRepositoryManager();
+ File localMetadataFile = new File( lrm.getRepository().getBasedir(), lrm.getPathForLocalMetadata( metadata ) );
+ if ( localMetadataFile.isFile() )
+ {
+ metadata = metadata.setFile( localMetadataFile );
+ Versioning versioning = readVersions( session, metadata, result );
+ for ( String version : versioning.getVersions() )
+ {
+ if ( !versionIndex.containsKey( version ) )
+ {
+ versionIndex.put( version, lrm.getRepository() );
+ }
+ }
+ }
+
+ for ( MetadataResult metadataResult : metadataResults )
+ {
+ result.addException( metadataResult.getException() );
+ Versioning versioning = readVersions( session, metadataResult.getMetadata(), result );
+ for ( String version : versioning.getVersions() )
+ {
+ if ( !versionIndex.containsKey( version ) )
+ {
+ versionIndex.put( version, metadataResult.getRequest().getRepository() );
+ }
+ }
+ }
+
+ return versionIndex;
+ }
+
+ private Metadata.Nature getNature( RepositorySystemSession session, Collection ranges )
+ {
+ for ( VersionRange range : ranges )
+ {
+ if ( range.acceptsSnapshots() )
+ {
+ return Metadata.Nature.RELEASE_OR_SNAPSHOT;
+ }
+ }
+ return Metadata.Nature.RELEASE;
+ }
+
+ private Versioning readVersions( RepositorySystemSession session, Metadata metadata, VersionRangeResult result )
+ {
+ Versioning versioning = null;
+
+ FileInputStream fis = null;
+ try
+ {
+ if ( metadata != null && metadata.getFile() != null )
+ {
+ fis = new FileInputStream( metadata.getFile() );
+ org.apache.maven.artifact.repository.metadata.Metadata m = new MetadataXpp3Reader().read( fis, false );
+ versioning = m.getVersioning();
+ }
+ }
+ catch ( FileNotFoundException e )
+ {
+ // tolerable
+ }
+ catch ( Exception e )
+ {
+ invalidMetadata( session, metadata, e );
+ result.addException( e );
+ }
+ finally
+ {
+ IOUtil.close( fis );
+ }
+
+ return ( versioning != null ) ? versioning : new Versioning();
+ }
+
+ private void invalidMetadata( RepositorySystemSession session, Metadata metadata, Exception exception )
+ {
+ RepositoryListener listener = session.getRepositoryListener();
+ if ( listener != null )
+ {
+ DefaultRepositoryEvent event = new DefaultRepositoryEvent( session, metadata );
+ event.setException( exception );
+ listener.metadataInvalid( event );
+ }
+ }
+
+}
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionResolver.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionResolver.java
new file mode 100644
index 0000000000..684306040f
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionResolver.java
@@ -0,0 +1,487 @@
+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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.sonatype.aether.RepositoryCache;
+import org.sonatype.aether.RepositoryListener;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.util.listener.DefaultRepositoryEvent;
+import org.sonatype.aether.util.metadata.DefaultMetadata;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.impl.MetadataResolver;
+import org.sonatype.aether.impl.VersionResolver;
+import org.sonatype.aether.impl.internal.CacheUtils;
+import org.sonatype.aether.metadata.Metadata;
+import org.sonatype.aether.repository.ArtifactRepository;
+import org.sonatype.aether.repository.LocalRepositoryManager;
+import org.sonatype.aether.repository.RemoteRepository;
+import org.sonatype.aether.repository.WorkspaceReader;
+import org.sonatype.aether.repository.WorkspaceRepository;
+import org.sonatype.aether.resolution.MetadataRequest;
+import org.sonatype.aether.resolution.MetadataResult;
+import org.sonatype.aether.resolution.VersionRequest;
+import org.sonatype.aether.resolution.VersionResolutionException;
+import org.sonatype.aether.resolution.VersionResult;
+import org.sonatype.aether.spi.locator.Service;
+import org.sonatype.aether.spi.locator.ServiceLocator;
+import org.sonatype.aether.spi.log.Logger;
+import org.sonatype.aether.spi.log.NullLogger;
+
+/**
+ * @author Benjamin Bentmann
+ */
+@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";
+
+ @Requirement
+ private Logger logger = NullLogger.INSTANCE;
+
+ @Requirement
+ private MetadataResolver metadataResolver;
+
+ public void initService( ServiceLocator locator )
+ {
+ setLogger( locator.getService( Logger.class ) );
+ setMetadataResolver( locator.getService( MetadataResolver.class ) );
+ }
+
+ public DefaultVersionResolver setLogger( Logger logger )
+ {
+ this.logger = ( logger != null ) ? logger : NullLogger.INSTANCE;
+ return this;
+ }
+
+ public DefaultVersionResolver setMetadataResolver( MetadataResolver metadataResolver )
+ {
+ if ( metadataResolver == null )
+ {
+ throw new IllegalArgumentException( "metadata resolver has not been specified" );
+ }
+ this.metadataResolver = metadataResolver;
+ return this;
+ }
+
+ public VersionResult resolveVersion( RepositorySystemSession session, VersionRequest request )
+ throws VersionResolutionException
+ {
+ Artifact artifact = request.getArtifact();
+
+ String version = artifact.getVersion();
+
+ VersionResult result = new VersionResult( request );
+
+ Key cacheKey = null;
+ RepositoryCache cache = session.getCache();
+ if ( cache != null )
+ {
+ 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 metadataRequests = new ArrayList( request.getRepositories().size() );
+ for ( RemoteRepository repository : request.getRepositories() )
+ {
+ MetadataRequest metadataRequest =
+ new MetadataRequest( metadata, repository, request.getRequestContext() );
+ metadataRequest.setDeleteLocalCopyIfMissing( true );
+ metadataRequest.setFavorLocalRepository( true );
+ metadataRequests.add( metadataRequest );
+ }
+ List metadataResults = metadataResolver.resolveMetadata( session, metadataRequests );
+
+ LocalRepositoryManager lrm = session.getLocalRepositoryManager();
+ File localMetadataFile =
+ new File( lrm.getRepository().getBasedir(), lrm.getPathForLocalMetadata( metadata ) );
+ if ( localMetadataFile.isFile() )
+ {
+ metadata = metadata.setFile( localMetadataFile );
+ }
+
+ Map infos = new HashMap();
+ merge( artifact, infos, readVersions( session, metadata, result ),
+ session.getLocalRepositoryManager().getRepository() );
+
+ for ( MetadataResult metadataResult : metadataResults )
+ {
+ result.addException( metadataResult.getException() );
+ merge( artifact, infos, readVersions( session, metadataResult.getMetadata(), result ),
+ metadataResult.getRequest().getRepository() );
+ }
+
+ 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 )
+ {
+ subRequest.setRepositories( Collections.singletonList( (RemoteRepository) result.getRepository() ) );
+ }
+ 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
+ {
+ if ( !resolve( result, infos, SNAPSHOT + artifact.getClassifier() )
+ && !resolve( result, infos, SNAPSHOT ) )
+ {
+ result.setVersion( version );
+ }
+ }
+
+ if ( StringUtils.isEmpty( result.getVersion() ) )
+ {
+ throw new VersionResolutionException( result );
+ }
+ }
+
+ if ( cacheKey != null && metadata != null )
+ {
+ cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) );
+ }
+
+ return result;
+ }
+
+ private boolean resolve( VersionResult result, Map 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, Metadata metadata, VersionResult result )
+ {
+ Versioning versioning = null;
+
+ FileInputStream fis = null;
+ try
+ {
+ if ( metadata != null && metadata.getFile() != null )
+ {
+ fis = new FileInputStream( metadata.getFile() );
+ org.apache.maven.artifact.repository.metadata.Metadata m = new MetadataXpp3Reader().read( fis, false );
+ versioning = m.getVersioning();
+ }
+ }
+ catch ( FileNotFoundException e )
+ {
+ // tolerable
+ }
+ catch ( Exception e )
+ {
+ invalidMetadata( session, metadata, e );
+ result.addException( e );
+ }
+ finally
+ {
+ IOUtil.close( fis );
+ }
+
+ return ( versioning != null ) ? versioning : new Versioning();
+ }
+
+ private void invalidMetadata( RepositorySystemSession session, Metadata metadata, Exception exception )
+ {
+ RepositoryListener listener = session.getRepositoryListener();
+ if ( listener != null )
+ {
+ DefaultRepositoryEvent event = new DefaultRepositoryEvent( session, metadata );
+ event.setException( exception );
+ listener.metadataInvalid( event );
+ }
+ }
+
+ private void merge( Artifact artifact, Map 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 );
+ }
+
+ boolean mainSnapshot = false;
+ for ( SnapshotVersion sv : versioning.getSnapshotVersions() )
+ {
+ if ( StringUtils.isNotEmpty( sv.getVersion() ) )
+ {
+ mainSnapshot |= sv.getClassifier().length() <= 0;
+ merge( SNAPSHOT + sv.getClassifier(), infos, sv.getUpdated(), sv.getVersion(), repository );
+ }
+ }
+
+ Snapshot snapshot = versioning.getSnapshot();
+ if ( !mainSnapshot && snapshot != null )
+ {
+ 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 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;
+ }
+ }
+
+ 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 version;
+
+ private final String context;
+
+ private final File localRepo;
+
+ private final WorkspaceRepository workspace;
+
+ private final List repositories;
+
+ private final int hashCode;
+
+ public Key( RepositorySystemSession session, VersionRequest request )
+ {
+ groupId = request.getArtifact().getGroupId();
+ artifactId = request.getArtifact().getArtifactId();
+ version = request.getArtifact().getVersion();
+ context = request.getRequestContext();
+ localRepo = session.getLocalRepository().getBasedir();
+ workspace = CacheUtils.getWorkspace( session );
+ repositories = new ArrayList( request.getRepositories().size() );
+ for ( RemoteRepository repository : request.getRepositories() )
+ {
+ if ( repository.isRepositoryManager() )
+ {
+ repositories.addAll( repository.getMirroredRepositories() );
+ }
+ else
+ {
+ repositories.add( repository );
+ }
+ }
+
+ int hash = 17;
+ hash = hash * 31 + groupId.hashCode();
+ hash = hash * 31 + artifactId.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 )
+ && 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;
+ }
+ }
+ }
+
+}
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/LocalSnapshotMetadata.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/LocalSnapshotMetadata.java
new file mode 100644
index 0000000000..7ad5ba2417
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/LocalSnapshotMetadata.java
@@ -0,0 +1,102 @@
+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 java.io.File;
+
+import org.apache.maven.artifact.repository.metadata.Metadata;
+import org.apache.maven.artifact.repository.metadata.Snapshot;
+import org.apache.maven.artifact.repository.metadata.Versioning;
+import org.sonatype.aether.artifact.Artifact;
+
+/**
+ * @author Benjamin Bentmann
+ */
+final class LocalSnapshotMetadata
+ extends MavenMetadata
+{
+
+ private final Artifact artifact;
+
+ public LocalSnapshotMetadata( Artifact artifact )
+ {
+ super( createMetadata( artifact ), null );
+ this.artifact = artifact;
+ }
+
+ public LocalSnapshotMetadata( Artifact artifact, File file )
+ {
+ super( createMetadata( artifact ), file );
+ this.artifact = artifact;
+ }
+
+ private static Metadata createMetadata( Artifact artifact )
+ {
+ Snapshot snapshot = new Snapshot();
+ snapshot.setLocalCopy( true );
+ Versioning versioning = new Versioning();
+ versioning.setSnapshot( snapshot );
+
+ Metadata metadata = new Metadata();
+ metadata.setModelVersion( "1.0.0" );
+ metadata.setVersioning( versioning );
+ metadata.setGroupId( artifact.getGroupId() );
+ metadata.setArtifactId( artifact.getArtifactId() );
+ metadata.setVersion( artifact.getBaseVersion() );
+
+ return metadata;
+ }
+
+ public MavenMetadata setFile( File file )
+ {
+ return new LocalSnapshotMetadata( artifact, file );
+ }
+
+ public Object getKey()
+ {
+ return getGroupId() + ':' + getArtifactId() + ':' + getVersion();
+ }
+
+ public static Object getKey( Artifact artifact )
+ {
+ return artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getBaseVersion();
+ }
+
+ public String getGroupId()
+ {
+ return artifact.getGroupId();
+ }
+
+ public String getArtifactId()
+ {
+ return artifact.getArtifactId();
+ }
+
+ public String getVersion()
+ {
+ return artifact.getBaseVersion();
+ }
+
+ public Nature getNature()
+ {
+ return Nature.SNAPSHOT;
+ }
+
+}
diff --git a/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/LocalSnapshotMetadataGenerator.java b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/LocalSnapshotMetadataGenerator.java
new file mode 100644
index 0000000000..8b234dd169
--- /dev/null
+++ b/maven-aether-provider/src/main/java/org/apache/maven/repository/internal/LocalSnapshotMetadataGenerator.java
@@ -0,0 +1,76 @@
+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 java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.impl.MetadataGenerator;
+import org.sonatype.aether.installation.InstallRequest;
+import org.sonatype.aether.metadata.Metadata;
+
+/**
+ * @author Benjamin Bentmann
+ */
+class LocalSnapshotMetadataGenerator
+ implements MetadataGenerator
+{
+
+ private Map