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