View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.javadoc.resolver;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.IOException;
28  import java.nio.file.Path;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.LinkedHashSet;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.apache.maven.RepositoryUtils;
40  import org.apache.maven.artifact.Artifact;
41  import org.apache.maven.artifact.ArtifactUtils;
42  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
43  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
44  import org.apache.maven.plugins.javadoc.AbstractJavadocMojo;
45  import org.apache.maven.plugins.javadoc.JavadocModule;
46  import org.apache.maven.plugins.javadoc.JavadocUtil;
47  import org.apache.maven.plugins.javadoc.ResourcesBundleMojo;
48  import org.apache.maven.plugins.javadoc.options.JavadocOptions;
49  import org.apache.maven.plugins.javadoc.options.io.xpp3.JavadocOptionsXpp3Reader;
50  import org.apache.maven.project.MavenProject;
51  import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
52  import org.apache.maven.shared.artifact.filter.resolve.transform.EclipseAetherFilterTransformer;
53  import org.codehaus.plexus.archiver.ArchiverException;
54  import org.codehaus.plexus.archiver.UnArchiver;
55  import org.codehaus.plexus.archiver.manager.ArchiverManager;
56  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
57  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
58  import org.eclipse.aether.RepositorySystem;
59  import org.eclipse.aether.RepositorySystemSession;
60  import org.eclipse.aether.graph.DefaultDependencyNode;
61  import org.eclipse.aether.graph.DependencyFilter;
62  import org.eclipse.aether.resolution.ArtifactRequest;
63  import org.eclipse.aether.resolution.ArtifactResult;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  /**
68   *
69   */
70  @Named
71  @Singleton
72  public final class ResourceResolver {
73      private static final Logger LOGGER = LoggerFactory.getLogger(ResourceResolver.class);
74  
75      @Inject
76      private RepositorySystem repoSystem;
77  
78      @Inject
79      private ArchiverManager archiverManager;
80  
81      /**
82       * The classifier for sources.
83       */
84      public static final String SOURCES_CLASSIFIER = "sources";
85  
86      /**
87       * The classifier for test sources
88       */
89      public static final String TEST_SOURCES_CLASSIFIER = "test-sources";
90  
91      private static final List<String> SOURCE_VALID_CLASSIFIERS =
92              Arrays.asList(SOURCES_CLASSIFIER, TEST_SOURCES_CLASSIFIER);
93  
94      private static final List<String> RESOURCE_VALID_CLASSIFIERS = Arrays.asList(
95              AbstractJavadocMojo.JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER,
96              AbstractJavadocMojo.TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER);
97  
98      /**
99       * @param config {@link SourceResolverConfig}
100      * @return list of {@link JavadocBundle}.
101      * @throws IOException {@link IOException}
102      */
103     public List<JavadocBundle> resolveDependencyJavadocBundles(final SourceResolverConfig config) throws IOException {
104         final List<JavadocBundle> bundles = new ArrayList<>();
105 
106         final Map<String, MavenProject> projectMap = new HashMap<>();
107         if (config.reactorProjects() != null) {
108             for (final MavenProject p : config.reactorProjects()) {
109                 projectMap.put(ArtifactUtils.key(p.getGroupId(), p.getArtifactId(), p.getVersion()), p);
110             }
111         }
112 
113         final List<Artifact> artifacts = config.project().getTestArtifacts();
114 
115         final List<Artifact> forResourceResolution = new ArrayList<>(artifacts.size());
116         for (final Artifact artifact : artifacts) {
117             final MavenProject p = projectMap.get(ArtifactUtils.key(artifact));
118             if (p != null) {
119                 bundles.addAll(resolveBundleFromProject(config, p, artifact));
120             } else {
121                 forResourceResolution.add(artifact);
122             }
123         }
124 
125         bundles.addAll(resolveBundlesFromArtifacts(config, forResourceResolution));
126 
127         return bundles;
128     }
129 
130     /**
131      * @param config {@link SourceResolverConfig}
132      * @return The list of resolved dependencies.
133      * @throws ArtifactResolutionException {@link ArtifactResolutionException}
134      * @throws ArtifactNotFoundException {@link ArtifactNotFoundException}
135      */
136     public Collection<JavadocModule> resolveDependencySourcePaths(final SourceResolverConfig config)
137             throws ArtifactResolutionException, ArtifactNotFoundException {
138         final Collection<JavadocModule> mappedDirs = new ArrayList<>();
139 
140         final Map<String, MavenProject> projectMap = new HashMap<>();
141         if (config.reactorProjects() != null) {
142             for (final MavenProject p : config.reactorProjects()) {
143                 projectMap.put(ArtifactUtils.key(p.getGroupId(), p.getArtifactId(), p.getVersion()), p);
144             }
145         }
146 
147         final List<Artifact> artifacts = config.project().getTestArtifacts();
148 
149         for (final Artifact artifact : artifacts) {
150             final String key = ArtifactUtils.key(artifact);
151             final MavenProject p = projectMap.get(key);
152             if (p != null) {
153                 mappedDirs.add(new JavadocModule(key, artifact.getFile(), resolveFromProject(config, p, artifact)));
154             } else {
155                 JavadocModule m = resolveFromArtifact(config, artifact);
156                 if (m != null) {
157                     mappedDirs.add(m);
158                 }
159             }
160         }
161 
162         return mappedDirs;
163     }
164 
165     private static List<JavadocBundle> resolveBundleFromProject(
166             SourceResolverConfig config, MavenProject project, Artifact artifact) throws IOException {
167         List<JavadocBundle> bundles = new ArrayList<>();
168 
169         List<String> classifiers = new ArrayList<>();
170         if (config.includeCompileSources()) {
171             classifiers.add(AbstractJavadocMojo.JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER);
172         }
173 
174         if (config.includeTestSources()) {
175             classifiers.add(AbstractJavadocMojo.TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER);
176         }
177 
178         for (String classifier : classifiers) {
179             File optionsFile = new File(
180                     project.getBuild().getDirectory(), "javadoc-bundle-options/javadoc-options-" + classifier + ".xml");
181             if (!optionsFile.exists()) {
182                 continue;
183             }
184 
185             try (FileInputStream stream = new FileInputStream(optionsFile)) {
186                 JavadocOptions options = new JavadocOptionsXpp3Reader().read(stream);
187                 bundles.add(new JavadocBundle(
188                         options, new File(project.getBasedir(), options.getJavadocResourcesDirectory())));
189             } catch (XmlPullParserException e) {
190                 IOException error = new IOException(
191                         "Failed to read javadoc options from: " + optionsFile + "\nReason: " + e.getMessage(), e);
192                 throw error;
193             }
194         }
195 
196         return bundles;
197     }
198 
199     private List<JavadocBundle> resolveBundlesFromArtifacts(
200             final SourceResolverConfig config, final List<Artifact> artifacts) throws IOException {
201         final List<org.eclipse.aether.artifact.Artifact> toResolve = new ArrayList<>(artifacts.size());
202 
203         for (final Artifact artifact : artifacts) {
204             if (config.filter() != null
205                     && !new ArtifactIncludeFilterTransformer()
206                             .transform(config.filter())
207                             .include(artifact)) {
208                 continue;
209             }
210 
211             if (config.includeCompileSources()) {
212                 toResolve.add(createResourceArtifact(
213                         artifact, AbstractJavadocMojo.JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER, config));
214             }
215 
216             if (config.includeTestSources()) {
217                 toResolve.add(createResourceArtifact(
218                         artifact, AbstractJavadocMojo.TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER, config));
219             }
220         }
221 
222         Collection<Path> dirs = new ArrayList<>(toResolve.size());
223         try {
224             dirs = resolveAndUnpack(toResolve, config, RESOURCE_VALID_CLASSIFIERS, false);
225         } catch (ArtifactResolutionException | ArtifactNotFoundException e) {
226             if (LOGGER.isDebugEnabled()) {
227                 LOGGER.debug(e.getMessage(), e);
228             }
229         }
230 
231         List<JavadocBundle> result = new ArrayList<>();
232 
233         for (Path d : dirs) {
234             File dir = d.toFile();
235             File resources = new File(dir, ResourcesBundleMojo.RESOURCES_DIR_PATH);
236             JavadocOptions options = null;
237 
238             File javadocOptions = new File(dir, ResourcesBundleMojo.BUNDLE_OPTIONS_PATH);
239             if (javadocOptions.exists()) {
240                 try (FileInputStream reader = new FileInputStream(javadocOptions)) {
241                     options = new JavadocOptionsXpp3Reader().read(reader);
242                 } catch (XmlPullParserException e) {
243                     IOException error = new IOException("Failed to parse javadoc options: " + e.getMessage(), e);
244                     throw error;
245                 }
246             }
247 
248             result.add(new JavadocBundle(options, resources));
249         }
250 
251         return result;
252     }
253 
254     private JavadocModule resolveFromArtifact(final SourceResolverConfig config, final Artifact artifact)
255             throws ArtifactResolutionException, ArtifactNotFoundException {
256         final List<org.eclipse.aether.artifact.Artifact> toResolve = new ArrayList<>(2);
257 
258         if (config.filter() != null
259                 && !new ArtifactIncludeFilterTransformer()
260                         .transform(config.filter())
261                         .include(artifact)) {
262             return null;
263         }
264 
265         if (config.includeCompileSources()) {
266             toResolve.add(createResourceArtifact(artifact, SOURCES_CLASSIFIER, config));
267         }
268 
269         if (config.includeTestSources()) {
270             toResolve.add(createResourceArtifact(artifact, TEST_SOURCES_CLASSIFIER, config));
271         }
272 
273         Collection<Path> sourcePaths = resolveAndUnpack(toResolve, config, SOURCE_VALID_CLASSIFIERS, true);
274 
275         return new JavadocModule(ArtifactUtils.key(artifact), artifact.getFile(), sourcePaths);
276     }
277 
278     private org.eclipse.aether.artifact.Artifact createResourceArtifact(
279             final Artifact artifact, final String classifier, final SourceResolverConfig config) {
280         return new org.eclipse.aether.artifact.DefaultArtifact(
281                 artifact.getGroupId(), artifact.getArtifactId(), classifier, "jar", artifact.getVersion());
282     }
283 
284     /**
285      *
286      * @param artifacts the artifacts to resolve
287      * @param config the configuration
288      * @param validClassifiers
289      * @param propagateErrors
290      * @return list of <dependencyConflictId, absolutePath>
291      * @throws ArtifactResolutionException if an exception occurs
292      * @throws ArtifactNotFoundException if an exception occurs
293      */
294     private Collection<Path> resolveAndUnpack(
295             final List<org.eclipse.aether.artifact.Artifact> artifacts,
296             final SourceResolverConfig config,
297             final List<String> validClassifiers,
298             final boolean propagateErrors)
299             throws ArtifactResolutionException, ArtifactNotFoundException {
300         // NOTE: Since these are '-sources' and '-test-sources' artifacts, they won't actually
301         // resolve transitively...this is just used to aggregate resolution failures into a single
302         // exception.
303         final Set<org.eclipse.aether.artifact.Artifact> artifactSet = new LinkedHashSet<>(artifacts);
304 
305         final DependencyFilter filter;
306         if (config.filter() != null) {
307             filter = new EclipseAetherFilterTransformer().transform(config.filter());
308         } else {
309             filter = null;
310         }
311 
312         final List<Path> result = new ArrayList<>(artifacts.size());
313         for (final org.eclipse.aether.artifact.Artifact a : artifactSet) {
314             if (!validClassifiers.contains(a.getClassifier())
315                     || (filter != null && !filter.accept(new DefaultDependencyNode(a), Collections.emptyList()))) {
316                 continue;
317             }
318 
319             Artifact resolvedArtifact;
320             ArtifactRequest req = new ArtifactRequest(a, config.project().getRemoteProjectRepositories(), null);
321             try {
322                 RepositorySystemSession repoSession =
323                         config.getBuildingRequest().getRepositorySession();
324                 ArtifactResult resolutionResult = repoSystem.resolveArtifact(repoSession, req);
325                 resolvedArtifact = RepositoryUtils.toArtifact(resolutionResult.getArtifact());
326             } catch (org.eclipse.aether.resolution.ArtifactResolutionException e) {
327                 continue;
328             }
329             final File d = new File(
330                     config.outputBasedir(), a.getArtifactId() + "-" + a.getVersion() + "-" + a.getClassifier());
331 
332             if (!d.exists()) {
333                 d.mkdirs();
334             }
335 
336             try {
337                 final UnArchiver unArchiver = archiverManager.getUnArchiver(a.getExtension());
338 
339                 unArchiver.setDestDirectory(d);
340                 unArchiver.setSourceFile(resolvedArtifact.getFile());
341 
342                 unArchiver.extract();
343 
344                 result.add(d.toPath().toAbsolutePath());
345             } catch (final NoSuchArchiverException e) {
346                 if (propagateErrors) {
347                     throw new ArtifactResolutionException(
348                             "Failed to retrieve valid un-archiver component: " + a.getExtension(),
349                             RepositoryUtils.toArtifact(a),
350                             e);
351                 }
352             } catch (final ArchiverException e) {
353                 if (propagateErrors) {
354                     throw new ArtifactResolutionException("Failed to unpack: " + a, RepositoryUtils.toArtifact(a), e);
355                 }
356             }
357         }
358 
359         return result;
360     }
361 
362     private static Collection<Path> resolveFromProject(
363             final SourceResolverConfig config, final MavenProject reactorProject, final Artifact artifact) {
364         final List<String> dirs = new ArrayList<>();
365 
366         if (config.filter() == null
367                 || new ArtifactIncludeFilterTransformer()
368                         .transform(config.filter())
369                         .include(artifact)) {
370             if (config.includeCompileSources()) {
371                 final List<String> srcRoots = reactorProject.getCompileSourceRoots();
372                 dirs.addAll(srcRoots);
373             }
374 
375             if (config.includeTestSources()) {
376                 final List<String> srcRoots = reactorProject.getTestCompileSourceRoots();
377                 dirs.addAll(srcRoots);
378             }
379         }
380 
381         return JavadocUtil.pruneDirs(reactorProject, dirs);
382     }
383 }