View Javadoc
1   package org.apache.maven.plugin.assembly.archive.task;
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.util.ArrayList;
24  import java.util.Collections;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.model.Dependency;
31  import org.apache.maven.model.Model;
32  import org.apache.maven.plugin.assembly.AssemblerConfigurationSource;
33  import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException;
34  import org.apache.maven.plugin.assembly.archive.ArchiveCreationException;
35  import org.apache.maven.plugin.assembly.format.AssemblyFormattingException;
36  import org.apache.maven.plugin.assembly.model.DependencySet;
37  import org.apache.maven.plugin.assembly.model.FileSet;
38  import org.apache.maven.plugin.assembly.model.UnpackOptions;
39  import org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils;
40  import org.apache.maven.plugin.assembly.utils.FilterUtils;
41  import org.apache.maven.plugin.assembly.utils.TypeConversionUtils;
42  import org.apache.maven.project.MavenProject;
43  import org.apache.maven.project.MavenProjectBuilder;
44  import org.apache.maven.project.ProjectBuildingException;
45  import org.apache.maven.shared.artifact.filter.ScopeArtifactFilter;
46  import org.codehaus.plexus.archiver.Archiver;
47  import org.codehaus.plexus.archiver.ArchiverException;
48  import org.codehaus.plexus.archiver.UnArchiver;
49  import org.codehaus.plexus.archiver.manager.ArchiverManager;
50  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
51  import org.codehaus.plexus.logging.Logger;
52  
53  /**
54   * @version $Id: AddDependencySetsTask.java 1601216 2014-06-08 11:58:09Z khmarbaise $
55   */
56  public class AddDependencySetsTask
57      implements ArchiverTask
58  {
59  
60      private static final List<String> NON_ARCHIVE_DEPENDENCY_TYPES;
61  
62      static
63      {
64          final List<String> nonArch = new ArrayList<String>();
65  
66          nonArch.add( "pom" );
67  
68          NON_ARCHIVE_DEPENDENCY_TYPES = Collections.unmodifiableList( nonArch );
69      }
70  
71      private final List<DependencySet> dependencySets;
72  
73      private final Logger logger;
74  
75      private final MavenProject project;
76  
77      private MavenProject moduleProject;
78  
79      private final MavenProjectBuilder projectBuilder;
80  
81      private String defaultOutputDirectory;
82  
83      private String defaultOutputFileNameMapping;
84  
85      private Artifact moduleArtifact;
86  
87      private final Set<Artifact> resolvedArtifacts;
88  
89      private final ArchiverManager archiverManager;
90  
91      public AddDependencySetsTask( final List<DependencySet> dependencySets, final Set<Artifact> resolvedArtifacts,
92                                    final MavenProject project, final MavenProjectBuilder projectBuilder,
93                                    final ArchiverManager archiverManager, final Logger logger )
94      {
95          this.dependencySets = dependencySets;
96          this.resolvedArtifacts = resolvedArtifacts;
97          this.project = project;
98          this.projectBuilder = projectBuilder;
99          this.archiverManager = archiverManager;
100         this.logger = logger;
101     }
102 
103     public void execute( final Archiver archiver, final AssemblerConfigurationSource configSource )
104         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
105     {
106         if ( ( dependencySets == null ) || dependencySets.isEmpty() )
107         {
108             logger.debug( "No dependency sets specified." );
109             return;
110         }
111 
112         @SuppressWarnings( "unchecked" )
113         final List<Dependency> deps = project.getDependencies();
114         if ( ( deps == null ) || deps.isEmpty() )
115         {
116             logger.debug( "Project " + project.getId() + " has no dependencies. Skipping dependency set addition." );
117         }
118 
119         for (final DependencySet dependencySet : dependencySets) {
120             addDependencySet(dependencySet, archiver, configSource);
121         }
122     }
123 
124     protected void addDependencySet( final DependencySet dependencySet, final Archiver archiver,
125                                      final AssemblerConfigurationSource configSource )
126         throws AssemblyFormattingException, ArchiveCreationException, InvalidAssemblerConfigurationException
127     {
128         logger.debug( "Processing DependencySet (output=" + dependencySet.getOutputDirectory() + ")" );
129 
130         if ( !dependencySet.isUseTransitiveDependencies() && dependencySet.isUseTransitiveFiltering() )
131         {
132             logger.warn( "DependencySet has nonsensical configuration: useTransitiveDependencies == false "
133                             + "AND useTransitiveFiltering == true. Transitive filtering flag will be ignored." );
134         }
135 
136         final Set<Artifact> dependencyArtifacts = resolveDependencyArtifacts( dependencySet );
137 
138         boolean filterContents = false;
139         final UnpackOptions opts = dependencySet.getUnpackOptions();
140         if ( dependencySet.isUnpack() && opts != null && ( opts.isFiltered() || opts.getLineEnding() != null ) )
141         {
142             filterContents = true;
143         }
144         else if ( dependencyArtifacts.size() > 1 )
145         {
146             checkMultiArtifactOutputConfig( dependencySet );
147         }
148 
149         logger.debug( "Adding " + dependencyArtifacts.size() + " dependency artifacts." );
150 
151         for (final Artifact depArtifact : dependencyArtifacts) {
152             MavenProject depProject;
153             try {
154                 depProject =
155                         projectBuilder.buildFromRepository(depArtifact, configSource.getRemoteRepositories(),
156                                 configSource.getLocalRepository());
157             } catch (final ProjectBuildingException e) {
158                 logger.debug("Error retrieving POM of module-dependency: " + depArtifact.getId() + "; Reason: "
159                         + e.getMessage() + "\n\nBuilding stub project instance.");
160 
161                 depProject = buildProjectStub(depArtifact);
162             }
163 
164             if (NON_ARCHIVE_DEPENDENCY_TYPES.contains(depArtifact.getType())) {
165                 addNonArchiveDependency(depArtifact, depProject, dependencySet, archiver, configSource);
166             } else {
167                 if (filterContents) {
168                     addFilteredUnpackedArtifact(dependencySet, depArtifact, depProject, archiver, configSource);
169                 } else {
170                     addNormalArtifact(dependencySet, depArtifact, depProject, archiver, configSource);
171                 }
172             }
173         }
174     }
175 
176     private void checkMultiArtifactOutputConfig( final DependencySet dependencySet )
177     {
178         String dir = dependencySet.getOutputDirectory();
179         if ( dir == null )
180         {
181             dir = defaultOutputDirectory;
182         }
183 
184         String mapping = dependencySet.getOutputFileNameMapping();
185         if ( mapping == null )
186         {
187             mapping = defaultOutputFileNameMapping;
188         }
189 
190         if ( ( dir == null || !dir.contains("${")) && ( mapping == null || !mapping.contains("${")) )
191         {
192             logger.warn( "NOTE: Your assembly specifies a dependencySet that matches multiple artifacts, but specifies a concrete output format. "
193                             + "THIS MAY RESULT IN ONE OR MORE ARTIFACTS BEING OBSCURED!\n\nOutput directory: '"
194                             + dir
195                             + "'\nOutput filename mapping: '" + mapping + "'" );
196         }
197     }
198 
199     private void addFilteredUnpackedArtifact( final DependencySet dependencySet, final Artifact depArtifact,
200                                               final MavenProject depProject, final Archiver archiver,
201                                               final AssemblerConfigurationSource configSource )
202         throws ArchiveCreationException, AssemblyFormattingException
203     {
204         logger.debug( "Adding dependency artifact " + depArtifact.getId() + " after filtering the unpacked contents." );
205 
206         final StringBuilder sb =
207             new StringBuilder().append( depArtifact.getGroupId() )
208                                .append( "_" )
209                                .append( depArtifact.getArtifactId() )
210                                .append( "_" )
211                                .append( depArtifact.getVersion() );
212 
213         final String classifier = depArtifact.getClassifier();
214         if ( classifier != null )
215         {
216             sb.append( "_" ).append( classifier );
217         }
218 
219         sb.append( "." ).append( depArtifact.getType() );
220 
221         final File dir = new File( configSource.getWorkingDirectory(), sb.toString() );
222         if ( dir.exists() )
223         {
224             logger.debug( "NOT unpacking: " + depArtifact.getId() + ". Directory already exists in workdir:\n\t"
225                             + dir.getAbsolutePath() );
226         }
227         else
228         {
229             dir.mkdirs();
230 
231             UnArchiver unarchiver;
232             try
233             {
234                 unarchiver = archiverManager.getUnArchiver( depArtifact.getFile() );
235             }
236             catch ( final NoSuchArchiverException e )
237             {
238                 throw new ArchiveCreationException( "Failed to retrieve un-archiver for: " + depArtifact.getId()
239                                 + ". Dependency filtering cannot proceed.", e );
240             }
241 
242             unarchiver.setDestDirectory( dir );
243             unarchiver.setOverwrite( true );
244             unarchiver.setSourceFile( depArtifact.getFile() );
245             unarchiver.setIgnorePermissions( configSource.isIgnorePermissions() );
246 
247             try
248             {
249                 unarchiver.extract();
250             }
251             catch ( final ArchiverException e )
252             {
253                 throw new ArchiveCreationException( "Failed to unpack dependency archive: " + depArtifact.getId()
254                                 + ". Dependency filtering cannot proceed.", e );
255             }
256         }
257 
258         final UnpackOptions opts = dependencySet.getUnpackOptions();
259 
260         final FileSet fs = new FileSet();
261         fs.setDirectory( dir.getAbsolutePath() );
262         fs.setDirectoryMode( dependencySet.getDirectoryMode() );
263         fs.setExcludes( opts.getExcludes() );
264         fs.setFileMode( dependencySet.getFileMode() );
265         fs.setFiltered( opts.isFiltered() );
266         fs.setIncludes( opts.getIncludes() );
267 
268         String outDir = dependencySet.getOutputDirectory();
269         if ( outDir == null )
270         {
271             outDir = defaultOutputDirectory;
272         }
273 
274         String filenameMapping = dependencySet.getOutputFileNameMapping();
275         if ( filenameMapping == null )
276         {
277             filenameMapping = defaultOutputFileNameMapping;
278         }
279 
280         filenameMapping =
281             AssemblyFormatUtils.evaluateFileNameMapping( filenameMapping, depArtifact, configSource.getProject(),
282                                                          moduleProject, moduleArtifact, depProject, configSource );
283 
284         final String outputLocation = new File( outDir, filenameMapping ).getPath();
285 
286         fs.setOutputDirectory( outputLocation );
287 
288         fs.setLineEnding( opts.getLineEnding() );
289         fs.setUseDefaultExcludes( opts.isUseDefaultExcludes() );
290 
291         final AddFileSetsTask task = new AddFileSetsTask( fs );
292         task.setProject( depProject );
293         task.setModuleProject( moduleProject );
294         task.setLogger( logger );
295 
296         task.execute( archiver, configSource );
297     }
298 
299     private void addNormalArtifact( final DependencySet dependencySet, final Artifact depArtifact,
300                                     final MavenProject depProject, final Archiver archiver,
301                                     final AssemblerConfigurationSource configSource )
302         throws AssemblyFormattingException, ArchiveCreationException
303     {
304         logger.debug( "Adding dependency artifact " + depArtifact.getId() + "." );
305 
306         final AddArtifactTask task = new AddArtifactTask( depArtifact, logger );
307 
308         task.setProject( depProject );
309         task.setModuleProject( moduleProject );
310         task.setModuleArtifact( moduleArtifact );
311         task.setOutputDirectory( dependencySet.getOutputDirectory(), defaultOutputDirectory );
312         task.setFileNameMapping( dependencySet.getOutputFileNameMapping(), defaultOutputFileNameMapping );
313 
314         final int dirMode = TypeConversionUtils.modeToInt( dependencySet.getDirectoryMode(), logger );
315         if ( dirMode != -1 )
316         {
317             task.setDirectoryMode( dirMode );
318         }
319 
320         final int fileMode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
321         if ( fileMode != -1 )
322         {
323             task.setFileMode( fileMode );
324         }
325 
326         task.setUnpack( dependencySet.isUnpack() );
327 
328         final UnpackOptions opts = dependencySet.getUnpackOptions();
329         if ( dependencySet.isUnpack() && ( opts != null ) )
330         {
331             task.setIncludes( opts.getIncludes() );
332             task.setExcludes( opts.getExcludes() );
333         }
334 
335         task.execute( archiver, configSource );
336     }
337 
338     private MavenProject buildProjectStub( final Artifact depArtifact )
339     {
340         final Model model = new Model();
341         model.setGroupId( depArtifact.getGroupId() );
342         model.setArtifactId( depArtifact.getArtifactId() );
343         model.setVersion( depArtifact.getBaseVersion() );
344         model.setPackaging( depArtifact.getType() );
345 
346         model.setDescription( "Stub for " + depArtifact.getId() );
347 
348         final MavenProject project = new MavenProject( model );
349         project.setArtifact( depArtifact );
350 
351         return project;
352     }
353 
354     protected Set<Artifact> resolveDependencyArtifacts( final DependencySet dependencySet )
355         throws InvalidAssemblerConfigurationException
356     {
357         final Set<Artifact> dependencyArtifacts = new LinkedHashSet<Artifact>();
358         if ( resolvedArtifacts != null )
359         {
360             dependencyArtifacts.addAll( resolvedArtifacts );
361         }
362 
363         if ( dependencySet.isUseProjectArtifact() )
364         {
365             final Artifact projectArtifact = project.getArtifact();
366             if ( ( projectArtifact != null ) && ( projectArtifact.getFile() != null ) )
367             {
368                 dependencyArtifacts.add( projectArtifact );
369             }
370             else
371             {
372                 logger.warn( "Cannot include project artifact: " + projectArtifact
373                                 + "; it doesn't have an associated file or directory." );
374             }
375         }
376 
377         if ( dependencySet.isUseProjectAttachments() )
378         {
379             @SuppressWarnings( "unchecked" )
380             final List<Artifact> attachments = project.getAttachedArtifacts();
381             if ( attachments != null )
382             {
383                 for (final Artifact attachment : attachments) {
384                     if (attachment.getFile() != null) {
385                         dependencyArtifacts.add(attachment);
386                     } else {
387                         logger.warn("Cannot include attached artifact: " + project.getId() + " for project: "
388                                 + project.getId() + "; it doesn't have an associated file or directory.");
389                     }
390                 }
391             }
392         }
393 
394         if ( dependencySet.isUseTransitiveFiltering() )
395         {
396             logger.debug( "Filtering dependency artifacts USING transitive dependency path information." );
397         }
398         else
399         {
400             logger.debug( "Filtering dependency artifacts WITHOUT transitive dependency path information." );
401         }
402 
403         final ScopeArtifactFilter filter = new ScopeArtifactFilter( dependencySet.getScope() );
404 
405         FilterUtils.filterArtifacts( dependencyArtifacts, dependencySet.getIncludes(), dependencySet.getExcludes(),
406                                      dependencySet.isUseStrictFiltering(), dependencySet.isUseTransitiveFiltering(),
407                                      logger, filter );
408 
409         return dependencyArtifacts;
410     }
411 
412     protected void addNonArchiveDependency( final Artifact depArtifact, final MavenProject depProject,
413                                             final DependencySet dependencySet, final Archiver archiver,
414                                             final AssemblerConfigurationSource configSource )
415         throws AssemblyFormattingException, ArchiveCreationException
416     {
417         final File source = depArtifact.getFile();
418 
419         String outputDirectory = dependencySet.getOutputDirectory();
420 
421         outputDirectory =
422             AssemblyFormatUtils.getOutputDirectory( outputDirectory, configSource.getProject(), moduleProject,
423                                                     depProject, depProject.getBuild().getFinalName(), configSource );
424 
425         final String destName =
426             AssemblyFormatUtils.evaluateFileNameMapping( dependencySet.getOutputFileNameMapping(), depArtifact,
427                                                          configSource.getProject(), moduleProject, moduleArtifact,
428                                                          depProject, configSource );
429 
430         String target;
431 
432         // omit the last char if ends with / or \\
433         if ( outputDirectory.endsWith( "/" ) || outputDirectory.endsWith( "\\" ) )
434         {
435             target = outputDirectory + destName;
436         }
437         else
438         {
439             target = outputDirectory + "/" + destName;
440         }
441 
442         try
443         {
444             final int mode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
445             if ( mode > -1 )
446             {
447                 archiver.addFile( source, target, mode );
448             }
449             else
450             {
451                 archiver.addFile( source, target );
452             }
453         }
454         catch ( final ArchiverException e )
455         {
456             throw new ArchiveCreationException( "Error adding file to archive: " + e.getMessage(), e );
457         }
458     }
459 
460     public List<DependencySet> getDependencySets()
461     {
462         return dependencySets;
463     }
464 
465     public Logger getLogger()
466     {
467         return logger;
468     }
469 
470     public String getDefaultOutputDirectory()
471     {
472         return defaultOutputDirectory;
473     }
474 
475     public void setDefaultOutputDirectory( final String defaultOutputDirectory )
476     {
477         this.defaultOutputDirectory = defaultOutputDirectory;
478     }
479 
480     public String getDefaultOutputFileNameMapping()
481     {
482         return defaultOutputFileNameMapping;
483     }
484 
485     public void setDefaultOutputFileNameMapping( final String defaultOutputFileNameMapping )
486     {
487         this.defaultOutputFileNameMapping = defaultOutputFileNameMapping;
488     }
489 
490     public MavenProject getModuleProject()
491     {
492         return moduleProject;
493     }
494 
495     public void setModuleProject( final MavenProject moduleProject )
496     {
497         this.moduleProject = moduleProject;
498     }
499 
500     public void setModuleArtifact( final Artifact moduleArtifact )
501     {
502         this.moduleArtifact = moduleArtifact;
503     }
504 
505     public Artifact getModuleArtifact()
506     {
507         return moduleArtifact;
508     }
509 }