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