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