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