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