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