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