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