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