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   *
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<>( 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 | IOException e )
187         {
188             throw new ArchiveCreationException(
189                 "Error creating assembly archive " + assembly.getId() + ": " + e.getMessage(), e );
190         }
191         catch ( final NoSuchArchiverException e )
192         {
193             throw new ArchiveCreationException(
194                 "Unable to obtain archiver for extension '" + format + "', for assembly: '" + assembly.getId() + "'",
195                 e );
196         }
197         catch ( final DependencyResolutionException e )
198         {
199             throw new ArchiveCreationException(
200                 "Unable to resolve dependencies for assembly '" + assembly.getId() + "'", e );
201         }
202 
203         return destFile;
204     }
205 
206     private void validate( final Assembly assembly )
207         throws InvalidAssemblerConfigurationException
208     {
209         if ( assembly.getId() == null || assembly.getId().trim().length() < 1 )
210         {
211             throw new InvalidAssemblerConfigurationException( "Assembly ID must be present and non-empty." );
212         }
213     }
214 
215     // CHECKSTYLE_OFF: LineLength
216     private List<ContainerDescriptorHandler> selectContainerDescriptorHandlers(
217         List<ContainerDescriptorHandlerConfig> requestedContainerDescriptorHandlers,
218         final AssemblerConfigurationSource configSource )
219         throws InvalidAssemblerConfigurationException
220     // CHECKSTYLE_ON: LineLength
221     {
222         getLogger().debug( "All known ContainerDescriptorHandler components: " + ( containerDescriptorHandlers == null
223             ? "none; map is null."
224             : "" + containerDescriptorHandlers.keySet() ) );
225 
226         if ( requestedContainerDescriptorHandlers == null )
227         {
228             requestedContainerDescriptorHandlers = new ArrayList<>();
229         }
230 
231         final List<ContainerDescriptorHandler> handlers = new ArrayList<>();
232         final List<String> hints = new ArrayList<>();
233 
234         if ( !requestedContainerDescriptorHandlers.isEmpty() )
235         {
236             for ( final ContainerDescriptorHandlerConfig config : requestedContainerDescriptorHandlers )
237             {
238                 final String hint = config.getHandlerName();
239                 final ContainerDescriptorHandler handler = containerDescriptorHandlers.get( hint );
240 
241                 if ( handler == null )
242                 {
243                     throw new InvalidAssemblerConfigurationException(
244                         "Cannot find ContainerDescriptorHandler with hint: " + hint );
245                 }
246 
247                 getLogger().debug(
248                     "Found container descriptor handler with hint: " + hint + " (component: " + handler + ")" );
249 
250                 if ( config.getConfiguration() != null )
251                 {
252                     getLogger().debug( "Configuring handler with:\n\n" + config.getConfiguration() + "\n\n" );
253 
254                     configureContainerDescriptorHandler( handler, (Xpp3Dom) config.getConfiguration(), configSource );
255                 }
256 
257                 handlers.add( handler );
258                 hints.add( hint );
259             }
260         }
261 
262         if ( !hints.contains( "plexus" ) )
263         {
264             handlers.add( new ComponentsXmlArchiverFileFilter() );
265         }
266 
267         return handlers;
268     }
269 
270     /**
271      * Creates the necessary archiver to build the distribution file.
272      *
273      * @param format                Archive format
274      * @param includeBaseDir        the base directory for include.
275      * @param finalName             The final name.
276      * @param configSource          {@link AssemblerConfigurationSource}
277      * @param containerHandlers     The list of {@link ContainerDescriptorHandler}
278      * @param recompressZippedFiles recompress zipped files.
279      * @param mergeManifestMode     how to handle already existing Manifest files
280      * @return archiver Archiver generated
281      * @throws org.codehaus.plexus.archiver.ArchiverException
282      * @throws org.codehaus.plexus.archiver.manager.NoSuchArchiverException
283      */
284     protected Archiver createArchiver( final String format, final boolean includeBaseDir, final String finalName,
285                                        final AssemblerConfigurationSource configSource,
286                                        final List<ContainerDescriptorHandler> containerHandlers,
287                                        boolean recompressZippedFiles, String mergeManifestMode )
288         throws NoSuchArchiverException
289     {
290         Archiver archiver;
291         if ( "txz".equals( format ) || "tgz".equals( format ) || "tbz2".equals( format ) || format.startsWith( "tar" ) )
292         {
293             archiver = createTarArchiver( format, TarLongFileMode.valueOf( configSource.getTarLongFileMode() ) );
294         }
295         else if ( "war".equals( format ) )
296         {
297             archiver = createWarArchiver();
298         }
299         else
300         {
301             archiver = archiverManager.getArchiver( format );
302         }
303 
304         if ( archiver instanceof AbstractZipArchiver )
305         {
306             ( (AbstractZipArchiver) archiver ).setRecompressAddedZips( recompressZippedFiles );
307         }
308 
309         final List<FileSelector> extraSelectors = new ArrayList<>();
310         final List<ArchiveFinalizer> extraFinalizers = new ArrayList<>();
311         if ( archiver instanceof JarArchiver )
312         {
313             if ( mergeManifestMode != null )
314             {
315                 ( (JarArchiver) archiver ).setFilesetmanifest(
316                     JarArchiver.FilesetManifestConfig.valueOf( mergeManifestMode ) );
317             }
318 
319             extraSelectors.add( new JarSecurityFileSelector() );
320 
321             extraFinalizers.add(
322                 new ManifestCreationFinalizer( configSource.getMavenSession(), configSource.getProject(),
323                                                configSource.getJarArchiveConfiguration() ) );
324 
325         }
326 
327         if ( configSource.getArchiverConfig() != null )
328         {
329             configureArchiver( archiver, configSource );
330         }
331 
332         String prefix = "";
333         if ( includeBaseDir )
334         {
335             prefix = finalName;
336         }
337 
338         archiver = new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers,
339                                               configSource.getWorkingDirectory(), getLogger() );
340         if ( configSource.isDryRun() )
341         {
342             archiver = new DryRunArchiver( archiver, getLogger() );
343         }
344 
345         archiver.setUseJvmChmod( configSource.isUpdateOnly() );
346         archiver.setIgnorePermissions( configSource.isIgnorePermissions() );
347         archiver.setForced( !configSource.isUpdateOnly() );
348 
349         return archiver;
350     }
351 
352     private void configureContainerDescriptorHandler( final ContainerDescriptorHandler handler, final Xpp3Dom config,
353                                                       final AssemblerConfigurationSource configSource )
354         throws InvalidAssemblerConfigurationException
355     {
356         getLogger().debug( "Configuring handler: '" + handler.getClass().getName() + "' -->" );
357 
358         try
359         {
360             configureComponent( handler, config, configSource );
361         }
362         catch ( final ComponentConfigurationException e )
363         {
364             throw new InvalidAssemblerConfigurationException(
365                 "Failed to configure handler: " + handler.getClass().getName(), e );
366         }
367         catch ( final ComponentLookupException e )
368         {
369             throw new InvalidAssemblerConfigurationException(
370                 "Failed to lookup configurator for setup of handler: " + handler.getClass().getName(), e );
371         }
372 
373         getLogger().debug( "-- end configuration --" );
374     }
375 
376     private void configureArchiver( final Archiver archiver, final AssemblerConfigurationSource configSource )
377     {
378         Xpp3Dom config;
379         try
380         {
381             config = Xpp3DomBuilder.build( new StringReader( configSource.getArchiverConfig() ) );
382         }
383         catch ( final XmlPullParserException | IOException e )
384         {
385             throw new ArchiverException( "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
386                                          e );
387         }
388 
389         getLogger().debug( "Configuring archiver: '" + archiver.getClass().getName() + "' -->" );
390 
391         try
392         {
393             configureComponent( archiver, config, configSource );
394         }
395         catch ( final ComponentConfigurationException e )
396         {
397             throw new ArchiverException( "Failed to configure archiver: " + archiver.getClass().getName(), e );
398         }
399         catch ( final ComponentLookupException e )
400         {
401             throw new ArchiverException(
402                 "Failed to lookup configurator for setup of archiver: " + archiver.getClass().getName(), e );
403         }
404 
405         getLogger().debug( "-- end configuration --" );
406     }
407 
408     private void configureComponent( final Object component, final Xpp3Dom config,
409                                      final AssemblerConfigurationSource configSource )
410         throws ComponentLookupException, ComponentConfigurationException
411     {
412         final ComponentConfigurator configurator = container.lookup( ComponentConfigurator.class, "basic" );
413 
414         final ConfigurationListener listener = new DebugConfigurationListener( getLogger() );
415 
416         final ExpressionEvaluator expressionEvaluator = new AssemblyExpressionEvaluator( configSource );
417 
418         final XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( config );
419 
420         final Object[] containerRealm = getContainerRealm();
421 
422         /*
423          * NOTE: The signature of configureComponent() has changed in Maven 3.x, the reflection prevents a linkage error
424          * and makes the code work with both Maven 2 and 3.
425          */
426         try
427         {
428             final Method configureComponent =
429                 ComponentConfigurator.class.getMethod( "configureComponent", Object.class, PlexusConfiguration.class,
430                                                        ExpressionEvaluator.class, (Class<?>) containerRealm[1],
431                                                        ConfigurationListener.class );
432 
433             configureComponent.invoke( configurator, component, configuration, expressionEvaluator, containerRealm[0],
434                                        listener );
435         }
436         catch ( final NoSuchMethodException | IllegalAccessException e )
437         {
438             throw new RuntimeException( e );
439         }
440         catch ( final InvocationTargetException e )
441         {
442             if ( e.getCause() instanceof ComponentConfigurationException )
443             {
444                 throw (ComponentConfigurationException) e.getCause();
445             }
446             throw new RuntimeException( e.getCause() );
447         }
448     }
449 
450     private Object[] getContainerRealm()
451     {
452         /*
453          * NOTE: The return type of getContainerRealm() has changed in Maven 3.x, the reflection prevents a linkage
454          * error and makes the code work with both Maven 2 and 3.
455          */
456         try
457         {
458             final Method getContainerRealm = container.getClass().getMethod( "getContainerRealm" );
459             return new Object[]{ getContainerRealm.invoke( container ), getContainerRealm.getReturnType() };
460         }
461         catch ( final NoSuchMethodException | IllegalAccessException e )
462         {
463             throw new RuntimeException( e );
464         }
465         catch ( final InvocationTargetException e )
466         {
467             throw new RuntimeException( e.getCause() );
468         }
469     }
470 
471     protected Archiver createWarArchiver()
472         throws NoSuchArchiverException
473     {
474         final WarArchiver warArchiver = (WarArchiver) archiverManager.getArchiver( "war" );
475         warArchiver.setIgnoreWebxml( false ); // See MNG-1274
476 
477         return warArchiver;
478     }
479 
480     protected Archiver createTarArchiver( final String format, final TarLongFileMode tarLongFileMode )
481         throws NoSuchArchiverException
482     {
483         final TarArchiver tarArchiver = (TarArchiver) archiverManager.getArchiver( "tar" );
484         final int index = format.indexOf( '.' );
485         if ( index >= 0 )
486         {
487             TarArchiver.TarCompressionMethod tarCompressionMethod;
488             // TODO: this should accept gz and bz2 as well so we can skip
489             // TODO: over the switch
490             final String compression = format.substring( index + 1 );
491             if ( "gz".equals( compression ) )
492             {
493                 tarCompressionMethod = TarArchiver.TarCompressionMethod.gzip;
494             }
495             else if ( "bz2".equals( compression ) )
496             {
497                 tarCompressionMethod = TarArchiver.TarCompressionMethod.bzip2;
498             }
499             else if ( "xz".equals( compression ) )
500             {
501                 tarCompressionMethod = TarArchiver.TarCompressionMethod.xz;
502             }
503             else if ( "snappy".equals( compression ) )
504             {
505                 tarCompressionMethod = TarArchiver.TarCompressionMethod.snappy;
506             }
507             else
508             {
509                 // TODO: better handling
510                 throw new IllegalArgumentException( "Unknown compression format: " + compression );
511             }
512             tarArchiver.setCompression( tarCompressionMethod );
513         }
514         else if ( "tgz".equals( format ) )
515         {
516             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.gzip );
517         }
518         else if ( "tbz2".equals( format ) )
519         {
520             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.bzip2 );
521         }
522         else if ( "txz".equals( format ) )
523         {
524             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.xz );
525         }
526 
527         tarArchiver.setLongfile( tarLongFileMode );
528 
529         return tarArchiver;
530     }
531 
532     @Override
533     public void contextualize( final Context context )
534         throws ContextException
535     {
536         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
537     }
538 
539     protected void setContainer( final PlexusContainer container )
540     {
541         this.container = container;
542     }
543 
544 }