View Javadoc
1   package org.apache.maven.plugin.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.plugin.assembly.AssemblerConfigurationSource;
24  import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException;
25  import org.apache.maven.plugin.assembly.archive.archiver.AssemblyProxyArchiver;
26  import org.apache.maven.plugin.assembly.archive.phase.AssemblyArchiverPhase;
27  import org.apache.maven.plugin.assembly.archive.phase.AssemblyArchiverPhaseComparator;
28  import org.apache.maven.plugin.assembly.artifact.DependencyResolutionException;
29  import org.apache.maven.plugin.assembly.filter.ComponentsXmlArchiverFileFilter;
30  import org.apache.maven.plugin.assembly.filter.ContainerDescriptorHandler;
31  import org.apache.maven.plugin.assembly.format.AssemblyFormattingException;
32  import org.apache.maven.plugin.assembly.interpolation.AssemblyExpressionEvaluator;
33  import org.apache.maven.plugin.assembly.model.Assembly;
34  import org.apache.maven.plugin.assembly.model.ContainerDescriptorHandlerConfig;
35  import org.apache.maven.plugin.assembly.utils.AssemblyFileUtils;
36  import org.apache.maven.plugin.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 AssemblyArchiverPhase} instances to
82   * interpret the various sections of the assembly descriptor and determine which files to add, and other associated
83   * activities.
84   *
85   * @version $Id: DefaultAssemblyArchiver.java 1706743 2015-10-05 00:46:32Z bimargulies $
86   */
87  @Component( role = AssemblyArchiver.class )
88  public class DefaultAssemblyArchiver
89      extends AbstractLogEnabled
90      implements AssemblyArchiver, Contextualizable
91  {
92  
93      @Requirement
94      private ArchiverManager archiverManager;
95  
96      @Requirement( role = AssemblyArchiverPhase.class )
97      private List<AssemblyArchiverPhase> assemblyPhases;
98  
99      @SuppressWarnings( "MismatchedQueryAndUpdateOfCollection" )
100     @Requirement( role = ContainerDescriptorHandler.class )
101     private Map<String, ContainerDescriptorHandler> containerDescriptorHandlers;
102 
103     private PlexusContainer container;
104 
105     @SuppressWarnings( "UnusedDeclaration" )
106     public DefaultAssemblyArchiver()
107     {
108     }
109 
110     // introduced for testing.
111 
112     /**
113      * @param archiverManager The archive manager.
114      * @param assemblyPhases  The list of {@link AssemblyArchiverPhase}
115      */
116     protected DefaultAssemblyArchiver( final ArchiverManager archiverManager,
117                                        final List<AssemblyArchiverPhase> assemblyPhases )
118     {
119         this.archiverManager = archiverManager;
120         this.assemblyPhases = assemblyPhases;
121     }
122 
123     private List<AssemblyArchiverPhase> sortedPhases()
124     {
125         List<AssemblyArchiverPhase> sorted = new ArrayList<AssemblyArchiverPhase>( assemblyPhases );
126         Collections.sort( sorted, new AssemblyArchiverPhaseComparator() );
127         return sorted;
128     }
129 
130     /**
131      * {@inheritDoc}
132      */
133     public File createArchive( final Assembly assembly, final String fullName, final String format,
134                                final AssemblerConfigurationSource configSource, boolean recompressZippedFiles )
135         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
136     {
137         validate( assembly );
138 
139         String filename = fullName;
140         if ( !configSource.isIgnoreDirFormatExtensions() || !format.startsWith( "dir" ) )
141         {
142             filename += "." + format;
143         }
144 
145         AssemblyFileUtils.verifyTempDirectoryAvailability( configSource.getTemporaryRootDirectory() );
146 
147         final File outputDirectory = configSource.getOutputDirectory();
148 
149         final File destFile = new File( outputDirectory, filename );
150 
151         try
152         {
153             final String finalName = configSource.getFinalName();
154             final String specifiedBasedir = assembly.getBaseDirectory();
155 
156             String basedir = finalName;
157 
158             if ( specifiedBasedir != null )
159             {
160                 basedir = AssemblyFormatUtils.getOutputDirectory( specifiedBasedir, finalName, configSource,
161                                                                   AssemblyFormatUtils.moduleProjectInterpolator(
162                                                                       configSource.getProject() ),
163                                                                   AssemblyFormatUtils.artifactProjectInterpolator(
164                                                                       null ) );
165             }
166 
167             final List<ContainerDescriptorHandler> containerHandlers =
168                 selectContainerDescriptorHandlers( assembly.getContainerDescriptorHandlers(), configSource );
169 
170             final Archiver archiver =
171                 createArchiver( format, assembly.isIncludeBaseDirectory(), basedir, configSource, containerHandlers,
172                                 recompressZippedFiles );
173 
174             archiver.setDestFile( destFile );
175 
176             for ( AssemblyArchiverPhase phase : sortedPhases() )
177             {
178                 phase.execute( assembly, archiver, configSource );
179             }
180 
181             archiver.createArchive();
182         }
183         catch ( final ArchiverException e )
184         {
185             throw new ArchiveCreationException(
186                 "Error creating assembly archive " + assembly.getId() + ": " + e.getMessage(), e );
187         }
188         catch ( final 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<ContainerDescriptorHandlerConfig>();
231         }
232 
233         final List<ContainerDescriptorHandler> handlers = new ArrayList<ContainerDescriptorHandler>();
234         final List<String> hints = new ArrayList<String>();
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      * @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 )
289         throws NoSuchArchiverException
290     {
291         Archiver archiver;
292         if ( "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<FileSelector>();
311         final List<ArchiveFinalizer> extraFinalizers = new ArrayList<ArchiveFinalizer>();
312         if ( archiver instanceof JarArchiver )
313         {
314             extraSelectors.add( new JarSecurityFileSelector() );
315 
316             extraFinalizers.add(
317                 new ManifestCreationFinalizer( configSource.getMavenSession(), configSource.getProject(),
318                                                configSource.getJarArchiveConfiguration() ) );
319 
320         }
321 
322         if ( configSource.getArchiverConfig() != null )
323         {
324             configureArchiver( archiver, configSource );
325         }
326 
327         String prefix = "";
328         if ( includeBaseDir )
329         {
330             prefix = finalName;
331         }
332 
333         archiver = new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers,
334                                               configSource.getWorkingDirectory(), getLogger() );
335         if ( configSource.isDryRun() )
336         {
337             archiver = new DryRunArchiver( archiver, getLogger() );
338         }
339 
340         archiver.setUseJvmChmod( configSource.isUpdateOnly() );
341         archiver.setIgnorePermissions( configSource.isIgnorePermissions() );
342         archiver.setForced( !configSource.isUpdateOnly() );
343 
344         return archiver;
345     }
346 
347     private void configureContainerDescriptorHandler( final ContainerDescriptorHandler handler, final Xpp3Dom config,
348                                                       final AssemblerConfigurationSource configSource )
349         throws InvalidAssemblerConfigurationException
350     {
351         getLogger().debug( "Configuring handler: '" + handler.getClass().getName() + "' -->" );
352 
353         try
354         {
355             configureComponent( handler, config, configSource );
356         }
357         catch ( final ComponentConfigurationException e )
358         {
359             throw new InvalidAssemblerConfigurationException(
360                 "Failed to configure handler: " + handler.getClass().getName(), e );
361         }
362         catch ( final ComponentLookupException e )
363         {
364             throw new InvalidAssemblerConfigurationException(
365                 "Failed to lookup configurator for setup of handler: " + handler.getClass().getName(), e );
366         }
367 
368         getLogger().debug( "-- end configuration --" );
369     }
370 
371     private void configureArchiver( final Archiver archiver, final AssemblerConfigurationSource configSource )
372     {
373         Xpp3Dom config;
374         try
375         {
376             config = Xpp3DomBuilder.build( new StringReader( configSource.getArchiverConfig() ) );
377         }
378         catch ( final XmlPullParserException e )
379         {
380             throw new ArchiverException( "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
381                                          e );
382         }
383         catch ( final 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 =
413             (ComponentConfigurator) container.lookup( ComponentConfigurator.ROLE, "basic" );
414 
415         final ConfigurationListener listener = new DebugConfigurationListener( getLogger() );
416 
417         final ExpressionEvaluator expressionEvaluator = new AssemblyExpressionEvaluator( configSource );
418 
419         final XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( config );
420 
421         final Object[] containerRealm = getContainerRealm();
422 
423         /*
424          * NOTE: The signature of configureComponent() has changed in Maven 3.x, the reflection prevents a linkage error
425          * and makes the code work with both Maven 2 and 3.
426          */
427         try
428         {
429             final Method configureComponent =
430                 ComponentConfigurator.class.getMethod( "configureComponent", Object.class, PlexusConfiguration.class,
431                                                        ExpressionEvaluator.class, (Class<?>) containerRealm[1],
432                                                        ConfigurationListener.class );
433 
434             configureComponent.invoke( configurator, component, configuration, expressionEvaluator, containerRealm[0],
435                                        listener );
436         }
437         catch ( final NoSuchMethodException e )
438         {
439             throw new RuntimeException( e );
440         }
441         catch ( final IllegalAccessException e )
442         {
443             throw new RuntimeException( e );
444         }
445         catch ( final InvocationTargetException e )
446         {
447             if ( e.getCause() instanceof ComponentConfigurationException )
448             {
449                 throw (ComponentConfigurationException) e.getCause();
450             }
451             throw new RuntimeException( e.getCause() );
452         }
453     }
454 
455     private Object[] getContainerRealm()
456     {
457         /*
458          * NOTE: The return type of getContainerRealm() has changed in Maven 3.x, the reflection prevents a linkage
459          * error and makes the code work with both Maven 2 and 3.
460          */
461         try
462         {
463             final Method getContainerRealm = container.getClass().getMethod( "getContainerRealm" );
464             return new Object[]{ getContainerRealm.invoke( container ), getContainerRealm.getReturnType() };
465         }
466         catch ( final NoSuchMethodException e )
467         {
468             throw new RuntimeException( e );
469         }
470         catch ( final IllegalAccessException e )
471         {
472             throw new RuntimeException( e );
473         }
474         catch ( final InvocationTargetException e )
475         {
476             throw new RuntimeException( e.getCause() );
477         }
478     }
479 
480     protected Archiver createWarArchiver()
481         throws NoSuchArchiverException
482     {
483         final WarArchiver warArchiver = (WarArchiver) archiverManager.getArchiver( "war" );
484         warArchiver.setIgnoreWebxml( false ); // See MNG-1274
485 
486         return warArchiver;
487     }
488 
489     protected Archiver createTarArchiver( final String format, final TarLongFileMode tarLongFileMode )
490         throws NoSuchArchiverException
491     {
492         final TarArchiver tarArchiver = (TarArchiver) archiverManager.getArchiver( "tar" );
493         final int index = format.indexOf( '.' );
494         if ( index >= 0 )
495         {
496             TarArchiver.TarCompressionMethod tarCompressionMethod;
497             // TODO: this should accept gz and bz2 as well so we can skip
498             // TODO: over the switch
499             final String compression = format.substring( index + 1 );
500             if ( "gz".equals( compression ) )
501             {
502                 tarCompressionMethod = TarArchiver.TarCompressionMethod.gzip;
503             }
504             else if ( "bz2".equals( compression ) )
505             {
506                 tarCompressionMethod = TarArchiver.TarCompressionMethod.bzip2;
507             }
508             else if ( "snappy".equals( compression ) )
509             {
510                tarCompressionMethod = TarArchiver.TarCompressionMethod.snappy;
511             }
512             else
513             {
514                 // TODO: better handling
515                 throw new IllegalArgumentException( "Unknown compression format: " + compression );
516             }
517             tarArchiver.setCompression( tarCompressionMethod );
518         }
519         else if ( "tgz".equals( format ) )
520         {
521             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.gzip );
522         }
523         else if ( "tbz2".equals( format ) )
524         {
525             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.bzip2 );
526         }
527 
528         tarArchiver.setLongfile( tarLongFileMode );
529 
530         return tarArchiver;
531     }
532 
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 }