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