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