blob: a9688d106d5f12686e78d99eefa0b2fe46a2f641 [file] [log] [blame]
package org.apache.maven.plugins.assembly.repository;
/*
* 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.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.DefaultArtifactRepository;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.artifact.repository.metadata.ArtifactRepositoryMetadata;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugins.assembly.repository.model.GroupVersionAlignment;
import org.apache.maven.plugins.assembly.repository.model.RepositoryInfo;
import org.apache.maven.project.DefaultMavenProjectBuilder;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.artifact.TransferUtils;
import org.apache.maven.shared.artifact.filter.PatternExcludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.ScopeArtifactFilter;
import org.apache.maven.shared.artifact.resolve.ArtifactResolver;
import org.apache.maven.shared.artifact.resolve.ArtifactResolverException;
import org.apache.maven.shared.artifact.resolve.ArtifactResult;
import org.apache.maven.shared.dependency.resolve.DependencyResolver;
import org.apache.maven.shared.dependency.resolve.DependencyResolverException;
import org.apache.maven.shared.repository.RepositoryManager;
import org.apache.maven.shared.utils.io.FileUtils;
import org.apache.maven.shared.utils.io.IOUtil;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.logging.Logger;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
import static org.apache.commons.codec.digest.DigestUtils.shaHex;
/**
* @author Jason van Zyl
*/
// todo will need to pop the processed project cache using reflection
@Component( role = RepositoryAssembler.class )
public class DefaultRepositoryAssembler
extends AbstractLogEnabled
implements RepositoryAssembler
{
protected static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
protected static final String UTC_TIMESTAMP_PATTERN = "yyyyMMddHHmmss";
@Requirement
protected ArtifactFactory artifactFactory;
// Replaced by Contextualizable code, to select the resolver in order of preference.
//
// @plexus.requirement
//
@Requirement
protected ArtifactResolver artifactResolver;
@Requirement
protected ArtifactRepositoryLayout repositoryLayout;
@Requirement
protected ArtifactRepositoryFactory artifactRepositoryFactory;
@Requirement
protected ArtifactMetadataSource metadataSource;
@Requirement
protected MavenProjectBuilder projectBuilder;
@Requirement
private DependencyResolver dependencyResolver;
@Requirement
private RepositoryManager repositoryManager;
public void buildRemoteRepository( File repositoryDirectory, RepositoryInfo repository,
RepositoryBuilderConfigSource configSource )
throws RepositoryAssemblyException
{
MavenProject project = configSource.getProject();
ArtifactRepository localRepository = configSource.getLocalRepository();
ProjectBuildingRequest buildingRequest = configSource.getProjectBuildingRequest();
Iterable<ArtifactResult> result = null;
Collection<Dependency> dependencies = project.getDependencies();
if ( dependencies == null )
{
Logger logger = getLogger();
if ( logger.isDebugEnabled() )
{
logger.debug( "dependency-artifact set for project: " + project.getId()
+ " is null. Skipping repository processing." );
}
return;
}
Collection<Dependency> managedDependencies = null;
if ( project.getDependencyManagement() != null )
{
managedDependencies = project.getDependencyManagement().getDependencies();
}
// Older Aether versions use an cache which can't be cleared. So can't delete repoDir and use it again.
// Instead create a temporary repository, delete it at end (should be in a finally-block)
File tempRepo = new File( repositoryDirectory.getParentFile(), repositoryDirectory.getName() + "_tmp" );
buildingRequest = repositoryManager.setLocalRepositoryBasedir( buildingRequest, tempRepo );
try
{
result = dependencyResolver.resolveDependencies( buildingRequest, dependencies, managedDependencies, null );
}
catch ( DependencyResolverException e )
{
throw new RepositoryAssemblyException( "Error resolving artifacts: " + e.getMessage(), e );
}
try
{
// Blow the cache in the project builder so that we get POMs again
// on this next download
invalidateProccessedProjectCache();
}
catch ( Exception e )
{
throw new RepositoryAssemblyException( "Error invalidating the processed project cache.", e );
}
ArtifactFilter filter = buildRepositoryFilter( repository, project );
buildingRequest = repositoryManager.setLocalRepositoryBasedir( buildingRequest, repositoryDirectory );
ArtifactRepository targetRepository = createLocalRepository( repositoryDirectory );
Map<String, GroupVersionAlignment> groupVersionAlignments =
createGroupVersionAlignments( repository.getGroupVersionAlignments() );
assembleRepositoryArtifacts( buildingRequest, result, filter, project, localRepository, targetRepository,
groupVersionAlignments );
ArtifactRepository centralRepository = findCentralRepository( project );
if ( repository.isIncludeMetadata() )
{
assembleRepositoryMetadata( result, filter, centralRepository, targetRepository );
}
try
{
FileUtils.deleteDirectory( tempRepo );
}
catch ( IOException e )
{
// noop
}
}
private ArtifactFilter buildRepositoryFilter( RepositoryInfo repository, MavenProject project )
{
AndArtifactFilter filter = new AndArtifactFilter();
ArtifactFilter scopeFilter = new ScopeArtifactFilter( repository.getScope() );
filter.add( scopeFilter );
// ----------------------------------------------------------------------------
// Includes
//
// We'll take everything if no includes are specified to try and make
// this
// process more maintainable. Don't want to have to update the assembly
// descriptor everytime the POM is updated.
// ----------------------------------------------------------------------------
List<String> includes = repository.getIncludes();
if ( ( includes == null ) || includes.isEmpty() )
{
List<String> patterns = new ArrayList<String>();
Set<Artifact> projectArtifacts = project.getDependencyArtifacts();
if ( projectArtifacts != null )
{
for ( Artifact artifact : projectArtifacts )
{
patterns.add( artifact.getDependencyConflictId() );
}
}
PatternIncludesArtifactFilter includeFilter = new PatternIncludesArtifactFilter( patterns, true );
filter.add( includeFilter );
}
else
{
filter.add( new PatternIncludesArtifactFilter( repository.getIncludes(), true ) );
}
// ----------------------------------------------------------------------------
// Excludes
//
// We still want to make it easy to exclude a few things even if we
// slurp
// up everything.
// ----------------------------------------------------------------------------
List<String> excludes = repository.getExcludes();
if ( ( excludes != null ) && !excludes.isEmpty() )
{
filter.add( new PatternExcludesArtifactFilter( repository.getExcludes(), true ) );
}
return filter;
}
private void assembleRepositoryArtifacts( ProjectBuildingRequest buildingRequest, Iterable<ArtifactResult> result,
ArtifactFilter filter, MavenProject project,
ArtifactRepository localRepository, ArtifactRepository targetRepository,
Map<String, GroupVersionAlignment> groupVersionAlignments )
throws RepositoryAssemblyException
{
try
{
for ( ArtifactResult ar : result )
{
Artifact a = ar.getArtifact();
if ( filter.include( a ) )
{
getLogger().debug( "Re-resolving: " + a + " for repository assembly." );
setAlignment( a, groupVersionAlignments );
artifactResolver.resolveArtifact( buildingRequest, TransferUtils.toArtifactCoordinate( a ) );
a.setVersion( a.getBaseVersion() );
File targetFile = new File( targetRepository.getBasedir(), targetRepository.pathOf( a ) );
FileUtils.copyFile( a.getFile(), targetFile );
writeChecksums( targetFile );
}
}
}
catch ( ArtifactResolverException e )
{
throw new RepositoryAssemblyException( "Error resolving artifacts: " + e.getMessage(), e );
}
catch ( IOException e )
{
throw new RepositoryAssemblyException( "Error writing artifact metdata.", e );
}
}
private ArtifactRepository findCentralRepository( MavenProject project )
{
ArtifactRepository centralRepository = null;
for ( ArtifactRepository r : project.getRemoteArtifactRepositories() )
{
if ( "central".equals( r.getId() ) )
{
centralRepository = r;
}
}
return centralRepository;
}
private void assembleRepositoryMetadata( Iterable<ArtifactResult> result, ArtifactFilter filter,
ArtifactRepository centralRepository, ArtifactRepository targetRepository )
throws RepositoryAssemblyException
{
for ( ArtifactResult ar : result )
{
Artifact a = ar.getArtifact();
if ( filter.include( a ) )
{
Versioning v = new Versioning();
v.setRelease( a.getVersion() );
v.setLatest( a.getVersion() );
v.addVersion( a.getVersion() );
v.setLastUpdated( getUtcDateFormatter().format( new Date() ) );
ArtifactRepositoryMetadata metadata = new ArtifactRepositoryMetadata( a, v );
String path = targetRepository.pathOfLocalRepositoryMetadata( metadata, centralRepository );
File metadataFile = new File( targetRepository.getBasedir(), path );
MetadataXpp3Writer metadataWriter = new MetadataXpp3Writer();
Writer writer = null;
try
{
writer = new FileWriter( metadataFile );
metadataWriter.write( writer, metadata.getMetadata() );
}
catch ( IOException e )
{
throw new RepositoryAssemblyException( "Error writing artifact metdata.", e );
}
finally
{
IOUtil.close( writer );
}
try
{
writeChecksums( metadataFile );
File metadataFileRemote = new File( targetRepository.getBasedir(),
targetRepository.pathOfRemoteRepositoryMetadata( metadata ) );
FileUtils.copyFile( metadataFile, metadataFileRemote );
FileUtils.copyFile( new File( metadataFile.getParentFile(), metadataFile.getName() + ".sha1" ),
new File( metadataFileRemote.getParentFile(),
metadataFileRemote.getName() + ".sha1" ) );
FileUtils.copyFile( new File( metadataFile.getParentFile(), metadataFile.getName() + ".md5" ),
new File( metadataFileRemote.getParentFile(),
metadataFileRemote.getName() + ".md5" ) );
}
catch ( IOException e )
{
throw new RepositoryAssemblyException( "Error writing artifact metdata.", e );
}
}
}
}
private void writeChecksums( File file )
throws IOException, RepositoryAssemblyException
{
FileInputStream data = new FileInputStream( file );
String md5 = md5Hex( data ).toUpperCase();
data.close();
FileInputStream data1 = new FileInputStream( file );
String sha1 = shaHex( data1 ).toUpperCase();
data1.close();
FileUtils.fileWrite( new File( file.getParentFile(), file.getName() + ".md5" ).getAbsolutePath(),
md5.toLowerCase() );
FileUtils.fileWrite( new File( file.getParentFile(), file.getName() + ".sha1" ).getAbsolutePath(),
sha1.toLowerCase() );
}
// CHECKSTYLE_OFF: LineLength
protected Map<String, GroupVersionAlignment> createGroupVersionAlignments( List<GroupVersionAlignment> versionAlignments )
// CHECKSTYLE_ON: LineLength
{
Map<String, GroupVersionAlignment> groupVersionAlignments = new HashMap<String, GroupVersionAlignment>();
if ( versionAlignments != null )
{
for ( GroupVersionAlignment alignment : versionAlignments )
{
groupVersionAlignments.put( alignment.getId(), alignment );
}
}
return groupVersionAlignments;
}
protected static DateFormat getUtcDateFormatter()
{
DateFormat utcDateFormatter = new SimpleDateFormat( UTC_TIMESTAMP_PATTERN );
utcDateFormatter.setTimeZone( UTC_TIME_ZONE );
return utcDateFormatter;
}
protected ArtifactRepository createLocalRepository( File directory )
{
String localRepositoryUrl = directory.getAbsolutePath();
if ( !localRepositoryUrl.startsWith( "file:" ) )
{
localRepositoryUrl = "file://" + localRepositoryUrl;
}
return createRepository( "local", localRepositoryUrl, false, true,
ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN );
}
public ArtifactRepository createRepository( String repositoryId, String repositoryUrl, boolean offline,
boolean updateSnapshots, String globalChecksumPolicy )
{
ArtifactRepository localRepository =
new DefaultArtifactRepository( repositoryId, repositoryUrl, repositoryLayout );
boolean snapshotPolicySet = false;
if ( offline )
{
snapshotPolicySet = true;
}
if ( !snapshotPolicySet && updateSnapshots )
{
artifactRepositoryFactory.setGlobalUpdatePolicy( ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS );
}
artifactRepositoryFactory.setGlobalChecksumPolicy( globalChecksumPolicy );
return localRepository;
}
private void invalidateProccessedProjectCache()
throws Exception
{
Class<DefaultMavenProjectBuilder> klass = DefaultMavenProjectBuilder.class;
try
{
Field field = klass.getDeclaredField( "processedProjectCache" );
field.setAccessible( true );
Object cache = field.get( projectBuilder );
cache.getClass().getDeclaredMethod( "clear", null ).invoke( cache, null );
field.setAccessible( false );
}
catch ( NoSuchFieldException e )
{
// fine... no field, no cache. we'll ignore it.
}
}
private void setAlignment( Artifact artifact, Map<String, GroupVersionAlignment> groupVersionAlignments )
{
GroupVersionAlignment alignment = groupVersionAlignments.get( artifact.getGroupId() );
if ( alignment != null )
{
if ( !alignment.getExcludes().contains( artifact.getArtifactId() ) )
{
artifact.setVersion( alignment.getVersion() );
}
}
}
}