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