View Javadoc
1   package org.apache.maven.plugins.assembly.archive;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import javax.inject.Inject;
23  import javax.inject.Named;
24  
25  import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
26  import org.apache.maven.plugins.assembly.InvalidAssemblerConfigurationException;
27  import org.apache.maven.plugins.assembly.archive.archiver.AssemblyProxyArchiver;
28  import org.apache.maven.plugins.assembly.archive.phase.AssemblyArchiverPhase;
29  import org.apache.maven.plugins.assembly.archive.phase.AssemblyArchiverPhaseComparator;
30  import org.apache.maven.plugins.assembly.artifact.DependencyResolutionException;
31  import org.apache.maven.plugins.assembly.filter.ComponentsXmlArchiverFileFilter;
32  import org.apache.maven.plugins.assembly.filter.ContainerDescriptorHandler;
33  import org.apache.maven.plugins.assembly.format.AssemblyFormattingException;
34  import org.apache.maven.plugins.assembly.internal.DebugConfigurationListener;
35  import org.apache.maven.plugins.assembly.interpolation.AssemblyExpressionEvaluator;
36  import org.apache.maven.plugins.assembly.model.Assembly;
37  import org.apache.maven.plugins.assembly.model.ContainerDescriptorHandlerConfig;
38  import org.apache.maven.plugins.assembly.utils.AssemblyFileUtils;
39  import org.apache.maven.plugins.assembly.utils.AssemblyFormatUtils;
40  import org.codehaus.plexus.PlexusContainer;
41  import org.codehaus.plexus.archiver.ArchiveFinalizer;
42  import org.codehaus.plexus.archiver.Archiver;
43  import org.codehaus.plexus.archiver.ArchiverException;
44  import org.codehaus.plexus.archiver.diags.DryRunArchiver;
45  import org.codehaus.plexus.archiver.filters.JarSecurityFileSelector;
46  import org.codehaus.plexus.archiver.jar.JarArchiver;
47  import org.codehaus.plexus.archiver.manager.ArchiverManager;
48  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
49  import org.codehaus.plexus.archiver.tar.TarArchiver;
50  import org.codehaus.plexus.archiver.tar.TarLongFileMode;
51  import org.codehaus.plexus.archiver.war.WarArchiver;
52  import org.codehaus.plexus.archiver.zip.AbstractZipArchiver;
53  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
54  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
55  import org.codehaus.plexus.component.configurator.ConfigurationListener;
56  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
57  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
58  import org.codehaus.plexus.components.io.fileselectors.FileSelector;
59  import org.codehaus.plexus.configuration.PlexusConfiguration;
60  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
61  import org.codehaus.plexus.util.StringUtils;
62  import org.codehaus.plexus.util.xml.Xpp3Dom;
63  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
64  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
65  import org.slf4j.Logger;
66  import org.slf4j.LoggerFactory;
67  
68  import java.io.File;
69  import java.io.IOException;
70  import java.io.StringReader;
71  import java.lang.reflect.InvocationTargetException;
72  import java.lang.reflect.Method;
73  import java.util.ArrayList;
74  import java.util.Collections;
75  import java.util.Date;
76  import java.util.List;
77  import java.util.Map;
78  
79  import static java.util.Objects.requireNonNull;
80  
81  /**
82   * Controller component designed to organize the many activities involved in creating an assembly archive. This includes
83   * locating and configuring {@link Archiver} instances, executing multiple {@link org.apache.maven.plugins.assembly
84   * .archive.phase.AssemblyArchiverPhase} instances to
85   * interpret the various sections of the assembly descriptor and determine which files to add, and other associated
86   * activities.
87   *
88   *
89   */
90  @Named
91  public class DefaultAssemblyArchiver implements AssemblyArchiver
92  {
93      private static final Logger LOGGER = LoggerFactory.getLogger( DefaultAssemblyArchiver.class );
94  
95      private final ArchiverManager archiverManager;
96  
97      private final List<AssemblyArchiverPhase> assemblyPhases;
98  
99      @SuppressWarnings( "MismatchedQueryAndUpdateOfCollection" )
100     private final Map<String, ContainerDescriptorHandler> containerDescriptorHandlers;
101 
102     private final PlexusContainer container;
103 
104 
105     @Inject
106     public DefaultAssemblyArchiver( ArchiverManager archiverManager, List<AssemblyArchiverPhase> assemblyPhases,
107                                     Map<String, ContainerDescriptorHandler> containerDescriptorHandlers,
108                                     PlexusContainer container )
109     {
110         this.archiverManager = requireNonNull( archiverManager );
111         this.assemblyPhases = requireNonNull( assemblyPhases );
112         this.containerDescriptorHandlers = requireNonNull( containerDescriptorHandlers );
113         this.container = requireNonNull( container );
114     }
115 
116     private List<AssemblyArchiverPhase> sortedPhases()
117     {
118         List<AssemblyArchiverPhase> sorted = new ArrayList<>( assemblyPhases );
119         Collections.sort( sorted, new AssemblyArchiverPhaseComparator() );
120         return sorted;
121     }
122 
123     /**
124      * {@inheritDoc}
125      */
126     @Override
127     public File createArchive( final Assembly assembly, final String fullName, final String format,
128                                final AssemblerConfigurationSource configSource, boolean recompressZippedFiles,
129                                String mergeManifestMode, Date outputTimestamp )
130         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
131     {
132         validate( assembly );
133 
134         String filename = fullName;
135         if ( !configSource.isIgnoreDirFormatExtensions() || !format.startsWith( "dir" ) )
136         {
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         {
148             final String finalName = configSource.getFinalName();
149             final String specifiedBasedir = assembly.getBaseDirectory();
150 
151             String basedir = finalName;
152 
153             if ( specifiedBasedir != null )
154             {
155                 basedir = AssemblyFormatUtils.getOutputDirectory( specifiedBasedir, finalName, configSource,
156                                                                   AssemblyFormatUtils.moduleProjectInterpolator(
157                                                                       configSource.getProject() ),
158                                                                   AssemblyFormatUtils.artifactProjectInterpolator(
159                                                                       null ) );
160             }
161 
162             final List<ContainerDescriptorHandler> containerHandlers =
163                 selectContainerDescriptorHandlers( assembly.getContainerDescriptorHandlers(), configSource );
164 
165             final Archiver archiver =
166                 createArchiver( format, assembly.isIncludeBaseDirectory(), basedir, configSource, containerHandlers,
167                                 recompressZippedFiles, mergeManifestMode, outputTimestamp );
168 
169             archiver.setDestFile( destFile );
170 
171             for ( AssemblyArchiverPhase phase : sortedPhases() )
172             {
173                 phase.execute( assembly, archiver, configSource );
174             }
175 
176             archiver.createArchive();
177         }
178         catch ( final ArchiverException | IOException e )
179         {
180             throw new ArchiveCreationException(
181                 "Error creating assembly archive " + assembly.getId() + ": " + e.getMessage(), e );
182         }
183         catch ( final NoSuchArchiverException e )
184         {
185             throw new ArchiveCreationException(
186                 "Unable to obtain archiver for extension '" + format + "', for assembly: '" + assembly.getId() + "'",
187                 e );
188         }
189         catch ( final DependencyResolutionException e )
190         {
191             throw new ArchiveCreationException(
192                 "Unable to resolve dependencies for assembly '" + assembly.getId() + "'", e );
193         }
194 
195         return destFile;
196     }
197 
198     private void validate( final Assembly assembly )
199         throws InvalidAssemblerConfigurationException
200     {
201         if ( assembly.getId() == null || assembly.getId().trim().length() < 1 )
202         {
203             throw new InvalidAssemblerConfigurationException( "Assembly ID must be present and non-empty." );
204         }
205     }
206 
207     // CHECKSTYLE_OFF: LineLength
208     private List<ContainerDescriptorHandler> selectContainerDescriptorHandlers(
209         List<ContainerDescriptorHandlerConfig> requestedContainerDescriptorHandlers,
210         final AssemblerConfigurationSource configSource )
211         throws InvalidAssemblerConfigurationException
212     // CHECKSTYLE_ON: LineLength
213     {
214         LOGGER.debug( "All known ContainerDescriptorHandler components: " + ( containerDescriptorHandlers == null
215             ? "none; map is null."
216             : "" + containerDescriptorHandlers.keySet() ) );
217 
218         if ( requestedContainerDescriptorHandlers == null )
219         {
220             requestedContainerDescriptorHandlers = new ArrayList<>();
221         }
222 
223         final List<ContainerDescriptorHandler> handlers = new ArrayList<>();
224         final List<String> hints = new ArrayList<>();
225 
226         if ( !requestedContainerDescriptorHandlers.isEmpty() )
227         {
228             for ( final ContainerDescriptorHandlerConfig config : requestedContainerDescriptorHandlers )
229             {
230                 final String hint = config.getHandlerName();
231                 final ContainerDescriptorHandler handler = containerDescriptorHandlers.get( hint );
232 
233                 if ( handler == null )
234                 {
235                     throw new InvalidAssemblerConfigurationException(
236                         "Cannot find ContainerDescriptorHandler with hint: " + hint );
237                 }
238 
239                 LOGGER.debug(
240                     "Found container descriptor handler with hint: " + hint + " (component: " + handler + ")" );
241 
242                 if ( config.getConfiguration() != null )
243                 {
244                     LOGGER.debug( "Configuring handler with:\n\n" + config.getConfiguration() + "\n\n" );
245 
246                     configureContainerDescriptorHandler( handler, (Xpp3Dom) config.getConfiguration(), configSource );
247                 }
248 
249                 handlers.add( handler );
250                 hints.add( hint );
251             }
252         }
253 
254         if ( !hints.contains( "plexus" ) )
255         {
256             handlers.add( new ComponentsXmlArchiverFileFilter() );
257         }
258 
259         return handlers;
260     }
261 
262     /**
263      * Creates the necessary archiver to build the distribution file.
264      *
265      * @param format                Archive format
266      * @param includeBaseDir        the base directory for include.
267      * @param finalName             The final name.
268      * @param configSource          {@link AssemblerConfigurationSource}
269      * @param containerHandlers     The list of {@link ContainerDescriptorHandler}
270      * @param recompressZippedFiles recompress zipped files.
271      * @param mergeManifestMode     how to handle already existing Manifest files
272      * @return archiver Archiver generated
273      * @throws org.codehaus.plexus.archiver.ArchiverException
274      * @throws org.codehaus.plexus.archiver.manager.NoSuchArchiverException
275      */
276     protected Archiver createArchiver( final String format, final boolean includeBaseDir, final String finalName,
277                                        final AssemblerConfigurationSource configSource,
278                                        final List<ContainerDescriptorHandler> containerHandlers,
279                                        boolean recompressZippedFiles, String mergeManifestMode, Date outputTimestamp )
280         throws NoSuchArchiverException
281     {
282         Archiver archiver;
283         if ( "txz".equals( format ) || "tgz".equals( format ) || "tbz2".equals( format ) || format.startsWith( "tar" ) )
284         {
285             archiver = createTarArchiver( format, TarLongFileMode.valueOf( configSource.getTarLongFileMode() ) );
286         }
287         else if ( "war".equals( format ) )
288         {
289             archiver = createWarArchiver();
290         }
291         else
292         {
293             archiver = archiverManager.getArchiver( format );
294         }
295 
296         if ( archiver instanceof AbstractZipArchiver )
297         {
298             ( (AbstractZipArchiver) archiver ).setRecompressAddedZips( recompressZippedFiles );
299         }
300 
301         final List<FileSelector> extraSelectors = new ArrayList<>();
302         final List<ArchiveFinalizer> extraFinalizers = new ArrayList<>();
303         if ( archiver instanceof JarArchiver )
304         {
305             if ( mergeManifestMode != null )
306             {
307                 ( (JarArchiver) archiver ).setFilesetmanifest(
308                     JarArchiver.FilesetManifestConfig.valueOf( mergeManifestMode ) );
309             }
310 
311             extraSelectors.add( new JarSecurityFileSelector() );
312 
313             extraFinalizers.add(
314                 new ManifestCreationFinalizer( configSource.getMavenSession(), configSource.getProject(),
315                                                configSource.getJarArchiveConfiguration() ) );
316 
317         }
318 
319         if ( configSource.getArchiverConfig() != null )
320         {
321             configureArchiver( archiver, configSource );
322         }
323 
324         String prefix = "";
325         if ( includeBaseDir )
326         {
327             prefix = finalName;
328         }
329 
330         archiver = new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers,
331                                               configSource.getWorkingDirectory() );
332         if ( configSource.isDryRun() )
333         {
334             archiver = new DryRunArchiver( archiver, LOGGER );
335         }
336 
337         archiver.setUseJvmChmod( configSource.isUpdateOnly() );
338         archiver.setIgnorePermissions( configSource.isIgnorePermissions() );
339         archiver.setForced( !configSource.isUpdateOnly() );
340 
341         // configure for Reproducible Builds based on outputTimestamp value
342         if ( outputTimestamp != null )
343         {
344             archiver.configureReproducible( outputTimestamp );
345         }
346 
347         if ( configSource.getOverrideUid() != null )
348         {
349             archiver.setOverrideUid( configSource.getOverrideUid() );
350         }
351         if ( StringUtils.isNotBlank( configSource.getOverrideUserName() ) )
352         {
353             archiver.setOverrideUserName( StringUtils.trim( configSource.getOverrideUserName() ) );
354         }
355         if ( configSource.getOverrideGid() != null )
356         {
357             archiver.setOverrideGid( configSource.getOverrideGid() );
358         }
359         if ( StringUtils.isNotBlank( configSource.getOverrideGroupName() ) )
360         {
361             archiver.setOverrideGroupName( StringUtils.trim( configSource.getOverrideGroupName() ) );
362         }
363 
364         return archiver;
365     }
366 
367     private void configureContainerDescriptorHandler( final ContainerDescriptorHandler handler, final Xpp3Dom config,
368                                                       final AssemblerConfigurationSource configSource )
369         throws InvalidAssemblerConfigurationException
370     {
371         LOGGER.debug( "Configuring handler: '" + handler.getClass().getName() + "' -->" );
372 
373         try
374         {
375             configureComponent( handler, config, configSource );
376         }
377         catch ( final ComponentConfigurationException e )
378         {
379             throw new InvalidAssemblerConfigurationException(
380                 "Failed to configure handler: " + handler.getClass().getName(), e );
381         }
382         catch ( final ComponentLookupException e )
383         {
384             throw new InvalidAssemblerConfigurationException(
385                 "Failed to lookup configurator for setup of handler: " + handler.getClass().getName(), e );
386         }
387 
388         LOGGER.debug( "-- end configuration --" );
389     }
390 
391     private void configureArchiver( final Archiver archiver, final AssemblerConfigurationSource configSource )
392     {
393         Xpp3Dom config;
394         try
395         {
396             config = Xpp3DomBuilder.build( new StringReader( configSource.getArchiverConfig() ) );
397         }
398         catch ( final XmlPullParserException | IOException e )
399         {
400             throw new ArchiverException( "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
401                                          e );
402         }
403 
404         LOGGER.debug( "Configuring archiver: '" + archiver.getClass().getName() + "' -->" );
405 
406         try
407         {
408             configureComponent( archiver, config, configSource );
409         }
410         catch ( final ComponentConfigurationException e )
411         {
412             throw new ArchiverException( "Failed to configure archiver: " + archiver.getClass().getName(), e );
413         }
414         catch ( final ComponentLookupException e )
415         {
416             throw new ArchiverException(
417                 "Failed to lookup configurator for setup of archiver: " + archiver.getClass().getName(), e );
418         }
419 
420         LOGGER.debug( "-- end configuration --" );
421     }
422 
423     private void configureComponent( final Object component, final Xpp3Dom config,
424                                      final AssemblerConfigurationSource configSource )
425         throws ComponentLookupException, ComponentConfigurationException
426     {
427         final ComponentConfigurator configurator = container.lookup( ComponentConfigurator.class, "basic" );
428 
429         final ConfigurationListener listener = new DebugConfigurationListener( LOGGER );
430 
431         final ExpressionEvaluator expressionEvaluator = new AssemblyExpressionEvaluator( configSource );
432 
433         final XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( config );
434 
435         final Object[] containerRealm = getContainerRealm();
436 
437         /*
438          * NOTE: The signature of configureComponent() has changed in Maven 3.x, the reflection prevents a linkage error
439          * and makes the code work with both Maven 2 and 3.
440          */
441         try
442         {
443             final Method configureComponent =
444                 ComponentConfigurator.class.getMethod( "configureComponent", Object.class, PlexusConfiguration.class,
445                                                        ExpressionEvaluator.class, (Class<?>) containerRealm[1],
446                                                        ConfigurationListener.class );
447 
448             configureComponent.invoke( configurator, component, configuration, expressionEvaluator, containerRealm[0],
449                                        listener );
450         }
451         catch ( final NoSuchMethodException | IllegalAccessException e )
452         {
453             throw new RuntimeException( e );
454         }
455         catch ( final InvocationTargetException e )
456         {
457             if ( e.getCause() instanceof ComponentConfigurationException )
458             {
459                 throw (ComponentConfigurationException) e.getCause();
460             }
461             throw new RuntimeException( e.getCause() );
462         }
463     }
464 
465     private Object[] getContainerRealm()
466     {
467         /*
468          * NOTE: The return type of getContainerRealm() has changed in Maven 3.x, the reflection prevents a linkage
469          * error and makes the code work with both Maven 2 and 3.
470          */
471         try
472         {
473             final Method getContainerRealm = container.getClass().getMethod( "getContainerRealm" );
474             return new Object[]{ getContainerRealm.invoke( container ), getContainerRealm.getReturnType() };
475         }
476         catch ( final NoSuchMethodException | IllegalAccessException e )
477         {
478             throw new RuntimeException( e );
479         }
480         catch ( final InvocationTargetException e )
481         {
482             throw new RuntimeException( e.getCause() );
483         }
484     }
485 
486     protected Archiver createWarArchiver()
487         throws NoSuchArchiverException
488     {
489         final WarArchiver warArchiver = (WarArchiver) archiverManager.getArchiver( "war" );
490         warArchiver.setIgnoreWebxml( false ); // See MNG-1274
491 
492         return warArchiver;
493     }
494 
495     protected Archiver createTarArchiver( final String format, final TarLongFileMode tarLongFileMode )
496         throws NoSuchArchiverException
497     {
498         final TarArchiver tarArchiver = (TarArchiver) archiverManager.getArchiver( "tar" );
499         final int index = format.indexOf( '.' );
500         if ( index >= 0 )
501         {
502             TarArchiver.TarCompressionMethod tarCompressionMethod;
503             // TODO: this should accept gz and bz2 as well so we can skip
504             // TODO: over the switch
505             final String compression = format.substring( index + 1 );
506             if ( "gz".equals( compression ) )
507             {
508                 tarCompressionMethod = TarArchiver.TarCompressionMethod.gzip;
509             }
510             else if ( "bz2".equals( compression ) )
511             {
512                 tarCompressionMethod = TarArchiver.TarCompressionMethod.bzip2;
513             }
514             else if ( "xz".equals( compression ) )
515             {
516                 tarCompressionMethod = TarArchiver.TarCompressionMethod.xz;
517             }
518             else if ( "snappy".equals( compression ) )
519             {
520                 tarCompressionMethod = TarArchiver.TarCompressionMethod.snappy;
521             }
522             else
523             {
524                 // TODO: better handling
525                 throw new IllegalArgumentException( "Unknown compression format: " + compression );
526             }
527             tarArchiver.setCompression( tarCompressionMethod );
528         }
529         else if ( "tgz".equals( format ) )
530         {
531             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.gzip );
532         }
533         else if ( "tbz2".equals( format ) )
534         {
535             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.bzip2 );
536         }
537         else if ( "txz".equals( format ) )
538         {
539             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.xz );
540         }
541 
542         tarArchiver.setLongfile( tarLongFileMode );
543 
544         return tarArchiver;
545     }
546 }