1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
79
80 public static final String SOURCES_CLASSIFIER = "sources";
81
82
83
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
96
97
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
129
130
131
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
284
285
286
287
288
289
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
298
299
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 }