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;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.StringReader;
27  import java.nio.file.attribute.FileTime;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
33  import org.apache.maven.plugins.assembly.InvalidAssemblerConfigurationException;
34  import org.apache.maven.plugins.assembly.archive.archiver.AssemblyProxyArchiver;
35  import org.apache.maven.plugins.assembly.archive.phase.AssemblyArchiverPhase;
36  import org.apache.maven.plugins.assembly.archive.phase.AssemblyArchiverPhaseComparator;
37  import org.apache.maven.plugins.assembly.artifact.DependencyResolutionException;
38  import org.apache.maven.plugins.assembly.filter.ComponentsXmlArchiverFileFilter;
39  import org.apache.maven.plugins.assembly.filter.ContainerDescriptorHandler;
40  import org.apache.maven.plugins.assembly.format.AssemblyFormattingException;
41  import org.apache.maven.plugins.assembly.internal.DebugConfigurationListener;
42  import org.apache.maven.plugins.assembly.interpolation.AssemblyExpressionEvaluator;
43  import org.apache.maven.plugins.assembly.model.Assembly;
44  import org.apache.maven.plugins.assembly.model.ContainerDescriptorHandlerConfig;
45  import org.apache.maven.plugins.assembly.utils.AssemblyFileUtils;
46  import org.apache.maven.plugins.assembly.utils.AssemblyFormatUtils;
47  import org.codehaus.plexus.PlexusContainer;
48  import org.codehaus.plexus.archiver.ArchiveFinalizer;
49  import org.codehaus.plexus.archiver.Archiver;
50  import org.codehaus.plexus.archiver.ArchiverException;
51  import org.codehaus.plexus.archiver.diags.DryRunArchiver;
52  import org.codehaus.plexus.archiver.filters.JarSecurityFileSelector;
53  import org.codehaus.plexus.archiver.jar.JarArchiver;
54  import org.codehaus.plexus.archiver.manager.ArchiverManager;
55  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
56  import org.codehaus.plexus.archiver.tar.TarArchiver;
57  import org.codehaus.plexus.archiver.tar.TarLongFileMode;
58  import org.codehaus.plexus.archiver.war.WarArchiver;
59  import org.codehaus.plexus.archiver.zip.AbstractZipArchiver;
60  import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
61  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
62  import org.codehaus.plexus.component.configurator.ConfigurationListener;
63  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
64  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
65  import org.codehaus.plexus.components.io.fileselectors.FileSelector;
66  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
67  import org.codehaus.plexus.util.StringUtils;
68  import org.codehaus.plexus.util.xml.Xpp3Dom;
69  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
70  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
71  import org.slf4j.Logger;
72  import org.slf4j.LoggerFactory;
73  
74  import static java.util.Objects.requireNonNull;
75  
76  /**
77   * Controller component designed to organize the many activities involved in creating an assembly archive. This includes
78   * locating and configuring {@link Archiver} instances, executing multiple {@link org.apache.maven.plugins.assembly
79   * .archive.phase.AssemblyArchiverPhase} instances to
80   * interpret the various sections of the assembly descriptor and determine which files to add, and other associated
81   * activities.
82   *
83   *
84   */
85  @Named
86  public class DefaultAssemblyArchiver implements AssemblyArchiver {
87      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAssemblyArchiver.class);
88  
89      private final ArchiverManager archiverManager;
90  
91      private final List<AssemblyArchiverPhase> assemblyPhases;
92  
93      @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
94      private final Map<String, ContainerDescriptorHandler> containerDescriptorHandlers;
95  
96      private final PlexusContainer container;
97  
98      private final BasicComponentConfigurator configurator;
99  
100     @Inject
101     public DefaultAssemblyArchiver(
102             ArchiverManager archiverManager,
103             List<AssemblyArchiverPhase> assemblyPhases,
104             Map<String, ContainerDescriptorHandler> containerDescriptorHandlers,
105             PlexusContainer container,
106             BasicComponentConfigurator configurator) {
107         this.archiverManager = requireNonNull(archiverManager);
108         this.assemblyPhases = requireNonNull(assemblyPhases);
109         this.containerDescriptorHandlers = requireNonNull(containerDescriptorHandlers);
110         this.container = requireNonNull(container);
111         this.configurator = requireNonNull(configurator);
112     }
113 
114     private List<AssemblyArchiverPhase> sortedPhases() {
115         List<AssemblyArchiverPhase> sorted = new ArrayList<>(assemblyPhases);
116         sorted.sort(new AssemblyArchiverPhaseComparator());
117         return sorted;
118     }
119 
120     /**
121      * {@inheritDoc}
122      */
123     @Override
124     public File createArchive(
125             final Assembly assembly,
126             final String fullName,
127             final String format,
128             final AssemblerConfigurationSource configSource,
129             FileTime outputTimestamp)
130             throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException {
131         validate(assembly);
132 
133         String filename = fullName;
134         if (!configSource.isIgnoreDirFormatExtensions() || !format.startsWith("dir")) {
135             filename += "." + format;
136         }
137 
138         AssemblyFileUtils.verifyTempDirectoryAvailability(configSource.getTemporaryRootDirectory());
139 
140         final File outputDirectory = configSource.getOutputDirectory();
141 
142         final File destFile = new File(outputDirectory, filename);
143 
144         try {
145             final String finalName = configSource.getFinalName();
146             final String specifiedBasedir = assembly.getBaseDirectory();
147 
148             String basedir = finalName;
149 
150             if (specifiedBasedir != null) {
151                 basedir = AssemblyFormatUtils.getOutputDirectory(
152                         specifiedBasedir,
153                         finalName,
154                         configSource,
155                         AssemblyFormatUtils.moduleProjectInterpolator(configSource.getProject()),
156                         AssemblyFormatUtils.artifactProjectInterpolator(null));
157             }
158 
159             final List<ContainerDescriptorHandler> containerHandlers =
160                     selectContainerDescriptorHandlers(assembly.getContainerDescriptorHandlers(), configSource);
161 
162             final Archiver archiver = createArchiver(
163                     format,
164                     assembly.isIncludeBaseDirectory(),
165                     basedir,
166                     configSource,
167                     containerHandlers,
168                     outputTimestamp);
169 
170             archiver.setDestFile(destFile);
171 
172             for (AssemblyArchiverPhase phase : sortedPhases()) {
173                 phase.execute(assembly, archiver, configSource);
174             }
175 
176             archiver.createArchive();
177         } catch (final ArchiverException | IOException e) {
178             throw new ArchiveCreationException(
179                     "Error creating assembly archive " + assembly.getId() + ": " + e.getMessage(), e);
180         } catch (final NoSuchArchiverException e) {
181             throw new ArchiveCreationException(
182                     "Unable to obtain archiver for extension '" + format + "', for assembly: '" + assembly.getId()
183                             + "'",
184                     e);
185         } catch (final DependencyResolutionException e) {
186             throw new ArchiveCreationException(
187                     "Unable to resolve dependencies for assembly '" + assembly.getId() + "'", e);
188         }
189 
190         return destFile;
191     }
192 
193     private void validate(final Assembly assembly) throws InvalidAssemblerConfigurationException {
194         if (assembly.getId() == null || assembly.getId().trim().length() < 1) {
195             throw new InvalidAssemblerConfigurationException("Assembly ID must be present and non-empty.");
196         }
197     }
198 
199     // CHECKSTYLE_OFF: LineLength
200     private List<ContainerDescriptorHandler> selectContainerDescriptorHandlers(
201             List<ContainerDescriptorHandlerConfig> requestedContainerDescriptorHandlers,
202             final AssemblerConfigurationSource configSource)
203             throws InvalidAssemblerConfigurationException
204                 // CHECKSTYLE_ON: LineLength
205             {
206         LOGGER.debug("All known ContainerDescriptorHandler components: "
207                 + (containerDescriptorHandlers == null
208                         ? "none; map is null."
209                         : "" + containerDescriptorHandlers.keySet()));
210 
211         if (requestedContainerDescriptorHandlers == null) {
212             requestedContainerDescriptorHandlers = new ArrayList<>();
213         }
214 
215         final List<ContainerDescriptorHandler> handlers = new ArrayList<>();
216         final List<String> hints = new ArrayList<>();
217 
218         if (!requestedContainerDescriptorHandlers.isEmpty()) {
219             for (final ContainerDescriptorHandlerConfig config : requestedContainerDescriptorHandlers) {
220                 final String hint = config.getHandlerName();
221                 final ContainerDescriptorHandler handler = containerDescriptorHandlers.get(hint);
222 
223                 if (handler == null) {
224                     throw new InvalidAssemblerConfigurationException(
225                             "Cannot find ContainerDescriptorHandler with hint: " + hint);
226                 }
227 
228                 LOGGER.debug("Found container descriptor handler with hint: " + hint + " (component: " + handler + ")");
229 
230                 if (config.getConfiguration() != null) {
231                     LOGGER.debug("Configuring handler with:\n\n" + config.getConfiguration() + "\n\n");
232 
233                     configureContainerDescriptorHandler(handler, (Xpp3Dom) config.getConfiguration(), configSource);
234                 }
235 
236                 handlers.add(handler);
237                 hints.add(hint);
238             }
239         }
240 
241         if (!hints.contains("plexus")) {
242             handlers.add(new ComponentsXmlArchiverFileFilter());
243         }
244 
245         return handlers;
246     }
247 
248     /**
249      * Creates the necessary archiver to build the distribution file.
250      *
251      * @param format                Archive format
252      * @param includeBaseDir        the base directory for include.
253      * @param finalName             The final name.
254      * @param configSource          {@link AssemblerConfigurationSource}
255      * @param containerHandlers     The list of {@link ContainerDescriptorHandler}
256      * @return archiver Archiver generated
257      * @throws org.codehaus.plexus.archiver.ArchiverException
258      * @throws org.codehaus.plexus.archiver.manager.NoSuchArchiverException
259      */
260     protected Archiver createArchiver(
261             final String format,
262             final boolean includeBaseDir,
263             final String finalName,
264             final AssemblerConfigurationSource configSource,
265             final List<ContainerDescriptorHandler> containerHandlers,
266             FileTime outputTimestamp)
267             throws NoSuchArchiverException {
268 
269         Archiver archiver = archiverManager.getArchiver(format);
270 
271         if (archiver instanceof TarArchiver) {
272             ((TarArchiver) archiver).setLongfile(TarLongFileMode.valueOf(configSource.getTarLongFileMode()));
273         }
274 
275         if (archiver instanceof WarArchiver) {
276             ((WarArchiver) archiver).setExpectWebXml(false);
277         }
278 
279         if (archiver instanceof AbstractZipArchiver) {
280             ((AbstractZipArchiver) archiver).setRecompressAddedZips(configSource.isRecompressZippedFiles());
281         }
282 
283         final List<FileSelector> extraSelectors = new ArrayList<>();
284         final List<ArchiveFinalizer> extraFinalizers = new ArrayList<>();
285         if (archiver instanceof JarArchiver) {
286             configureJarArchiver((JarArchiver) archiver, configSource.getMergeManifestMode());
287 
288             extraSelectors.add(new JarSecurityFileSelector());
289 
290             extraFinalizers.add(new ManifestCreationFinalizer(
291                     configSource.getMavenSession(),
292                     configSource.getProject(),
293                     configSource.getJarArchiveConfiguration()));
294         }
295 
296         if (configSource.getArchiverConfig() != null) {
297             configureArchiver(archiver, configSource);
298         }
299 
300         String prefix = "";
301         if (includeBaseDir) {
302             prefix = finalName;
303         }
304 
305         archiver = new AssemblyProxyArchiver(
306                 prefix,
307                 archiver,
308                 containerHandlers,
309                 extraSelectors,
310                 extraFinalizers,
311                 configSource.getWorkingDirectory());
312         if (configSource.isDryRun()) {
313             archiver = new DryRunArchiver(archiver, LOGGER);
314         }
315 
316         archiver.setIgnorePermissions(configSource.isIgnorePermissions());
317         archiver.setForced(!configSource.isUpdateOnly());
318 
319         // configure for Reproducible Builds based on outputTimestamp value
320         if (outputTimestamp != null) {
321             archiver.configureReproducibleBuild(outputTimestamp);
322         }
323 
324         if (configSource.getOverrideUid() != null) {
325             archiver.setOverrideUid(configSource.getOverrideUid());
326         }
327         if (StringUtils.isNotBlank(configSource.getOverrideUserName())) {
328             archiver.setOverrideUserName(StringUtils.trim(configSource.getOverrideUserName()));
329         }
330         if (configSource.getOverrideGid() != null) {
331             archiver.setOverrideGid(configSource.getOverrideGid());
332         }
333         if (StringUtils.isNotBlank(configSource.getOverrideGroupName())) {
334             archiver.setOverrideGroupName(StringUtils.trim(configSource.getOverrideGroupName()));
335         }
336 
337         return archiver;
338     }
339 
340     private void configureJarArchiver(JarArchiver archiver, String mergeManifestMode) {
341 
342         if (mergeManifestMode != null) {
343             archiver.setFilesetmanifest(JarArchiver.FilesetManifestConfig.valueOf(mergeManifestMode));
344         }
345 
346         archiver.setMinimalDefaultManifest(true);
347     }
348 
349     private void configureContainerDescriptorHandler(
350             final ContainerDescriptorHandler handler,
351             final Xpp3Dom config,
352             final AssemblerConfigurationSource configSource)
353             throws InvalidAssemblerConfigurationException {
354         LOGGER.debug("Configuring handler: '" + handler.getClass().getName() + "' -->");
355 
356         try {
357             configureComponent(handler, config, configSource);
358         } catch (final ComponentConfigurationException e) {
359             throw new InvalidAssemblerConfigurationException(
360                     "Failed to configure handler: " + handler.getClass().getName(), e);
361         } catch (final ComponentLookupException e) {
362             throw new InvalidAssemblerConfigurationException(
363                     "Failed to lookup configurator for setup of handler: "
364                             + handler.getClass().getName(),
365                     e);
366         }
367 
368         LOGGER.debug("-- end configuration --");
369     }
370 
371     private void configureArchiver(final Archiver archiver, final AssemblerConfigurationSource configSource) {
372         Xpp3Dom config;
373         try {
374             config = Xpp3DomBuilder.build(new StringReader(configSource.getArchiverConfig()));
375         } catch (final XmlPullParserException | IOException e) {
376             throw new ArchiverException(
377                     "Failed to parse archiver configuration for: "
378                             + archiver.getClass().getName(),
379                     e);
380         }
381 
382         LOGGER.debug("Configuring archiver: '" + archiver.getClass().getName() + "' -->");
383 
384         try {
385             configureComponent(archiver, config, configSource);
386         } catch (final ComponentConfigurationException e) {
387             throw new ArchiverException(
388                     "Failed to configure archiver: " + archiver.getClass().getName(), e);
389         } catch (final ComponentLookupException e) {
390             throw new ArchiverException(
391                     "Failed to lookup configurator for setup of archiver: "
392                             + archiver.getClass().getName(),
393                     e);
394         }
395 
396         LOGGER.debug("-- end configuration --");
397     }
398 
399     private void configureComponent(
400             final Object component, final Xpp3Dom config, final AssemblerConfigurationSource configSource)
401             throws ComponentLookupException, ComponentConfigurationException {
402 
403         final ConfigurationListener listener = new DebugConfigurationListener(LOGGER);
404 
405         final ExpressionEvaluator expressionEvaluator = new AssemblyExpressionEvaluator(configSource);
406 
407         final XmlPlexusConfiguration configuration = new XmlPlexusConfiguration(config);
408 
409         configurator.configureComponent(
410                 component, configuration, expressionEvaluator, container.getContainerRealm(), listener);
411     }
412 }