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.filters.JarSecurityFileSelector;
43  import org.codehaus.plexus.archiver.jar.JarArchiver;
44  import org.codehaus.plexus.archiver.manager.ArchiverManager;
45  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
46  import org.codehaus.plexus.archiver.tar.TarArchiver;
47  import org.codehaus.plexus.archiver.tar.TarLongFileMode;
48  import org.codehaus.plexus.archiver.war.WarArchiver;
49  import org.codehaus.plexus.archiver.zip.AbstractZipArchiver;
50  import org.codehaus.plexus.component.annotations.Component;
51  import org.codehaus.plexus.component.annotations.Requirement;
52  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
53  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
54  import org.codehaus.plexus.component.configurator.ConfigurationListener;
55  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
56  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
57  import org.codehaus.plexus.components.io.fileselectors.FileSelector;
58  import org.codehaus.plexus.configuration.PlexusConfiguration;
59  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
60  import org.codehaus.plexus.context.Context;
61  import org.codehaus.plexus.context.ContextException;
62  import org.codehaus.plexus.logging.AbstractLogEnabled;
63  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
64  import org.codehaus.plexus.util.xml.Xpp3Dom;
65  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
66  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
67  
68  import java.io.File;
69  import java.io.IOException;
70  import java.io.StringReader;
71  import java.lang.reflect.InvocationTargetException;
72  import java.lang.reflect.Method;
73  import java.util.ArrayList;
74  import java.util.Collections;
75  import java.util.List;
76  import java.util.Map;
77  
78  /**
79   * Controller component designed to organize the many activities involved in creating an assembly archive. This includes
80   * locating and configuring {@link Archiver} instances, executing multiple {@link AssemblyArchiverPhase} instances to
81   * interpret the various sections of the assembly descriptor and determine which files to add, and other associated
82   * activities.
83   *
84   * @version $Id: DefaultAssemblyArchiver.java 1639422 2014-11-13 18:08:07Z krosenvold $
85   */
86  @Component( role = AssemblyArchiver.class )
87  public class DefaultAssemblyArchiver
88      extends AbstractLogEnabled
89      implements AssemblyArchiver, Contextualizable
90  {
91  
92      @Requirement
93      private ArchiverManager archiverManager;
94  
95      @Requirement( role = AssemblyArchiverPhase.class )
96      private List<AssemblyArchiverPhase> assemblyPhases;
97  
98      @SuppressWarnings( "MismatchedQueryAndUpdateOfCollection" )
99      @Requirement( role = ContainerDescriptorHandler.class )
100     private Map<String, ContainerDescriptorHandler> containerDescriptorHandlers;
101 
102     private PlexusContainer container;
103 
104     @SuppressWarnings( "UnusedDeclaration" )
105     public DefaultAssemblyArchiver()
106     {
107     }
108 
109     // introduced for testing.
110 
111     /**
112      * @param archiverManager The archive manager.
113      * @param assemblyPhases  The list of {@link AssemblyArchiverPhase}
114      */
115     protected DefaultAssemblyArchiver( final ArchiverManager archiverManager,
116                                        final List<AssemblyArchiverPhase> assemblyPhases )
117     {
118         this.archiverManager = archiverManager;
119         this.assemblyPhases = assemblyPhases;
120     }
121 
122     private List<AssemblyArchiverPhase> sortedPhases()
123     {
124         List<AssemblyArchiverPhase> sorted = new ArrayList<AssemblyArchiverPhase>( assemblyPhases );
125         Collections.sort( sorted, new AssemblyArchiverPhaseComparator() );
126         return sorted;
127     }
128 
129     /**
130      * {@inheritDoc}
131      */
132     public File createArchive( final Assembly assembly, final String fullName, final String format,
133                                final AssemblerConfigurationSource configSource, boolean recompressZippedFiles )
134         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
135     {
136         validate( assembly );
137 
138         String filename = fullName;
139         if ( !configSource.isIgnoreDirFormatExtensions() || !format.startsWith( "dir" ) )
140         {
141             filename += "." + format;
142         }
143 
144         AssemblyFileUtils.verifyTempDirectoryAvailability( configSource.getTemporaryRootDirectory() );
145 
146         final File outputDirectory = configSource.getOutputDirectory();
147 
148         final File destFile = new File( outputDirectory, filename );
149 
150         try
151         {
152             final String finalName = configSource.getFinalName();
153             final String specifiedBasedir = assembly.getBaseDirectory();
154 
155             String basedir = finalName;
156 
157             if ( specifiedBasedir != null )
158             {
159                 basedir = AssemblyFormatUtils.getOutputDirectory( specifiedBasedir, finalName, configSource,
160                                                                   AssemblyFormatUtils.moduleProjectInterpolator(
161                                                                       configSource.getProject() ),
162                                                                   AssemblyFormatUtils.artifactProjectInterpolator(
163                                                                       null ) );
164             }
165 
166             final List<ContainerDescriptorHandler> containerHandlers =
167                 selectContainerDescriptorHandlers( assembly.getContainerDescriptorHandlers(), configSource );
168 
169             final Archiver archiver =
170                 createArchiver( format, assembly.isIncludeBaseDirectory(), basedir, configSource, containerHandlers,
171                                 recompressZippedFiles );
172 
173             archiver.setDestFile( destFile );
174 
175             for ( AssemblyArchiverPhase phase : sortedPhases() )
176             {
177                 phase.execute( assembly, archiver, configSource );
178             }
179 
180             archiver.createArchive();
181         }
182         catch ( final ArchiverException e )
183         {
184             throw new ArchiveCreationException(
185                 "Error creating assembly archive " + assembly.getId() + ": " + e.getMessage(), e );
186         }
187         catch ( final 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<ContainerDescriptorHandlerConfig>();
230         }
231 
232         final List<ContainerDescriptorHandler> handlers = new ArrayList<ContainerDescriptorHandler>();
233         final List<String> hints = new ArrayList<String>();
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      * @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 )
288         throws ArchiverException, NoSuchArchiverException
289     {
290         Archiver archiver;
291         if ( "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<FileSelector>();
310         final List<ArchiveFinalizer> extraFinalizers = new ArrayList<ArchiveFinalizer>();
311         if ( archiver instanceof JarArchiver )
312         {
313             extraSelectors.add( new JarSecurityFileSelector() );
314 
315             extraFinalizers.add(
316                 new ManifestCreationFinalizer( configSource.getMavenSession(), configSource.getProject(),
317                                                configSource.getJarArchiveConfiguration() ) );
318 
319         }
320 
321         if ( configSource.getArchiverConfig() != null )
322         {
323             configureArchiver( archiver, configSource );
324         }
325 
326         String prefix = "";
327         if ( includeBaseDir )
328         {
329             prefix = finalName;
330         }
331 
332         archiver = new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers,
333                                               configSource.getWorkingDirectory(), getLogger(),
334                                               configSource.isDryRun() );
335 
336         archiver.setUseJvmChmod( configSource.isUpdateOnly() );
337         archiver.setIgnorePermissions( configSource.isIgnorePermissions() );
338         archiver.setForced( !configSource.isUpdateOnly() );
339 
340         return archiver;
341     }
342 
343     private void configureContainerDescriptorHandler( final ContainerDescriptorHandler handler, final Xpp3Dom config,
344                                                       final AssemblerConfigurationSource configSource )
345         throws InvalidAssemblerConfigurationException
346     {
347         getLogger().debug( "Configuring handler: '" + handler.getClass().getName() + "' -->" );
348 
349         try
350         {
351             configureComponent( handler, config, configSource );
352         }
353         catch ( final ComponentConfigurationException e )
354         {
355             throw new InvalidAssemblerConfigurationException(
356                 "Failed to configure handler: " + handler.getClass().getName(), e );
357         }
358         catch ( final ComponentLookupException e )
359         {
360             throw new InvalidAssemblerConfigurationException(
361                 "Failed to lookup configurator for setup of handler: " + handler.getClass().getName(), e );
362         }
363 
364         getLogger().debug( "-- end configuration --" );
365     }
366 
367     private void configureArchiver( final Archiver archiver, final AssemblerConfigurationSource configSource )
368         throws ArchiverException
369     {
370         Xpp3Dom config;
371         try
372         {
373             config = Xpp3DomBuilder.build( new StringReader( configSource.getArchiverConfig() ) );
374         }
375         catch ( final XmlPullParserException e )
376         {
377             throw new ArchiverException( "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
378                                          e );
379         }
380         catch ( final IOException e )
381         {
382             throw new ArchiverException( "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
383                                          e );
384         }
385 
386         getLogger().debug( "Configuring archiver: '" + archiver.getClass().getName() + "' -->" );
387 
388         try
389         {
390             configureComponent( archiver, config, configSource );
391         }
392         catch ( final ComponentConfigurationException e )
393         {
394             throw new ArchiverException( "Failed to configure archiver: " + archiver.getClass().getName(), e );
395         }
396         catch ( final ComponentLookupException e )
397         {
398             throw new ArchiverException(
399                 "Failed to lookup configurator for setup of archiver: " + archiver.getClass().getName(), e );
400         }
401 
402         getLogger().debug( "-- end configuration --" );
403     }
404 
405     private void configureComponent( final Object component, final Xpp3Dom config,
406                                      final AssemblerConfigurationSource configSource )
407         throws ComponentLookupException, ComponentConfigurationException
408     {
409         final ComponentConfigurator configurator =
410             (ComponentConfigurator) container.lookup( ComponentConfigurator.ROLE, "basic" );
411 
412         final ConfigurationListener listener = new DebugConfigurationListener( getLogger() );
413 
414         final ExpressionEvaluator expressionEvaluator = new AssemblyExpressionEvaluator( configSource );
415 
416         final XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( config );
417 
418         final Object[] containerRealm = getContainerRealm();
419 
420         /*
421          * NOTE: The signature of configureComponent() has changed in Maven 3.x, the reflection prevents a linkage error
422          * and makes the code work with both Maven 2 and 3.
423          */
424         try
425         {
426             final Method configureComponent =
427                 ComponentConfigurator.class.getMethod( "configureComponent", Object.class, PlexusConfiguration.class,
428                                                        ExpressionEvaluator.class, (Class<?>) containerRealm[1],
429                                                        ConfigurationListener.class );
430 
431             configureComponent.invoke( configurator, component, configuration, expressionEvaluator, containerRealm[0],
432                                        listener );
433         }
434         catch ( final NoSuchMethodException e )
435         {
436             throw new RuntimeException( e );
437         }
438         catch ( final IllegalAccessException e )
439         {
440             throw new RuntimeException( e );
441         }
442         catch ( final InvocationTargetException e )
443         {
444             if ( e.getCause() instanceof ComponentConfigurationException )
445             {
446                 throw (ComponentConfigurationException) e.getCause();
447             }
448             throw new RuntimeException( e.getCause() );
449         }
450     }
451 
452     private Object[] getContainerRealm()
453     {
454         /*
455          * NOTE: The return type of getContainerRealm() has changed in Maven 3.x, the reflection prevents a linkage
456          * error and makes the code work with both Maven 2 and 3.
457          */
458         try
459         {
460             final Method getContainerRealm = container.getClass().getMethod( "getContainerRealm" );
461             return new Object[]{ getContainerRealm.invoke( container ), getContainerRealm.getReturnType() };
462         }
463         catch ( final NoSuchMethodException e )
464         {
465             throw new RuntimeException( e );
466         }
467         catch ( final IllegalAccessException e )
468         {
469             throw new RuntimeException( e );
470         }
471         catch ( final InvocationTargetException e )
472         {
473             throw new RuntimeException( e.getCause() );
474         }
475     }
476 
477     protected Archiver createWarArchiver()
478         throws NoSuchArchiverException
479     {
480         final WarArchiver warArchiver = (WarArchiver) archiverManager.getArchiver( "war" );
481         warArchiver.setIgnoreWebxml( false ); // See MNG-1274
482 
483         return warArchiver;
484     }
485 
486     protected Archiver createTarArchiver( final String format, final TarLongFileMode tarLongFileMode )
487         throws NoSuchArchiverException, ArchiverException
488     {
489         final TarArchiver tarArchiver = (TarArchiver) archiverManager.getArchiver( "tar" );
490         final int index = format.indexOf( '.' );
491         if ( index >= 0 )
492         {
493             TarArchiver.TarCompressionMethod tarCompressionMethod;
494             // TODO: this should accept gz and bz2 as well so we can skip
495             // over the switch
496             final String compression = format.substring( index + 1 );
497             if ( "gz".equals( compression ) )
498             {
499                 tarCompressionMethod = TarArchiver.TarCompressionMethod.gzip;
500             }
501             else if ( "bz2".equals( compression ) )
502             {
503                 tarCompressionMethod = TarArchiver.TarCompressionMethod.bzip2;
504             }
505             else
506             {
507                 // TODO: better handling
508                 throw new IllegalArgumentException( "Unknown compression format: " + compression );
509             }
510             tarArchiver.setCompression( tarCompressionMethod );
511         }
512         else if ( "tgz".equals( format ) )
513         {
514             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.gzip );
515         }
516         else if ( "tbz2".equals( format ) )
517         {
518             tarArchiver.setCompression( TarArchiver.TarCompressionMethod.bzip2 );
519         }
520 
521         tarArchiver.setLongfile( tarLongFileMode );
522 
523         return tarArchiver;
524     }
525 
526     public void contextualize( final Context context )
527         throws ContextException
528     {
529         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
530     }
531 
532     protected void setContainer( final PlexusContainer container )
533     {
534         this.container = container;
535     }
536 
537 }