blob: 5e3eeda37016b35379d400a3c270a5c1719dbc7f [file] [log] [blame]
package org.apache.maven.plugins.javadoc.resolver;
/*
* 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 static org.codehaus.plexus.util.IOUtil.close;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.plugins.javadoc.AbstractJavadocMojo;
import org.apache.maven.plugins.javadoc.JavadocUtil;
import org.apache.maven.plugins.javadoc.ResourcesBundleMojo;
import org.apache.maven.plugins.javadoc.options.JavadocOptions;
import org.apache.maven.plugins.javadoc.options.io.xpp3.JavadocOptionsXpp3Reader;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
*
*/
@Component( role = ResourceResolver.class )
public final class ResourceResolver extends AbstractLogEnabled
{
@Requirement
private ArtifactFactory artifactFactory;
@Requirement
private ArtifactResolver resolver;
@Requirement
private DependencyResolver dependencyResolver;
@Requirement
private ArtifactMetadataSource artifactMetadataSource;
@Requirement
private ArchiverManager archiverManager;
/**
* The classifier for sources.
*/
public static final String SOURCES_CLASSIFIER = "sources";
/**
* The classifier for test sources
*/
public static final String TEST_SOURCES_CLASSIFIER = "test-sources";
private static final List<String> SOURCE_VALID_CLASSIFIERS = Arrays.asList( SOURCES_CLASSIFIER,
TEST_SOURCES_CLASSIFIER );
private static final List<String> RESOURCE_VALID_CLASSIFIERS =
Arrays.asList( AbstractJavadocMojo.JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER,
AbstractJavadocMojo.TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER );
/**
* @param config {@link SourceResolverConfig}
* @return list of {@link JavadocBundle}.
* @throws IOException {@link IOException}
*/
public List<JavadocBundle> resolveDependencyJavadocBundles( final SourceResolverConfig config )
throws IOException
{
final List<JavadocBundle> bundles = new ArrayList<>();
final Map<String, MavenProject> projectMap = new HashMap<>();
if ( config.reactorProjects() != null )
{
for ( final MavenProject p : config.reactorProjects() )
{
projectMap.put( key( p.getGroupId(), p.getArtifactId() ), p );
}
}
final List<Artifact> artifacts = config.project().getTestArtifacts();
final List<Artifact> forResourceResolution = new ArrayList<>( artifacts.size() );
for ( final Artifact artifact : artifacts )
{
final String key = key( artifact.getGroupId(), artifact.getArtifactId() );
final MavenProject p = projectMap.get( key );
if ( p != null )
{
bundles.addAll( resolveBundleFromProject( config, p, artifact ) );
}
else
{
forResourceResolution.add( artifact );
}
}
bundles.addAll( resolveBundlesFromArtifacts( config, forResourceResolution ) );
return bundles;
}
/**
* @param config {@link SourceResolverConfig}
* @return The list of resolved dependencies.
* @throws ArtifactResolutionException {@link ArtifactResolutionException}
* @throws ArtifactNotFoundException {@link ArtifactNotFoundException}
*/
public Map<String, Collection<String>> resolveDependencySourcePaths( final SourceResolverConfig config )
throws ArtifactResolutionException, ArtifactNotFoundException
{
final Map<String, Collection<String>> mappedDirs = new LinkedHashMap<>();
final Map<String, MavenProject> projectMap = new HashMap<>();
if ( config.reactorProjects() != null )
{
for ( final MavenProject p : config.reactorProjects() )
{
projectMap.put( key( p.getGroupId(), p.getArtifactId() ), p );
}
}
final List<Artifact> artifacts = config.project().getTestArtifacts();
final List<Artifact> forResourceResolution = new ArrayList<>( artifacts.size() );
for ( final Artifact artifact : artifacts )
{
final String key = key( artifact.getGroupId(), artifact.getArtifactId() );
final MavenProject p = projectMap.get( key );
if ( p != null )
{
mappedDirs.put( key, resolveFromProject( config, p, artifact ) );
}
else
{
forResourceResolution.add( artifact );
}
}
for ( Map.Entry<String, String> entry : resolveFromArtifacts( config, forResourceResolution ) )
{
mappedDirs.put( entry.getKey(), Collections.singletonList( entry.getValue() ) );
}
return mappedDirs;
}
private static List<JavadocBundle> resolveBundleFromProject( SourceResolverConfig config, MavenProject project,
Artifact artifact ) throws IOException
{
List<JavadocBundle> bundles = new ArrayList<>();
List<String> classifiers = new ArrayList<>();
if ( config.includeCompileSources() )
{
classifiers.add( AbstractJavadocMojo.JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER );
}
if ( config.includeTestSources() )
{
classifiers.add( AbstractJavadocMojo.TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER );
}
for ( String classifier : classifiers )
{
File optionsFile =
new File( project.getBuild().getDirectory(), "javadoc-bundle-options/javadoc-options-" + classifier
+ ".xml" );
if ( !optionsFile.exists() )
{
continue;
}
FileInputStream stream = null;
try
{
stream = new FileInputStream( optionsFile );
JavadocOptions options = new JavadocOptionsXpp3Reader().read( stream );
stream.close();
stream = null;
bundles.add( new JavadocBundle( options, new File( project.getBasedir(),
options.getJavadocResourcesDirectory() ) ) );
}
catch ( XmlPullParserException e )
{
IOException error =
new IOException( "Failed to read javadoc options from: " + optionsFile + "\nReason: "
+ e.getMessage(), e );
throw error;
}
finally
{
close( stream );
}
}
return bundles;
}
private List<JavadocBundle> resolveBundlesFromArtifacts( final SourceResolverConfig config,
final List<Artifact> artifacts )
throws IOException
{
final List<Artifact> toResolve = new ArrayList<>( artifacts.size() );
for ( final Artifact artifact : artifacts )
{
if ( config.filter() != null
&& !new ArtifactIncludeFilterTransformer().transform( config.filter() ).include( artifact ) )
{
continue;
}
if ( config.includeCompileSources() )
{
toResolve.add( createResourceArtifact( artifact,
AbstractJavadocMojo.JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER,
config ) );
}
if ( config.includeTestSources() )
{
toResolve.add( createResourceArtifact( artifact,
AbstractJavadocMojo.TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER,
config ) );
}
}
List<String> dirs = new ArrayList<>( toResolve.size() );
try
{
for ( Map.Entry<String, String> entry : resolveAndUnpack( toResolve, config, RESOURCE_VALID_CLASSIFIERS,
false ) )
{
dirs.add( entry.getValue() );
}
}
catch ( ArtifactResolutionException | ArtifactNotFoundException e )
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( e.getMessage(), e );
}
}
List<JavadocBundle> result = new ArrayList<>();
for ( String d : dirs )
{
File dir = new File( d );
File resources = new File( dir, ResourcesBundleMojo.RESOURCES_DIR_PATH );
JavadocOptions options = null;
File javadocOptions = new File( dir, ResourcesBundleMojo.BUNDLE_OPTIONS_PATH );
if ( javadocOptions.exists() )
{
try ( FileInputStream reader = new FileInputStream( javadocOptions ) )
{
options = new JavadocOptionsXpp3Reader().read( reader );
}
catch ( XmlPullParserException e )
{
IOException error = new IOException( "Failed to parse javadoc options: " + e.getMessage(), e );
throw error;
}
}
result.add( new JavadocBundle( options, resources ) );
}
return result;
}
private Collection<Entry<String, String>> resolveFromArtifacts( final SourceResolverConfig config,
final List<Artifact> artifacts )
throws ArtifactResolutionException, ArtifactNotFoundException
{
final List<Artifact> toResolve = new ArrayList<>( artifacts.size() );
for ( final Artifact artifact : artifacts )
{
if ( config.filter() != null
&& !new ArtifactIncludeFilterTransformer().transform( config.filter() ).include( artifact ) )
{
continue;
}
if ( config.includeCompileSources() )
{
toResolve.add( createResourceArtifact( artifact, SOURCES_CLASSIFIER, config ) );
}
if ( config.includeTestSources() )
{
toResolve.add( createResourceArtifact( artifact, TEST_SOURCES_CLASSIFIER, config ) );
}
}
return resolveAndUnpack( toResolve, config, SOURCE_VALID_CLASSIFIERS, true );
}
private Artifact createResourceArtifact( final Artifact artifact, final String classifier,
final SourceResolverConfig config )
{
final DefaultArtifact a =
(DefaultArtifact) artifactFactory.createArtifactWithClassifier( artifact.getGroupId(),
artifact.getArtifactId(),
artifact.getVersion(), "jar",
classifier );
a.setRepository( artifact.getRepository() );
return a;
}
/**
*
* @param artifacts the artifacts to resolve
* @param config the configuration
* @param validClassifiers
* @param propagateErrors
* @return list of <dependencyConflictId, absolutePath>
* @throws ArtifactResolutionException if an exception occurs
* @throws ArtifactNotFoundException if an exception occurs
*/
private Collection<Map.Entry<String, String>> resolveAndUnpack( final List<Artifact> artifacts,
final SourceResolverConfig config,
final List<String> validClassifiers,
final boolean propagateErrors )
throws ArtifactResolutionException, ArtifactNotFoundException
{
// NOTE: Since these are '-sources' and '-test-sources' artifacts, they won't actually
// resolve transitively...this is just used to aggregate resolution failures into a single
// exception.
final Set<Artifact> artifactSet = new LinkedHashSet<>( artifacts );
final ArtifactFilter filter;
if ( config.filter() != null )
{
filter = new ArtifactIncludeFilterTransformer().transform( config.filter() );
}
else
{
filter = null;
}
final List<Map.Entry<String, String>> result = new ArrayList<>( artifacts.size() );
for ( final Artifact a : artifactSet )
{
if ( !validClassifiers.contains( a.getClassifier() ) || ( filter != null && !filter.include( a ) ) )
{
continue;
}
Artifact resolvedArtifact;
try
{
resolvedArtifact = resolver.resolveArtifact( config.getBuildingRequest(), a ).getArtifact();
}
catch ( ArtifactResolverException e1 )
{
continue;
}
final File d =
new File( config.outputBasedir(), a.getArtifactId() + "-" + a.getVersion() + "-" + a.getClassifier() );
if ( !d.exists() )
{
d.mkdirs();
}
try
{
final UnArchiver unArchiver = archiverManager.getUnArchiver( a.getType() );
unArchiver.setDestDirectory( d );
unArchiver.setSourceFile( resolvedArtifact.getFile() );
unArchiver.extract();
result.add( new AbstractMap.SimpleEntry<>( a.getDependencyConflictId(), d.getAbsolutePath() ) );
}
catch ( final NoSuchArchiverException e )
{
if ( propagateErrors )
{
throw new ArtifactResolutionException( "Failed to retrieve valid un-archiver component: "
+ a.getType(), a, e );
}
}
catch ( final ArchiverException e )
{
if ( propagateErrors )
{
throw new ArtifactResolutionException( "Failed to unpack: " + a.getId(), a, e );
}
}
}
return result;
}
private static Collection<String> resolveFromProject( final SourceResolverConfig config,
final MavenProject reactorProject, final Artifact artifact )
{
final List<String> dirs = new ArrayList<>();
if ( config.filter() == null
|| new ArtifactIncludeFilterTransformer().transform( config.filter() ).include( artifact ) )
{
if ( config.includeCompileSources() )
{
final List<String> srcRoots = reactorProject.getCompileSourceRoots();
dirs.addAll( srcRoots );
}
if ( config.includeTestSources() )
{
final List<String> srcRoots = reactorProject.getTestCompileSourceRoots();
dirs.addAll( srcRoots );
}
}
return JavadocUtil.pruneDirs( reactorProject, dirs );
}
private static String key( final String gid, final String aid )
{
return gid + ":" + aid;
}
}