View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.assembly.archive.task;
20  
21  import java.io.File;
22  import java.nio.charset.Charset;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
32  import org.apache.maven.model.Dependency;
33  import org.apache.maven.model.Model;
34  import org.apache.maven.model.building.ModelBuildingRequest;
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      private static final Logger LOGGER = LoggerFactory.getLogger(AddDependencySetsTask.class);
65  
66      private static final List<String> NON_ARCHIVE_DEPENDENCY_TYPES;
67  
68      static {
69          final List<String> nonArch = new ArrayList<>();
70  
71          nonArch.add("pom");
72  
73          NON_ARCHIVE_DEPENDENCY_TYPES = Collections.unmodifiableList(nonArch);
74      }
75  
76      private final List<DependencySet> dependencySets;
77  
78      private final MavenProject project;
79  
80      private final ProjectBuilder projectBuilder1;
81  
82      private final Set<Artifact> resolvedArtifacts;
83  
84      private MavenProject moduleProject;
85  
86      private String defaultOutputDirectory;
87  
88      private String defaultOutputFileNameMapping;
89  
90      private Artifact moduleArtifact;
91  
92      public AddDependencySetsTask(
93              final List<DependencySet> dependencySets,
94              final Set<Artifact> resolvedArtifacts,
95              final MavenProject project,
96              ProjectBuilder projectBuilder) {
97          this.dependencySets = dependencySets;
98          this.resolvedArtifacts = resolvedArtifacts;
99          this.project = project;
100         this.projectBuilder1 = projectBuilder;
101     }
102 
103     public void execute(final Archiver archiver, final AssemblerConfigurationSource configSource)
104             throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException {
105         if ((dependencySets == null) || dependencySets.isEmpty()) {
106             LOGGER.debug("No dependency sets specified.");
107             return;
108         }
109 
110         final List<Dependency> deps = project.getDependencies();
111         if ((deps == null) || deps.isEmpty()) {
112             LOGGER.debug("Project " + project.getId() + " has no dependencies. Skipping dependency set addition.");
113         }
114 
115         for (final DependencySet dependencySet : dependencySets) {
116             addDependencySet(dependencySet, archiver, configSource);
117         }
118     }
119 
120     void addDependencySet(
121             final DependencySet dependencySet, final Archiver archiver, final AssemblerConfigurationSource configSource)
122             throws AssemblyFormattingException, ArchiveCreationException, InvalidAssemblerConfigurationException {
123         LOGGER.debug("Processing DependencySet (output=" + dependencySet.getOutputDirectory() + ")");
124 
125         if (!dependencySet.isUseTransitiveDependencies() && dependencySet.isUseTransitiveFiltering()) {
126             LOGGER.warn("DependencySet has nonsensical configuration: useTransitiveDependencies == false "
127                     + "AND useTransitiveFiltering == true. Transitive filtering flag will be ignored.");
128         }
129 
130         final Set<Artifact> dependencyArtifacts = resolveDependencyArtifacts(dependencySet);
131 
132         if (!unpackTransformsContent(dependencySet) && dependencyArtifacts.size() > 1) {
133             checkMultiArtifactOutputConfig(dependencySet);
134         }
135 
136         LOGGER.debug("Adding " + dependencyArtifacts.size() + " dependency artifacts.");
137 
138         UnpackOptions unpackOptions = dependencySet.getUnpackOptions();
139         InputStreamTransformer fileSetTransformers = isUnpackWithOptions(dependencySet)
140                 ? ReaderFormatter.getFileSetTransformers(
141                         configSource,
142                         unpackOptions.isFiltered(),
143                         new HashSet<>(unpackOptions.getNonFilteredFileExtensions()),
144                         unpackOptions.getLineEnding())
145                 : null;
146 
147         for (final Artifact depArtifact : dependencyArtifacts) {
148             ProjectBuildingRequest pbr = getProjectBuildingRequest(configSource);
149             MavenProject depProject;
150             try {
151                 ProjectBuildingResult build = projectBuilder1.build(depArtifact, pbr);
152                 depProject = build.getProject();
153             } catch (final ProjectBuildingException e) {
154                 LOGGER.debug("Error retrieving POM of module-dependency: " + depArtifact.getId() + "; Reason: "
155                         + e.getMessage() + "\n\nBuilding stub project instance.");
156 
157                 depProject = buildProjectStub(depArtifact);
158             }
159 
160             if (NON_ARCHIVE_DEPENDENCY_TYPES.contains(depArtifact.getType())) {
161                 addNonArchiveDependency(depArtifact, depProject, dependencySet, archiver, configSource);
162             } else {
163                 addNormalArtifact(dependencySet, depArtifact, depProject, archiver, configSource, fileSetTransformers);
164             }
165         }
166     }
167 
168     private ProjectBuildingRequest getProjectBuildingRequest(AssemblerConfigurationSource configSource) {
169         return new DefaultProjectBuildingRequest(configSource.getMavenSession().getProjectBuildingRequest())
170                 .setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL)
171                 .setProcessPlugins(false);
172     }
173 
174     private boolean isUnpackWithOptions(DependencySet dependencySet) {
175         return dependencySet.isUnpack() && dependencySet.getUnpackOptions() != null;
176     }
177 
178     private boolean unpackTransformsContent(DependencySet dependencySet) {
179         return isUnpackWithOptions(dependencySet) && isContentModifyingOption(dependencySet.getUnpackOptions());
180     }
181 
182     private boolean isContentModifyingOption(UnpackOptions opts) {
183         return (opts.isFiltered() || opts.getLineEnding() != null);
184     }
185 
186     private void checkMultiArtifactOutputConfig(final DependencySet dependencySet) {
187         String dir = dependencySet.getOutputDirectory();
188         if (dir == null) {
189             dir = defaultOutputDirectory;
190         }
191 
192         String mapping = dependencySet.getOutputFileNameMapping();
193         if (mapping == null) {
194             mapping = defaultOutputFileNameMapping;
195         }
196 
197         if ((dir == null || !dir.contains("${")) && (mapping == null || !mapping.contains("${"))) {
198             LOGGER.warn("NOTE: Your assembly specifies a dependencySet that matches multiple artifacts, but "
199                     + "specifies a concrete output format. THIS MAY RESULT IN ONE OR MORE ARTIFACTS BEING "
200                     + "OBSCURED!\n\n" + "Output directory: '" + dir + "'\nOutput filename mapping: '" + mapping
201                     + "'");
202         }
203     }
204 
205     private void addNormalArtifact(
206             final DependencySet dependencySet,
207             final Artifact depArtifact,
208             final MavenProject depProject,
209             final Archiver archiver,
210             final AssemblerConfigurationSource configSource,
211             InputStreamTransformer fileSetTransformers)
212             throws AssemblyFormattingException, ArchiveCreationException {
213         LOGGER.debug("Adding dependency artifact " + depArtifact.getId() + ".");
214 
215         String encoding = isUnpackWithOptions(dependencySet)
216                 ? dependencySet.getUnpackOptions().getEncoding()
217                 : null;
218         Charset charset = encoding != null ? Charset.forName(encoding) : null;
219         final AddArtifactTask task = new AddArtifactTask(depArtifact, fileSetTransformers, charset);
220 
221         task.setProject(depProject);
222         task.setModuleProject(moduleProject);
223         task.setModuleArtifact(moduleArtifact);
224         task.setOutputDirectory(dependencySet.getOutputDirectory(), defaultOutputDirectory);
225         task.setFileNameMapping(dependencySet.getOutputFileNameMapping(), defaultOutputFileNameMapping);
226 
227         final int dirMode = TypeConversionUtils.modeToInt(dependencySet.getDirectoryMode(), LOGGER);
228         if (dirMode != -1) {
229             task.setDirectoryMode(dirMode);
230         }
231 
232         final int fileMode = TypeConversionUtils.modeToInt(dependencySet.getFileMode(), LOGGER);
233         if (fileMode != -1) {
234             task.setFileMode(fileMode);
235         }
236 
237         task.setUnpack(dependencySet.isUnpack());
238 
239         final UnpackOptions opts = dependencySet.getUnpackOptions();
240         if (isUnpackWithOptions(dependencySet)) {
241             task.setIncludes(opts.getIncludes());
242             task.setExcludes(opts.getExcludes());
243             task.setUsingDefaultExcludes(opts.isUseDefaultExcludes());
244         }
245 
246         task.execute(archiver, configSource);
247     }
248 
249     private MavenProject buildProjectStub(final Artifact depArtifact) {
250         final Model model = new Model();
251         model.setGroupId(depArtifact.getGroupId());
252         model.setArtifactId(depArtifact.getArtifactId());
253         model.setVersion(depArtifact.getBaseVersion());
254         model.setPackaging(depArtifact.getType());
255 
256         model.setDescription("Stub for " + depArtifact.getId());
257 
258         final MavenProject project = new MavenProject(model);
259         project.setArtifact(depArtifact);
260 
261         return project;
262     }
263 
264     Set<Artifact> resolveDependencyArtifacts(final DependencySet dependencySet)
265             throws InvalidAssemblerConfigurationException {
266         final Set<Artifact> dependencyArtifacts = new LinkedHashSet<>();
267         if (resolvedArtifacts != null) {
268             dependencyArtifacts.addAll(resolvedArtifacts);
269         }
270 
271         if (dependencySet.isUseProjectArtifact()) {
272             final Artifact projectArtifact = project.getArtifact();
273             if ((projectArtifact != null) && (projectArtifact.getFile() != null)) {
274                 dependencyArtifacts.add(projectArtifact);
275             } else {
276                 LOGGER.warn("Cannot include project artifact: " + projectArtifact
277                         + "; it doesn't have an associated file or directory.");
278             }
279         }
280 
281         if (dependencySet.isUseProjectAttachments()) {
282             final List<Artifact> attachments = project.getAttachedArtifacts();
283             if (attachments != null) {
284                 for (final Artifact attachment : attachments) {
285                     if (attachment.getFile() != null) {
286                         dependencyArtifacts.add(attachment);
287                     } else {
288                         LOGGER.warn("Cannot include attached artifact: " + project.getId() + " for project: "
289                                 + project.getId() + "; it doesn't have an associated file or directory.");
290                     }
291                 }
292             }
293         }
294 
295         if (dependencySet.isUseTransitiveFiltering()) {
296             LOGGER.debug("Filtering dependency artifacts USING transitive dependency path information.");
297         } else {
298             LOGGER.debug("Filtering dependency artifacts WITHOUT transitive dependency path information.");
299         }
300 
301         final ScopeFilter scopeFilter = FilterUtils.newScopeFilter(dependencySet.getScope());
302 
303         final ArtifactFilter filter = new ArtifactIncludeFilterTransformer().transform(scopeFilter);
304 
305         FilterUtils.filterArtifacts(
306                 dependencyArtifacts,
307                 dependencySet.getIncludes(),
308                 dependencySet.getExcludes(),
309                 dependencySet.isUseStrictFiltering(),
310                 dependencySet.isUseTransitiveFiltering(),
311                 LOGGER,
312                 filter);
313 
314         return dependencyArtifacts;
315     }
316 
317     private void addNonArchiveDependency(
318             final Artifact depArtifact,
319             final MavenProject depProject,
320             final DependencySet dependencySet,
321             final Archiver archiver,
322             final AssemblerConfigurationSource configSource)
323             throws AssemblyFormattingException, ArchiveCreationException {
324         final File source = depArtifact.getFile();
325 
326         String outputDirectory = dependencySet.getOutputDirectory();
327 
328         FixedStringSearchInterpolator moduleProjectInterpolator =
329                 AssemblyFormatUtils.moduleProjectInterpolator(moduleProject);
330         FixedStringSearchInterpolator artifactProjectInterpolator =
331                 AssemblyFormatUtils.artifactProjectInterpolator(depProject);
332         outputDirectory = AssemblyFormatUtils.getOutputDirectory(
333                 outputDirectory,
334                 depProject.getBuild().getFinalName(),
335                 configSource,
336                 moduleProjectInterpolator,
337                 artifactProjectInterpolator);
338 
339         final String destName = AssemblyFormatUtils.evaluateFileNameMapping(
340                 dependencySet.getOutputFileNameMapping(),
341                 depArtifact,
342                 configSource.getProject(),
343                 moduleArtifact,
344                 configSource,
345                 moduleProjectInterpolator,
346                 artifactProjectInterpolator);
347 
348         String target;
349 
350         // omit the last char if ends with / or \\
351         if (outputDirectory.endsWith("/") || outputDirectory.endsWith("\\")) {
352             target = outputDirectory + destName;
353         } else {
354             target = outputDirectory + "/" + destName;
355         }
356 
357         try {
358             final int mode = TypeConversionUtils.modeToInt(dependencySet.getFileMode(), LOGGER);
359             if (mode > -1) {
360                 archiver.addFile(source, target, mode);
361             } else {
362                 archiver.addFile(source, target);
363             }
364         } catch (final ArchiverException e) {
365             throw new ArchiveCreationException("Error adding file to archive: " + e.getMessage(), e);
366         }
367     }
368 
369     public List<DependencySet> getDependencySets() {
370         return dependencySets;
371     }
372 
373     public void setDefaultOutputDirectory(final String defaultOutputDirectory) {
374         this.defaultOutputDirectory = defaultOutputDirectory;
375     }
376 
377     public void setDefaultOutputFileNameMapping(final String defaultOutputFileNameMapping) {
378         this.defaultOutputFileNameMapping = defaultOutputFileNameMapping;
379     }
380 
381     public void setModuleProject(final MavenProject moduleProject) {
382         this.moduleProject = moduleProject;
383     }
384 
385     public void setModuleArtifact(final Artifact moduleArtifact) {
386         this.moduleArtifact = moduleArtifact;
387     }
388 }