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.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 1163853 2011-08-31 22:42:32Z jdcasey $
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 )
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(), getLogger() );
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 
172             archiver.setDestFile( destFile );
173 
174             final AssemblyContext context = new DefaultAssemblyContext();
175 
176             dependencyResolver.resolve( assembly, configSource, context );
177 
178             for ( final Iterator<AssemblyArchiverPhase> phaseIterator = assemblyPhases.iterator(); phaseIterator.hasNext(); )
179             {
180                 final AssemblyArchiverPhase phase = phaseIterator.next();
181 
182                 phase.execute( assembly, archiver, configSource, context );
183             }
184 
185             archiver.createArchive();
186         }
187         catch ( final ArchiverException e )
188         {
189             throw new ArchiveCreationException( "Error creating assembly archive " + assembly.getId() + ": "
190                 + e.getMessage(), e );
191         }
192         catch ( final IOException e )
193         {
194             throw new ArchiveCreationException( "Error creating assembly archive " + assembly.getId() + ": "
195                 + e.getMessage(), e );
196         }
197         catch ( final NoSuchArchiverException e )
198         {
199             throw new ArchiveCreationException( "Unable to obtain archiver for extension '" + format
200                 + "', for assembly: '" + assembly.getId() + "'", e );
201         }
202         catch ( final DependencyResolutionException e )
203         {
204             throw new ArchiveCreationException( "Unable to resolve dependencies for assembly '" + assembly.getId()
205                 + "'", e );
206         }
207 
208         return destFile;
209     }
210 
211     private void validate( final Assembly assembly )
212         throws InvalidAssemblerConfigurationException
213     {
214         if ( assembly.getId() == null || assembly.getId().trim().length() < 1 )
215         {
216             throw new InvalidAssemblerConfigurationException( "Assembly ID must be present and non-empty." );
217         }
218     }
219 
220     private List<ContainerDescriptorHandler> selectContainerDescriptorHandlers( List<ContainerDescriptorHandlerConfig> requestedContainerDescriptorHandlers,
221                                                                                 final AssemblerConfigurationSource configSource )
222         throws InvalidAssemblerConfigurationException
223     {
224         getLogger().debug( "All known ContainerDescriptorHandler components: "
225                                + ( containerDescriptorHandlers == null ? "none; map is null." : ""
226                                    + containerDescriptorHandlers.keySet() ) );
227 
228         if ( requestedContainerDescriptorHandlers == null )
229         {
230             requestedContainerDescriptorHandlers = new ArrayList<ContainerDescriptorHandlerConfig>();
231         }
232 
233         final List<ContainerDescriptorHandler> handlers = new ArrayList<ContainerDescriptorHandler>();
234         final List<String> hints = new ArrayList<String>();
235 
236         if ( ( requestedContainerDescriptorHandlers != null ) && !requestedContainerDescriptorHandlers.isEmpty() )
237         {
238             for ( final Iterator<ContainerDescriptorHandlerConfig> it = requestedContainerDescriptorHandlers.iterator(); it.hasNext(); )
239             {
240                 final ContainerDescriptorHandlerConfig config = it.next();
241 
242                 final String hint = config.getHandlerName();
243                 final ContainerDescriptorHandler handler = containerDescriptorHandlers.get( hint );
244 
245                 if ( handler == null )
246                 {
247                     throw new InvalidAssemblerConfigurationException(
248                                                                       "Cannot find ContainerDescriptorHandler with hint: "
249                                                                           + hint );
250                 }
251 
252                 getLogger().debug( "Found container descriptor handler with hint: " + hint + " (component: " + handler
253                                        + ")" );
254 
255                 if ( config.getConfiguration() != null )
256                 {
257                     getLogger().debug( "Configuring handler with:\n\n" + config.getConfiguration() + "\n\n" );
258 
259                     configureContainerDescriptorHandler( handler, (Xpp3Dom) config.getConfiguration(), configSource );
260                 }
261 
262                 handlers.add( handler );
263                 hints.add( hint );
264             }
265         }
266 
267         if ( !hints.contains( "plexus" ) )
268         {
269             handlers.add( new ComponentsXmlArchiverFileFilter() );
270         }
271 
272         return handlers;
273     }
274 
275     /**
276      * Creates the necessary archiver to build the distribution file.
277      * 
278      * @param format Archive format
279      * @param includeBaseDir
280      * @param finalName
281      * @param configSource
282      * @param containerHandlers
283      * @return archiver Archiver generated
284      * @throws org.codehaus.plexus.archiver.ArchiverException
285      * @throws org.codehaus.plexus.archiver.manager.NoSuchArchiverException
286      */
287     protected Archiver createArchiver( final String format, final boolean includeBaseDir, final String finalName,
288                                        final AssemblerConfigurationSource configSource,
289                                        final List<ContainerDescriptorHandler> containerHandlers )
290         throws ArchiverException, NoSuchArchiverException
291     {
292         Archiver archiver;
293         if ( format.startsWith( "tar" ) )
294         {
295             archiver = createTarArchiver( format, configSource.getTarLongFileMode() );
296         }
297         else if ( "war".equals( format ) )
298         {
299             archiver = createWarArchiver();
300         }
301         else
302         {
303             archiver = archiverManager.getArchiver( format );
304         }
305 
306         final List<FileSelector> extraSelectors = new ArrayList<FileSelector>();
307         final List<ArchiveFinalizer> extraFinalizers = new ArrayList<ArchiveFinalizer>();
308         if ( archiver instanceof JarArchiver )
309         {
310             extraSelectors.add( new JarSecurityFileSelector() );
311 
312             extraFinalizers.add( new ManifestCreationFinalizer( configSource.getProject(),
313                                                                 configSource.getJarArchiveConfiguration() ) );
314 
315         }
316 
317         if ( configSource.getArchiverConfig() != null )
318         {
319             configureArchiver( archiver, configSource );
320         }
321 
322         String prefix = "";
323         if ( includeBaseDir )
324         {
325             prefix = finalName;
326         }
327 
328         archiver =
329             new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers,
330                                        configSource.getWorkingDirectory(), getLogger(), configSource.isDryRun() );
331 
332         archiver.setUseJvmChmod( configSource.isUpdateOnly() );
333         archiver.setIgnorePermissions( configSource.isIgnorePermissions() );
334         archiver.setForced( !configSource.isUpdateOnly() );
335 
336         return archiver;
337     }
338 
339     private void configureContainerDescriptorHandler( final ContainerDescriptorHandler handler, final Xpp3Dom config,
340                                                       final AssemblerConfigurationSource configSource )
341         throws InvalidAssemblerConfigurationException
342     {
343         getLogger().debug( "Configuring handler: '" + handler.getClass().getName() + "' -->" );
344 
345         try
346         {
347             configureComponent( handler, config, configSource );
348         }
349         catch ( final ComponentConfigurationException e )
350         {
351             throw new InvalidAssemblerConfigurationException( "Failed to configure handler: "
352                 + handler.getClass().getName(), e );
353         }
354         catch ( final ComponentLookupException e )
355         {
356             throw new InvalidAssemblerConfigurationException( "Failed to lookup configurator for setup of handler: "
357                 + handler.getClass().getName(), e );
358         }
359 
360         getLogger().debug( "-- end configuration --" );
361     }
362 
363     private void configureArchiver( final Archiver archiver, final AssemblerConfigurationSource configSource )
364         throws ArchiverException
365     {
366         Xpp3Dom config;
367         try
368         {
369             config = Xpp3DomBuilder.build( new StringReader( configSource.getArchiverConfig() ) );
370         }
371         catch ( final XmlPullParserException e )
372         {
373             throw new ArchiverException(
374                                          "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
375                                          e );
376         }
377         catch ( final IOException e )
378         {
379             throw new ArchiverException(
380                                          "Failed to parse archiver configuration for: " + archiver.getClass().getName(),
381                                          e );
382         }
383 
384         getLogger().debug( "Configuring archiver: '" + archiver.getClass().getName() + "' -->" );
385 
386         try
387         {
388             configureComponent( archiver, config, configSource );
389         }
390         catch ( final ComponentConfigurationException e )
391         {
392             throw new ArchiverException( "Failed to configure archiver: " + archiver.getClass().getName(), e );
393         }
394         catch ( final ComponentLookupException e )
395         {
396             throw new ArchiverException( "Failed to lookup configurator for setup of archiver: "
397                 + archiver.getClass().getName(), e );
398         }
399 
400         getLogger().debug( "-- end configuration --" );
401     }
402 
403     private void configureComponent( final Object component, final Xpp3Dom config,
404                                      final AssemblerConfigurationSource configSource )
405         throws ComponentLookupException, ComponentConfigurationException
406     {
407         final ComponentConfigurator configurator =
408             (ComponentConfigurator) container.lookup( ComponentConfigurator.ROLE, "basic" );
409 
410         final ConfigurationListener listener = new DebugConfigurationListener( getLogger() );
411 
412         final ExpressionEvaluator expressionEvaluator = new AssemblyExpressionEvaluator( configSource );
413 
414         final XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( config );
415 
416         final Object[] containerRealm = getContainerRealm();
417 
418         /*
419          * NOTE: The signature of configureComponent() has changed in Maven 3.x, the reflection prevents a linkage error
420          * and makes the code work with both Maven 2 and 3.
421          */
422         try
423         {
424             final Method configureComponent =
425                 ComponentConfigurator.class.getMethod( "configureComponent", new Class[] { Object.class,
426                     PlexusConfiguration.class, ExpressionEvaluator.class, (Class<?>) containerRealm[1],
427                     ConfigurationListener.class } );
428 
429             configureComponent.invoke( configurator, new Object[] { component, configuration, expressionEvaluator,
430                 containerRealm[0], listener } );
431         }
432         catch ( final NoSuchMethodException e )
433         {
434             throw new RuntimeException( e );
435         }
436         catch ( final IllegalAccessException e )
437         {
438             throw new RuntimeException( e );
439         }
440         catch ( final InvocationTargetException e )
441         {
442             if ( e.getCause() instanceof ComponentConfigurationException )
443             {
444                 throw (ComponentConfigurationException) e.getCause();
445             }
446             throw new RuntimeException( e.getCause() );
447         }
448     }
449 
450     private Object[] getContainerRealm()
451     {
452         /*
453          * NOTE: The return type of getContainerRealm() has changed in Maven 3.x, the reflection prevents a linkage
454          * error and makes the code work with both Maven 2 and 3.
455          */
456         try
457         {
458             final Method getContainerRealm = container.getClass().getMethod( "getContainerRealm" );
459             return new Object[] { getContainerRealm.invoke( container ), getContainerRealm.getReturnType() };
460         }
461         catch ( final NoSuchMethodException e )
462         {
463             throw new RuntimeException( e );
464         }
465         catch ( final IllegalAccessException e )
466         {
467             throw new RuntimeException( e );
468         }
469         catch ( final InvocationTargetException e )
470         {
471             throw new RuntimeException( e.getCause() );
472         }
473     }
474 
475     protected Archiver createWarArchiver()
476         throws NoSuchArchiverException
477     {
478         final WarArchiver warArchiver = (WarArchiver) archiverManager.getArchiver( "war" );
479         warArchiver.setIgnoreWebxml( false ); // See MNG-1274
480 
481         return warArchiver;
482     }
483 
484     protected Archiver createTarArchiver( final String format, final String tarLongFileMode )
485         throws NoSuchArchiverException, ArchiverException
486     {
487         final TarArchiver tarArchiver = (TarArchiver) archiverManager.getArchiver( "tar" );
488         final int index = format.indexOf( '.' );
489         if ( index >= 0 )
490         {
491             // TODO: this needs a cleanup in plexus archiver - use a real
492             // typesafe enum
493             final TarArchiver.TarCompressionMethod tarCompressionMethod = new TarArchiver.TarCompressionMethod();
494             // TODO: this should accept gz and bz2 as well so we can skip
495             // over the switch
496             final String compression = format.substring( index + 1 );
497             if ( "gz".equals( compression ) )
498             {
499                 tarCompressionMethod.setValue( "gzip" );
500             }
501             else if ( "bz2".equals( compression ) )
502             {
503                 tarCompressionMethod.setValue( "bzip2" );
504             }
505             else
506             {
507                 // TODO: better handling
508                 throw new IllegalArgumentException( "Unknown compression format: " + compression );
509             }
510             tarArchiver.setCompression( tarCompressionMethod );
511         }
512 
513         final TarLongFileMode tarFileMode = new TarLongFileMode();
514 
515         tarFileMode.setValue( tarLongFileMode );
516 
517         tarArchiver.setLongfile( tarFileMode );
518 
519         return tarArchiver;
520     }
521 
522     public void contextualize( final Context context )
523         throws ContextException
524     {
525         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
526     }
527 
528     protected void setContainer( final PlexusContainer container )
529     {
530         this.container = container;
531     }
532 
533 }