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