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 java.io.File;
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.maven.plugin.DebugConfigurationListener;
32  import org.apache.maven.plugin.assembly.AssemblerConfigurationSource;
33  import org.apache.maven.plugin.assembly.AssemblyContext;
34  import org.apache.maven.plugin.assembly.DefaultAssemblyContext;
35  import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException;
36  import org.apache.maven.plugin.assembly.archive.archiver.AssemblyProxyArchiver;
37  import org.apache.maven.plugin.assembly.archive.phase.AssemblyArchiverPhase;
38  import org.apache.maven.plugin.assembly.artifact.DependencyResolutionException;
39  import org.apache.maven.plugin.assembly.artifact.DependencyResolver;
40  import org.apache.maven.plugin.assembly.filter.ComponentsXmlArchiverFileFilter;
41  import org.apache.maven.plugin.assembly.filter.ContainerDescriptorHandler;
42  import org.apache.maven.plugin.assembly.format.AssemblyFormattingException;
43  import org.apache.maven.plugin.assembly.interpolation.AssemblyExpressionEvaluator;
44  import org.apache.maven.plugin.assembly.model.Assembly;
45  import org.apache.maven.plugin.assembly.model.ContainerDescriptorHandlerConfig;
46  import org.apache.maven.plugin.assembly.utils.AssemblyFileUtils;
47  import org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils;
48  import org.codehaus.plexus.PlexusConstants;
49  import org.codehaus.plexus.PlexusContainer;
50  import org.codehaus.plexus.archiver.ArchiveFinalizer;
51  import org.codehaus.plexus.archiver.Archiver;
52  import org.codehaus.plexus.archiver.ArchiverException;
53  import org.codehaus.plexus.archiver.filters.JarSecurityFileSelector;
54  import org.codehaus.plexus.archiver.jar.JarArchiver;
55  import org.codehaus.plexus.archiver.manager.ArchiverManager;
56  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
57  import org.codehaus.plexus.archiver.tar.TarArchiver;
58  import org.codehaus.plexus.archiver.tar.TarLongFileMode;
59  import org.codehaus.plexus.archiver.war.WarArchiver;
60  import org.codehaus.plexus.archiver.zip.AbstractZipArchiver;
61  import org.codehaus.plexus.component.annotations.Component;
62  import org.codehaus.plexus.component.annotations.Requirement;
63  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
64  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
65  import org.codehaus.plexus.component.configurator.ConfigurationListener;
66  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
67  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
68  import org.codehaus.plexus.components.io.fileselectors.FileSelector;
69  import org.codehaus.plexus.configuration.PlexusConfiguration;
70  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
71  import org.codehaus.plexus.context.Context;
72  import org.codehaus.plexus.context.ContextException;
73  import org.codehaus.plexus.logging.AbstractLogEnabled;
74  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
75  import org.codehaus.plexus.util.xml.Xpp3Dom;
76  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
77  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
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 1601216 2014-06-08 11:58:09Z khmarbaise $
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
97      private DependencyResolver dependencyResolver;
98  
99      @Requirement( role = AssemblyArchiverPhase.class )
100     private List<AssemblyArchiverPhase> assemblyPhases;
101 
102     @Requirement( role = ContainerDescriptorHandler.class )
103     private Map<String, ContainerDescriptorHandler> containerDescriptorHandlers;
104 
105     private PlexusContainer container;
106 
107     public DefaultAssemblyArchiver()
108     {
109         // needed for plexus
110     }
111 
112     // introduced for testing.
113     protected DefaultAssemblyArchiver( final ArchiverManager archiverManager, final DependencyResolver resolver,
114                                        final List<AssemblyArchiverPhase> assemblyPhases )
115     {
116         this.archiverManager = archiverManager;
117         dependencyResolver = resolver;
118         this.assemblyPhases = assemblyPhases;
119     }
120 
121     /**
122      * Create the assembly archive. Generally:
123      * <ol>
124      * <li>Setup any directory structures for temporary files</li>
125      * <li>Calculate the output directory/file for the assembly</li>
126      * <li>Setup any handler components for special descriptor files we may encounter</li>
127      * <li>Lookup and configure the {@link Archiver} to be used</li>
128      * <li>Determine what, if any, dependency resolution will be required, and resolve any dependency-version conflicts
129      * up front to produce a managed-version map for the whole assembly process.</li>
130      * <li>Iterate through the available {@link AssemblyArchiverPhase} instances, executing each to handle a different
131      * top-level section of the assembly descriptor, if that section is present.</li>
132      * </ol>
133      */
134     public File createArchive( final Assembly assembly, final String fullName, final String format,
135                                final AssemblerConfigurationSource configSource, boolean recompressZippedFiles )
136         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
137     {
138         validate( assembly );
139 
140         String filename = fullName;
141         if ( !configSource.isIgnoreDirFormatExtensions() || !format.startsWith( "dir" ) )
142         {
143             filename += "." + format;
144         }
145 
146         AssemblyFileUtils.verifyTempDirectoryAvailability( configSource.getTemporaryRootDirectory() );
147 
148         final File outputDirectory = configSource.getOutputDirectory();
149 
150         final File destFile = new File( outputDirectory, filename );
151 
152         try
153         {
154             final String finalName = configSource.getFinalName();
155             final String specifiedBasedir = assembly.getBaseDirectory();
156 
157             String basedir = finalName;
158 
159             if ( specifiedBasedir != null )
160             {
161                 basedir =
162                     AssemblyFormatUtils.getOutputDirectory( specifiedBasedir, configSource.getProject(), null,
163                                                             finalName, configSource );
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             final AssemblyContext context = new DefaultAssemblyContext();
176 
177             dependencyResolver.resolve( assembly, configSource, context );
178 
179             for ( AssemblyArchiverPhase phase : assemblyPhases )
180             {
181                 phase.execute( assembly, archiver, configSource, context );
182             }
183 
184             archiver.createArchive();
185         }
186         catch ( final ArchiverException e )
187         {
188             throw new ArchiveCreationException( "Error creating assembly archive " + assembly.getId() + ": "
189                 + e.getMessage(), e );
190         }
191         catch ( final IOException e )
192         {
193             throw new ArchiveCreationException( "Error creating assembly archive " + assembly.getId() + ": "
194                 + e.getMessage(), e );
195         }
196         catch ( final NoSuchArchiverException e )
197         {
198             throw new ArchiveCreationException( "Unable to obtain archiver for extension '" + format
199                 + "', for assembly: '" + assembly.getId() + "'", e );
200         }
201         catch ( final DependencyResolutionException e )
202         {
203             throw new ArchiveCreationException( "Unable to resolve dependencies for assembly '" + assembly.getId()
204                 + "'", e );
205         }
206 
207         return destFile;
208     }
209 
210     private void validate( final Assembly assembly )
211         throws InvalidAssemblerConfigurationException
212     {
213         if ( assembly.getId() == null || assembly.getId().trim().length() < 1 )
214         {
215             throw new InvalidAssemblerConfigurationException( "Assembly ID must be present and non-empty." );
216         }
217     }
218 
219     private List<ContainerDescriptorHandler> selectContainerDescriptorHandlers( List<ContainerDescriptorHandlerConfig> requestedContainerDescriptorHandlers,
220                                                                                 final AssemblerConfigurationSource configSource )
221         throws InvalidAssemblerConfigurationException
222     {
223         getLogger().debug( "All known ContainerDescriptorHandler components: "
224                                + ( containerDescriptorHandlers == null ? "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                 final String hint = config.getHandlerName();
239                 final ContainerDescriptorHandler handler = containerDescriptorHandlers.get(hint);
240 
241                 if (handler == null) {
242                     throw new InvalidAssemblerConfigurationException(
243                             "Cannot find ContainerDescriptorHandler with hint: "
244                                     + hint);
245                 }
246 
247                 getLogger().debug("Found container descriptor handler with hint: " + hint + " (component: " + handler
248                         + ")");
249 
250                 if (config.getConfiguration() != null) {
251                     getLogger().debug("Configuring handler with:\n\n" + config.getConfiguration() + "\n\n");
252 
253                     configureContainerDescriptorHandler(handler, (Xpp3Dom) config.getConfiguration(), configSource);
254                 }
255 
256                 handlers.add(handler);
257                 hints.add(hint);
258             }
259         }
260 
261         if ( !hints.contains( "plexus" ) )
262         {
263             handlers.add( new ComponentsXmlArchiverFileFilter() );
264         }
265 
266         return handlers;
267     }
268 
269     /**
270      * Creates the necessary archiver to build the distribution file.
271      * 
272      * @param format Archive format
273      * @param includeBaseDir
274      * @param finalName
275      * @param configSource
276      * @param containerHandlers
277      * @param recompressZippedFiles
278      * @return archiver Archiver generated
279      * @throws org.codehaus.plexus.archiver.ArchiverException
280      * @throws org.codehaus.plexus.archiver.manager.NoSuchArchiverException
281      */
282     protected Archiver createArchiver( final String format, final boolean includeBaseDir, final String finalName,
283                                        final AssemblerConfigurationSource configSource,
284                                        final List<ContainerDescriptorHandler> containerHandlers,
285                                        boolean recompressZippedFiles )
286         throws ArchiverException, NoSuchArchiverException
287     {
288         Archiver archiver;
289         if ( format.startsWith( "tar" ) )
290         {
291             archiver = createTarArchiver( format, configSource.getTarLongFileMode() );
292         }
293         else if ( "war".equals( format ) )
294         {
295             archiver = createWarArchiver();
296         }
297         else
298         {
299             archiver = archiverManager.getArchiver( format );
300         }
301 
302         if ( archiver instanceof AbstractZipArchiver )
303         {
304             ( (AbstractZipArchiver) archiver ).setRecompressAddedZips( recompressZippedFiles );
305         }
306 
307         final List<FileSelector> extraSelectors = new ArrayList<FileSelector>();
308         final List<ArchiveFinalizer> extraFinalizers = new ArrayList<ArchiveFinalizer>();
309         if ( archiver instanceof JarArchiver )
310         {
311             extraSelectors.add( new JarSecurityFileSelector() );
312 
313             extraFinalizers.add( new ManifestCreationFinalizer( configSource.getMavenSession(),
314                                                                 configSource.getProject(),
315                                                                 configSource.getJarArchiveConfiguration() ) );
316 
317         }
318 
319         if ( configSource.getArchiverConfig() != null )
320         {
321             configureArchiver( archiver, configSource );
322         }
323 
324         String prefix = "";
325         if ( includeBaseDir )
326         {
327             prefix = finalName;
328         }
329 
330         archiver =
331             new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers,
332                                        configSource.getWorkingDirectory(), getLogger(), configSource.isDryRun() );
333 
334         archiver.setUseJvmChmod( configSource.isUpdateOnly() );
335         archiver.setIgnorePermissions( configSource.isIgnorePermissions() );
336         archiver.setForced( !configSource.isUpdateOnly() );
337 
338         return archiver;
339     }
340 
341     private void configureContainerDescriptorHandler( final ContainerDescriptorHandler handler, final Xpp3Dom config,
342                                                       final AssemblerConfigurationSource configSource )
343         throws InvalidAssemblerConfigurationException
344     {
345         getLogger().debug( "Configuring handler: '" + handler.getClass().getName() + "' -->" );
346 
347         try
348         {
349             configureComponent( handler, config, configSource );
350         }
351         catch ( final ComponentConfigurationException e )
352         {
353             throw new InvalidAssemblerConfigurationException( "Failed to configure handler: "
354                 + handler.getClass().getName(), e );
355         }
356         catch ( final ComponentLookupException e )
357         {
358             throw new InvalidAssemblerConfigurationException( "Failed to lookup configurator for setup of handler: "
359                 + handler.getClass().getName(), e );
360         }
361 
362         getLogger().debug( "-- end configuration --" );
363     }
364 
365     private void configureArchiver( final Archiver archiver, final AssemblerConfigurationSource configSource )
366         throws ArchiverException
367     {
368         Xpp3Dom config;
369         try
370         {
371             config = Xpp3DomBuilder.build( new StringReader( configSource.getArchiverConfig() ) );
372         }
373         catch ( final XmlPullParserException e )
374         {
375             throw new ArchiverException(
376                                          "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
377                                          e );
378         }
379         catch ( final IOException e )
380         {
381             throw new ArchiverException(
382                                          "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( "Failed to lookup configurator for setup of archiver: "
399                 + 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", new Class[] { Object.class,
428                     PlexusConfiguration.class, ExpressionEvaluator.class, (Class<?>) containerRealm[1],
429                     ConfigurationListener.class } );
430 
431             configureComponent.invoke( configurator, component, configuration, expressionEvaluator,
432                     containerRealm[0], 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 String 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             // TODO: this needs a cleanup in plexus archiver - use a real
494             // typesafe enum
495             final TarArchiver.TarCompressionMethod tarCompressionMethod = new TarArchiver.TarCompressionMethod();
496             // TODO: this should accept gz and bz2 as well so we can skip
497             // over the switch
498             final String compression = format.substring( index + 1 );
499             if ( "gz".equals( compression ) )
500             {
501                 tarCompressionMethod.setValue( "gzip" );
502             }
503             else if ( "bz2".equals( compression ) )
504             {
505                 tarCompressionMethod.setValue( "bzip2" );
506             }
507             else
508             {
509                 // TODO: better handling
510                 throw new IllegalArgumentException( "Unknown compression format: " + compression );
511             }
512             tarArchiver.setCompression( tarCompressionMethod );
513         }
514 
515         final TarLongFileMode tarFileMode = new TarLongFileMode();
516 
517         tarFileMode.setValue( tarLongFileMode );
518 
519         tarArchiver.setLongfile( tarFileMode );
520 
521         return tarArchiver;
522     }
523 
524     public void contextualize( final Context context )
525         throws ContextException
526     {
527         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
528     }
529 
530     protected void setContainer( final PlexusContainer container )
531     {
532         this.container = container;
533     }
534 
535 }