blob: ffbd0b4645a007b3901737ea8288e079a9fa70db [file] [log] [blame]
/*
* 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.
*/
package org.apache.maven.plugins.javadoc.resolver;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.plugins.javadoc.AbstractJavadocMojo;
import org.apache.maven.plugins.javadoc.JavadocModule;
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.artifact.filter.resolve.transform.EclipseAetherFilterTransformer;
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.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.graph.DefaultDependencyNode;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResult;
/**
*
*/
@Named
@Singleton
public final class ResourceResolver extends AbstractLogEnabled {
@Inject
private RepositorySystem repoSystem;
@Inject
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 Collection<JavadocModule> resolveDependencySourcePaths(final SourceResolverConfig config)
throws ArtifactResolutionException, ArtifactNotFoundException {
final Collection<JavadocModule> mappedDirs = 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();
for (final Artifact artifact : artifacts) {
final String key = key(artifact.getGroupId(), artifact.getArtifactId());
final MavenProject p = projectMap.get(key);
if (p != null) {
mappedDirs.add(new JavadocModule(key, artifact.getFile(), resolveFromProject(config, p, artifact)));
} else {
JavadocModule m = resolveFromArtifact(config, artifact);
if (m != null) {
mappedDirs.add(m);
}
}
}
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;
}
try (FileInputStream stream = new FileInputStream(optionsFile)) {
JavadocOptions options = new JavadocOptionsXpp3Reader().read(stream);
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;
}
}
return bundles;
}
private List<JavadocBundle> resolveBundlesFromArtifacts(
final SourceResolverConfig config, final List<Artifact> artifacts) throws IOException {
final List<org.eclipse.aether.artifact.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));
}
}
Collection<Path> dirs = new ArrayList<>(toResolve.size());
try {
dirs = resolveAndUnpack(toResolve, config, RESOURCE_VALID_CLASSIFIERS, false);
} catch (ArtifactResolutionException | ArtifactNotFoundException e) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(e.getMessage(), e);
}
}
List<JavadocBundle> result = new ArrayList<>();
for (Path d : dirs) {
File dir = d.toFile();
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 JavadocModule resolveFromArtifact(final SourceResolverConfig config, final Artifact artifact)
throws ArtifactResolutionException, ArtifactNotFoundException {
final List<org.eclipse.aether.artifact.Artifact> toResolve = new ArrayList<>(2);
if (config.filter() != null
&& !new ArtifactIncludeFilterTransformer()
.transform(config.filter())
.include(artifact)) {
return null;
}
if (config.includeCompileSources()) {
toResolve.add(createResourceArtifact(artifact, SOURCES_CLASSIFIER, config));
}
if (config.includeTestSources()) {
toResolve.add(createResourceArtifact(artifact, TEST_SOURCES_CLASSIFIER, config));
}
Collection<Path> sourcePaths = resolveAndUnpack(toResolve, config, SOURCE_VALID_CLASSIFIERS, true);
return new JavadocModule(key(artifact.getGroupId(), artifact.getArtifactId()), artifact.getFile(), sourcePaths);
}
private org.eclipse.aether.artifact.Artifact createResourceArtifact(
final Artifact artifact, final String classifier, final SourceResolverConfig config) {
return new org.eclipse.aether.artifact.DefaultArtifact(
artifact.getGroupId(), artifact.getArtifactId(), classifier, "jar", artifact.getVersion());
}
/**
*
* @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<Path> resolveAndUnpack(
final List<org.eclipse.aether.artifact.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<org.eclipse.aether.artifact.Artifact> artifactSet = new LinkedHashSet<>(artifacts);
final DependencyFilter filter;
if (config.filter() != null) {
filter = new EclipseAetherFilterTransformer().transform(config.filter());
} else {
filter = null;
}
final List<Path> result = new ArrayList<>(artifacts.size());
for (final org.eclipse.aether.artifact.Artifact a : artifactSet) {
if (!validClassifiers.contains(a.getClassifier())
|| (filter != null && !filter.accept(new DefaultDependencyNode(a), Collections.emptyList()))) {
continue;
}
Artifact resolvedArtifact;
ArtifactRequest req = new ArtifactRequest(a, config.project().getRemoteProjectRepositories(), null);
try {
RepositorySystemSession repoSession =
config.getBuildingRequest().getRepositorySession();
ArtifactResult resolutionResult = repoSystem.resolveArtifact(repoSession, req);
resolvedArtifact = RepositoryUtils.toArtifact(resolutionResult.getArtifact());
} catch (org.eclipse.aether.resolution.ArtifactResolutionException e) {
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.getExtension());
unArchiver.setDestDirectory(d);
unArchiver.setSourceFile(resolvedArtifact.getFile());
unArchiver.extract();
result.add(d.toPath().toAbsolutePath());
} catch (final NoSuchArchiverException e) {
if (propagateErrors) {
throw new ArtifactResolutionException(
"Failed to retrieve valid un-archiver component: " + a.getExtension(),
RepositoryUtils.toArtifact(a),
e);
}
} catch (final ArchiverException e) {
if (propagateErrors) {
throw new ArtifactResolutionException("Failed to unpack: " + a, RepositoryUtils.toArtifact(a), e);
}
}
}
return result;
}
private static Collection<Path> 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;
}
}