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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.model.Dependency;
24  import org.apache.maven.model.Model;
25  import org.apache.maven.plugin.assembly.AssemblerConfigurationSource;
26  import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException;
27  import org.apache.maven.plugin.assembly.archive.ArchiveCreationException;
28  import org.apache.maven.plugin.assembly.format.AssemblyFormattingException;
29  import org.apache.maven.plugin.assembly.format.ReaderFormatter;
30  import org.apache.maven.plugin.assembly.model.DependencySet;
31  import org.apache.maven.plugin.assembly.model.UnpackOptions;
32  import org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils;
33  import org.apache.maven.plugin.assembly.utils.FilterUtils;
34  import org.apache.maven.plugin.assembly.utils.TypeConversionUtils;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.project.MavenProjectBuilder;
37  import org.apache.maven.project.ProjectBuildingException;
38  import org.apache.maven.shared.artifact.filter.ScopeArtifactFilter;
39  import org.codehaus.plexus.archiver.Archiver;
40  import org.codehaus.plexus.archiver.ArchiverException;
41  import org.codehaus.plexus.components.io.functions.InputStreamTransformer;
42  import org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator;
43  import org.codehaus.plexus.logging.Logger;
44  
45  import java.io.File;
46  import java.nio.charset.Charset;
47  import java.util.ArrayList;
48  import java.util.Collections;
49  import java.util.LinkedHashSet;
50  import java.util.List;
51  import java.util.Set;
52  
53  import static org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils.artifactProjectInterpolator;
54  import static org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils.moduleProjectInterpolator;
55  
56  /**
57   * @version $Id: AddDependencySetsTask.java 1674674 2015-04-19 19:14:00Z krosenvold $
58   */
59  public class AddDependencySetsTask
60  {
61  
62      private static final List<String> NON_ARCHIVE_DEPENDENCY_TYPES;
63  
64      static
65      {
66          final List<String> nonArch = new ArrayList<String>();
67  
68          nonArch.add( "pom" );
69  
70          NON_ARCHIVE_DEPENDENCY_TYPES = Collections.unmodifiableList( nonArch );
71      }
72  
73      private final List<DependencySet> dependencySets;
74  
75      private final Logger logger;
76  
77      private final MavenProject project;
78  
79      private MavenProject moduleProject;
80  
81      private final MavenProjectBuilder projectBuilder;
82  
83      private String defaultOutputDirectory;
84  
85      private String defaultOutputFileNameMapping;
86  
87      private Artifact moduleArtifact;
88  
89      private final Set<Artifact> resolvedArtifacts;
90  
91  
92      public AddDependencySetsTask( final List<DependencySet> dependencySets, final Set<Artifact> resolvedArtifacts,
93                                    final MavenProject project, final MavenProjectBuilder projectBuilder,
94                                    final Logger logger )
95      {
96          this.dependencySets = dependencySets;
97          this.resolvedArtifacts = resolvedArtifacts;
98          this.project = project;
99          this.projectBuilder = projectBuilder;
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" ) final List<Dependency> deps = project.getDependencies();
113         if ( ( deps == null ) || deps.isEmpty() )
114         {
115             logger.debug( "Project " + project.getId() + " has no dependencies. Skipping dependency set addition." );
116         }
117 
118         for ( final DependencySet dependencySet : dependencySets )
119         {
120             addDependencySet( dependencySet, archiver, configSource );
121         }
122     }
123 
124     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         if ( !unpackTransformsContent( dependencySet ) && dependencyArtifacts.size() > 1 )
139         {
140             checkMultiArtifactOutputConfig( dependencySet );
141         }
142 
143         logger.debug( "Adding " + dependencyArtifacts.size() + " dependency artifacts." );
144 
145         InputStreamTransformer fileSetTransformers =
146             isUnpackWithOptions( dependencySet )
147                 ? ReaderFormatter.getFileSetTransformers( configSource, dependencySet.getUnpackOptions().isFiltered(),
148                                                           dependencySet.getUnpackOptions().getLineEnding() )
149                 : null;
150 
151         for ( final Artifact depArtifact : dependencyArtifacts )
152         {
153             MavenProject depProject;
154             try
155             {
156                 depProject = projectBuilder.buildFromRepository( depArtifact, configSource.getRemoteRepositories(),
157                                                                  configSource.getLocalRepository() );
158             }
159             catch ( final ProjectBuildingException e )
160             {
161                 logger.debug(
162                     "Error retrieving POM of module-dependency: " + depArtifact.getId() + "; Reason: " + e.getMessage()
163                         + "\n\nBuilding stub project instance." );
164 
165                 depProject = buildProjectStub( depArtifact );
166             }
167 
168             if ( NON_ARCHIVE_DEPENDENCY_TYPES.contains( depArtifact.getType() ) )
169             {
170                 addNonArchiveDependency( depArtifact, depProject, dependencySet, archiver, configSource );
171             }
172             else
173             {
174                 addNormalArtifact( dependencySet, depArtifact, depProject, archiver, configSource,
175                                    fileSetTransformers );
176             }
177         }
178     }
179 
180     private boolean isUnpackWithOptions( DependencySet dependencySet )
181     {
182         return dependencySet.isUnpack() && dependencySet.getUnpackOptions() != null;
183     }
184 
185     private boolean unpackTransformsContent( DependencySet dependencySet )
186     {
187         return isUnpackWithOptions( dependencySet ) && isConentModifyingOption( dependencySet.getUnpackOptions() );
188     }
189 
190     private boolean isConentModifyingOption( UnpackOptions opts )
191     {
192         return ( opts.isFiltered() || opts.getLineEnding() != null );
193     }
194 
195     private void checkMultiArtifactOutputConfig( final DependencySet dependencySet )
196     {
197         String dir = dependencySet.getOutputDirectory();
198         if ( dir == null )
199         {
200             dir = defaultOutputDirectory;
201         }
202 
203         String mapping = dependencySet.getOutputFileNameMapping();
204         if ( mapping == null )
205         {
206             mapping = defaultOutputFileNameMapping;
207         }
208 
209         if ( ( dir == null || !dir.contains( "${" ) ) && ( mapping == null || !mapping.contains( "${" ) ) )
210         {
211             logger.warn( "NOTE: Your assembly specifies a dependencySet that matches multiple artifacts, but "
212                              + "specifies a concrete output format. THIS MAY RESULT IN ONE OR MORE ARTIFACTS BEING "
213                              + "OBSCURED!\n\n" + "Output directory: '" + dir + "'\nOutput filename mapping: '" + mapping
214                              + "'" );
215         }
216     }
217 
218     private void addNormalArtifact( final DependencySet dependencySet, final Artifact depArtifact,
219                                     final MavenProject depProject, final Archiver archiver,
220                                     final AssemblerConfigurationSource configSource,
221                                     InputStreamTransformer fileSetTransformers )
222         throws AssemblyFormattingException, ArchiveCreationException
223     {
224         logger.debug( "Adding dependency artifact " + depArtifact.getId() + "." );
225 
226         String encoding = isUnpackWithOptions( dependencySet ) ? dependencySet.getUnpackOptions().getEncoding() : null;
227         Charset charset = encoding != null ? Charset.forName( encoding ) : null;
228         final AddArtifactTask task = new AddArtifactTask( depArtifact, logger, fileSetTransformers, charset );
229 
230         task.setProject( depProject );
231         task.setModuleProject( moduleProject );
232         task.setModuleArtifact( moduleArtifact );
233         task.setOutputDirectory( dependencySet.getOutputDirectory(), defaultOutputDirectory );
234         task.setFileNameMapping( dependencySet.getOutputFileNameMapping(), defaultOutputFileNameMapping );
235 
236         final int dirMode = TypeConversionUtils.modeToInt( dependencySet.getDirectoryMode(), logger );
237         if ( dirMode != -1 )
238         {
239             task.setDirectoryMode( dirMode );
240         }
241 
242         final int fileMode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
243         if ( fileMode != -1 )
244         {
245             task.setFileMode( fileMode );
246         }
247 
248         task.setUnpack( dependencySet.isUnpack() );
249 
250         final UnpackOptions opts = dependencySet.getUnpackOptions();
251         if ( isUnpackWithOptions( dependencySet ) )
252         {
253             task.setIncludes( opts.getIncludes() );
254             task.setExcludes( opts.getExcludes() );
255         }
256 
257         task.execute( archiver, configSource );
258 
259     }
260 
261     private MavenProject buildProjectStub( final Artifact depArtifact )
262     {
263         final Model model = new Model();
264         model.setGroupId( depArtifact.getGroupId() );
265         model.setArtifactId( depArtifact.getArtifactId() );
266         model.setVersion( depArtifact.getBaseVersion() );
267         model.setPackaging( depArtifact.getType() );
268 
269         model.setDescription( "Stub for " + depArtifact.getId() );
270 
271         final MavenProject project = new MavenProject( model );
272         project.setArtifact( depArtifact );
273 
274         return project;
275     }
276 
277     Set<Artifact> resolveDependencyArtifacts( final DependencySet dependencySet )
278         throws InvalidAssemblerConfigurationException
279     {
280         final Set<Artifact> dependencyArtifacts = new LinkedHashSet<Artifact>();
281         if ( resolvedArtifacts != null )
282         {
283             dependencyArtifacts.addAll( resolvedArtifacts );
284         }
285 
286         if ( dependencySet.isUseProjectArtifact() )
287         {
288             final Artifact projectArtifact = project.getArtifact();
289             if ( ( projectArtifact != null ) && ( projectArtifact.getFile() != null ) )
290             {
291                 dependencyArtifacts.add( projectArtifact );
292             }
293             else
294             {
295                 logger.warn( "Cannot include project artifact: " + projectArtifact
296                                  + "; it doesn't have an associated file or directory." );
297             }
298         }
299 
300         if ( dependencySet.isUseProjectAttachments() )
301         {
302             @SuppressWarnings( "unchecked" ) final List<Artifact> attachments = project.getAttachedArtifacts();
303             if ( attachments != null )
304             {
305                 for ( final Artifact attachment : attachments )
306                 {
307                     if ( attachment.getFile() != null )
308                     {
309                         dependencyArtifacts.add( attachment );
310                     }
311                     else
312                     {
313                         logger.warn(
314                             "Cannot include attached artifact: " + project.getId() + " for project: " + project.getId()
315                                 + "; it doesn't have an associated file or directory." );
316                     }
317                 }
318             }
319         }
320 
321         if ( dependencySet.isUseTransitiveFiltering() )
322         {
323             logger.debug( "Filtering dependency artifacts USING transitive dependency path information." );
324         }
325         else
326         {
327             logger.debug( "Filtering dependency artifacts WITHOUT transitive dependency path information." );
328         }
329 
330         final ScopeArtifactFilter filter = new ScopeArtifactFilter( dependencySet.getScope() );
331 
332         FilterUtils.filterArtifacts( dependencyArtifacts, dependencySet.getIncludes(), dependencySet.getExcludes(),
333                                      dependencySet.isUseStrictFiltering(), dependencySet.isUseTransitiveFiltering(),
334                                      logger, filter );
335 
336         return dependencyArtifacts;
337     }
338 
339     private void addNonArchiveDependency( final Artifact depArtifact, final MavenProject depProject,
340                                           final DependencySet dependencySet, final Archiver archiver,
341                                           final AssemblerConfigurationSource configSource )
342         throws AssemblyFormattingException, ArchiveCreationException
343     {
344         final File source = depArtifact.getFile();
345 
346         String outputDirectory = dependencySet.getOutputDirectory();
347 
348         FixedStringSearchInterpolator moduleProjectInterpolator = moduleProjectInterpolator( moduleProject );
349         FixedStringSearchInterpolator artifactProjectInterpolator = artifactProjectInterpolator( depProject );
350         outputDirectory =
351             AssemblyFormatUtils.getOutputDirectory( outputDirectory, depProject.getBuild().getFinalName(), configSource,
352                                                     moduleProjectInterpolator, artifactProjectInterpolator );
353 
354         final String destName =
355             AssemblyFormatUtils.evaluateFileNameMapping( dependencySet.getOutputFileNameMapping(), depArtifact,
356                                                          configSource.getProject(), moduleArtifact, configSource,
357                                                          moduleProjectInterpolator, artifactProjectInterpolator );
358 
359         String target;
360 
361         // omit the last char if ends with / or \\
362         if ( outputDirectory.endsWith( "/" ) || outputDirectory.endsWith( "\\" ) )
363         {
364             target = outputDirectory + destName;
365         }
366         else
367         {
368             target = outputDirectory + "/" + destName;
369         }
370 
371         try
372         {
373             final int mode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
374             if ( mode > -1 )
375             {
376                 archiver.addFile( source, target, mode );
377             }
378             else
379             {
380                 archiver.addFile( source, target );
381             }
382         }
383         catch ( final ArchiverException e )
384         {
385             throw new ArchiveCreationException( "Error adding file to archive: " + e.getMessage(), e );
386         }
387     }
388 
389     public List<DependencySet> getDependencySets()
390     {
391         return dependencySets;
392     }
393 
394     public Logger getLogger()
395     {
396         return logger;
397     }
398 
399     public void setDefaultOutputDirectory( final String defaultOutputDirectory )
400     {
401         this.defaultOutputDirectory = defaultOutputDirectory;
402     }
403 
404     public void setDefaultOutputFileNameMapping( final String defaultOutputFileNameMapping )
405     {
406         this.defaultOutputFileNameMapping = defaultOutputFileNameMapping;
407     }
408 
409     public void setModuleProject( final MavenProject moduleProject )
410     {
411         this.moduleProject = moduleProject;
412     }
413 
414     public void setModuleArtifact( final Artifact moduleArtifact )
415     {
416         this.moduleArtifact = moduleArtifact;
417     }
418 }