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