| package org.apache.maven.plugins.assembly.archive; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.maven.plugin.DebugConfigurationListener; |
| import org.apache.maven.plugins.assembly.AssemblerConfigurationSource; |
| import org.apache.maven.plugins.assembly.InvalidAssemblerConfigurationException; |
| import org.apache.maven.plugins.assembly.archive.archiver.AssemblyProxyArchiver; |
| import org.apache.maven.plugins.assembly.archive.phase.AssemblyArchiverPhase; |
| import org.apache.maven.plugins.assembly.archive.phase.AssemblyArchiverPhaseComparator; |
| import org.apache.maven.plugins.assembly.artifact.DependencyResolutionException; |
| import org.apache.maven.plugins.assembly.filter.ComponentsXmlArchiverFileFilter; |
| import org.apache.maven.plugins.assembly.filter.ContainerDescriptorHandler; |
| import org.apache.maven.plugins.assembly.format.AssemblyFormattingException; |
| import org.apache.maven.plugins.assembly.interpolation.AssemblyExpressionEvaluator; |
| import org.apache.maven.plugins.assembly.model.Assembly; |
| import org.apache.maven.plugins.assembly.model.ContainerDescriptorHandlerConfig; |
| import org.apache.maven.plugins.assembly.utils.AssemblyFileUtils; |
| import org.apache.maven.plugins.assembly.utils.AssemblyFormatUtils; |
| import org.codehaus.plexus.PlexusConstants; |
| import org.codehaus.plexus.PlexusContainer; |
| import org.codehaus.plexus.archiver.ArchiveFinalizer; |
| import org.codehaus.plexus.archiver.Archiver; |
| import org.codehaus.plexus.archiver.ArchiverException; |
| import org.codehaus.plexus.archiver.diags.DryRunArchiver; |
| import org.codehaus.plexus.archiver.filters.JarSecurityFileSelector; |
| import org.codehaus.plexus.archiver.jar.JarArchiver; |
| import org.codehaus.plexus.archiver.manager.ArchiverManager; |
| import org.codehaus.plexus.archiver.manager.NoSuchArchiverException; |
| import org.codehaus.plexus.archiver.tar.TarArchiver; |
| import org.codehaus.plexus.archiver.tar.TarLongFileMode; |
| import org.codehaus.plexus.archiver.war.WarArchiver; |
| import org.codehaus.plexus.archiver.zip.AbstractZipArchiver; |
| import org.codehaus.plexus.component.annotations.Component; |
| import org.codehaus.plexus.component.annotations.Requirement; |
| import org.codehaus.plexus.component.configurator.ComponentConfigurationException; |
| import org.codehaus.plexus.component.configurator.ComponentConfigurator; |
| import org.codehaus.plexus.component.configurator.ConfigurationListener; |
| import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; |
| import org.codehaus.plexus.component.repository.exception.ComponentLookupException; |
| import org.codehaus.plexus.components.io.fileselectors.FileSelector; |
| import org.codehaus.plexus.configuration.PlexusConfiguration; |
| import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; |
| import org.codehaus.plexus.context.Context; |
| import org.codehaus.plexus.context.ContextException; |
| import org.codehaus.plexus.logging.AbstractLogEnabled; |
| import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable; |
| import org.codehaus.plexus.util.xml.Xpp3Dom; |
| import org.codehaus.plexus.util.xml.Xpp3DomBuilder; |
| import org.codehaus.plexus.util.xml.pull.XmlPullParserException; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Controller component designed to organize the many activities involved in creating an assembly archive. This includes |
| * locating and configuring {@link Archiver} instances, executing multiple {@link org.apache.maven.plugins.assembly |
| * .archive.phase.AssemblyArchiverPhase} instances to |
| * interpret the various sections of the assembly descriptor and determine which files to add, and other associated |
| * activities. |
| * |
| * |
| */ |
| @Component( role = AssemblyArchiver.class, instantiationStrategy = "per-lookup" ) |
| public class DefaultAssemblyArchiver |
| extends AbstractLogEnabled |
| implements AssemblyArchiver, Contextualizable |
| { |
| |
| @Requirement |
| private ArchiverManager archiverManager; |
| |
| @Requirement( role = AssemblyArchiverPhase.class ) |
| private List<AssemblyArchiverPhase> assemblyPhases; |
| |
| @SuppressWarnings( "MismatchedQueryAndUpdateOfCollection" ) |
| @Requirement( role = ContainerDescriptorHandler.class ) |
| private Map<String, ContainerDescriptorHandler> containerDescriptorHandlers; |
| |
| private PlexusContainer container; |
| |
| @SuppressWarnings( "UnusedDeclaration" ) |
| public DefaultAssemblyArchiver() |
| { |
| } |
| |
| // introduced for testing. |
| |
| /** |
| * @param archiverManager The archive manager. |
| * @param assemblyPhases The list of {@link AssemblyArchiverPhase} |
| */ |
| protected DefaultAssemblyArchiver( final ArchiverManager archiverManager, |
| final List<AssemblyArchiverPhase> assemblyPhases ) |
| { |
| this.archiverManager = archiverManager; |
| this.assemblyPhases = assemblyPhases; |
| } |
| |
| private List<AssemblyArchiverPhase> sortedPhases() |
| { |
| List<AssemblyArchiverPhase> sorted = new ArrayList<>( assemblyPhases ); |
| Collections.sort( sorted, new AssemblyArchiverPhaseComparator() ); |
| return sorted; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public File createArchive( final Assembly assembly, final String fullName, final String format, |
| final AssemblerConfigurationSource configSource, boolean recompressZippedFiles, |
| String mergeManifestMode, Date outputTimestamp ) |
| throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException |
| { |
| validate( assembly ); |
| |
| String filename = fullName; |
| if ( !configSource.isIgnoreDirFormatExtensions() || !format.startsWith( "dir" ) ) |
| { |
| filename += "." + format; |
| } |
| |
| AssemblyFileUtils.verifyTempDirectoryAvailability( configSource.getTemporaryRootDirectory() ); |
| |
| final File outputDirectory = configSource.getOutputDirectory(); |
| |
| final File destFile = new File( outputDirectory, filename ); |
| |
| try |
| { |
| final String finalName = configSource.getFinalName(); |
| final String specifiedBasedir = assembly.getBaseDirectory(); |
| |
| String basedir = finalName; |
| |
| if ( specifiedBasedir != null ) |
| { |
| basedir = AssemblyFormatUtils.getOutputDirectory( specifiedBasedir, finalName, configSource, |
| AssemblyFormatUtils.moduleProjectInterpolator( |
| configSource.getProject() ), |
| AssemblyFormatUtils.artifactProjectInterpolator( |
| null ) ); |
| } |
| |
| final List<ContainerDescriptorHandler> containerHandlers = |
| selectContainerDescriptorHandlers( assembly.getContainerDescriptorHandlers(), configSource ); |
| |
| final Archiver archiver = |
| createArchiver( format, assembly.isIncludeBaseDirectory(), basedir, configSource, containerHandlers, |
| recompressZippedFiles, mergeManifestMode, outputTimestamp ); |
| |
| archiver.setDestFile( destFile ); |
| |
| for ( AssemblyArchiverPhase phase : sortedPhases() ) |
| { |
| phase.execute( assembly, archiver, configSource ); |
| } |
| |
| archiver.createArchive(); |
| } |
| catch ( final ArchiverException | IOException e ) |
| { |
| throw new ArchiveCreationException( |
| "Error creating assembly archive " + assembly.getId() + ": " + e.getMessage(), e ); |
| } |
| catch ( final NoSuchArchiverException e ) |
| { |
| throw new ArchiveCreationException( |
| "Unable to obtain archiver for extension '" + format + "', for assembly: '" + assembly.getId() + "'", |
| e ); |
| } |
| catch ( final DependencyResolutionException e ) |
| { |
| throw new ArchiveCreationException( |
| "Unable to resolve dependencies for assembly '" + assembly.getId() + "'", e ); |
| } |
| |
| return destFile; |
| } |
| |
| private void validate( final Assembly assembly ) |
| throws InvalidAssemblerConfigurationException |
| { |
| if ( assembly.getId() == null || assembly.getId().trim().length() < 1 ) |
| { |
| throw new InvalidAssemblerConfigurationException( "Assembly ID must be present and non-empty." ); |
| } |
| } |
| |
| // CHECKSTYLE_OFF: LineLength |
| private List<ContainerDescriptorHandler> selectContainerDescriptorHandlers( |
| List<ContainerDescriptorHandlerConfig> requestedContainerDescriptorHandlers, |
| final AssemblerConfigurationSource configSource ) |
| throws InvalidAssemblerConfigurationException |
| // CHECKSTYLE_ON: LineLength |
| { |
| getLogger().debug( "All known ContainerDescriptorHandler components: " + ( containerDescriptorHandlers == null |
| ? "none; map is null." |
| : "" + containerDescriptorHandlers.keySet() ) ); |
| |
| if ( requestedContainerDescriptorHandlers == null ) |
| { |
| requestedContainerDescriptorHandlers = new ArrayList<>(); |
| } |
| |
| final List<ContainerDescriptorHandler> handlers = new ArrayList<>(); |
| final List<String> hints = new ArrayList<>(); |
| |
| if ( !requestedContainerDescriptorHandlers.isEmpty() ) |
| { |
| for ( final ContainerDescriptorHandlerConfig config : requestedContainerDescriptorHandlers ) |
| { |
| final String hint = config.getHandlerName(); |
| final ContainerDescriptorHandler handler = containerDescriptorHandlers.get( hint ); |
| |
| if ( handler == null ) |
| { |
| throw new InvalidAssemblerConfigurationException( |
| "Cannot find ContainerDescriptorHandler with hint: " + hint ); |
| } |
| |
| getLogger().debug( |
| "Found container descriptor handler with hint: " + hint + " (component: " + handler + ")" ); |
| |
| if ( config.getConfiguration() != null ) |
| { |
| getLogger().debug( "Configuring handler with:\n\n" + config.getConfiguration() + "\n\n" ); |
| |
| configureContainerDescriptorHandler( handler, (Xpp3Dom) config.getConfiguration(), configSource ); |
| } |
| |
| handlers.add( handler ); |
| hints.add( hint ); |
| } |
| } |
| |
| if ( !hints.contains( "plexus" ) ) |
| { |
| handlers.add( new ComponentsXmlArchiverFileFilter() ); |
| } |
| |
| return handlers; |
| } |
| |
| /** |
| * Creates the necessary archiver to build the distribution file. |
| * |
| * @param format Archive format |
| * @param includeBaseDir the base directory for include. |
| * @param finalName The final name. |
| * @param configSource {@link AssemblerConfigurationSource} |
| * @param containerHandlers The list of {@link ContainerDescriptorHandler} |
| * @param recompressZippedFiles recompress zipped files. |
| * @param mergeManifestMode how to handle already existing Manifest files |
| * @return archiver Archiver generated |
| * @throws org.codehaus.plexus.archiver.ArchiverException |
| * @throws org.codehaus.plexus.archiver.manager.NoSuchArchiverException |
| */ |
| protected Archiver createArchiver( final String format, final boolean includeBaseDir, final String finalName, |
| final AssemblerConfigurationSource configSource, |
| final List<ContainerDescriptorHandler> containerHandlers, |
| boolean recompressZippedFiles, String mergeManifestMode, Date outputTimestamp ) |
| throws NoSuchArchiverException |
| { |
| Archiver archiver; |
| if ( "txz".equals( format ) || "tgz".equals( format ) || "tbz2".equals( format ) || format.startsWith( "tar" ) ) |
| { |
| archiver = createTarArchiver( format, TarLongFileMode.valueOf( configSource.getTarLongFileMode() ) ); |
| } |
| else if ( "war".equals( format ) ) |
| { |
| archiver = createWarArchiver(); |
| } |
| else |
| { |
| archiver = archiverManager.getArchiver( format ); |
| } |
| |
| if ( archiver instanceof AbstractZipArchiver ) |
| { |
| ( (AbstractZipArchiver) archiver ).setRecompressAddedZips( recompressZippedFiles ); |
| } |
| |
| final List<FileSelector> extraSelectors = new ArrayList<>(); |
| final List<ArchiveFinalizer> extraFinalizers = new ArrayList<>(); |
| if ( archiver instanceof JarArchiver ) |
| { |
| if ( mergeManifestMode != null ) |
| { |
| ( (JarArchiver) archiver ).setFilesetmanifest( |
| JarArchiver.FilesetManifestConfig.valueOf( mergeManifestMode ) ); |
| } |
| |
| extraSelectors.add( new JarSecurityFileSelector() ); |
| |
| extraFinalizers.add( |
| new ManifestCreationFinalizer( configSource.getMavenSession(), configSource.getProject(), |
| configSource.getJarArchiveConfiguration() ) ); |
| |
| } |
| |
| if ( configSource.getArchiverConfig() != null ) |
| { |
| configureArchiver( archiver, configSource ); |
| } |
| |
| String prefix = ""; |
| if ( includeBaseDir ) |
| { |
| prefix = finalName; |
| } |
| |
| archiver = new AssemblyProxyArchiver( prefix, archiver, containerHandlers, extraSelectors, extraFinalizers, |
| configSource.getWorkingDirectory(), getLogger() ); |
| if ( configSource.isDryRun() ) |
| { |
| archiver = new DryRunArchiver( archiver, getLogger() ); |
| } |
| |
| archiver.setUseJvmChmod( configSource.isUpdateOnly() ); |
| archiver.setIgnorePermissions( configSource.isIgnorePermissions() ); |
| archiver.setForced( !configSource.isUpdateOnly() ); |
| |
| // configure for Reproducible Builds based on outputTimestamp value |
| if ( outputTimestamp != null ) |
| { |
| archiver.configureReproducible( outputTimestamp ); |
| } |
| |
| return archiver; |
| } |
| |
| private void configureContainerDescriptorHandler( final ContainerDescriptorHandler handler, final Xpp3Dom config, |
| final AssemblerConfigurationSource configSource ) |
| throws InvalidAssemblerConfigurationException |
| { |
| getLogger().debug( "Configuring handler: '" + handler.getClass().getName() + "' -->" ); |
| |
| try |
| { |
| configureComponent( handler, config, configSource ); |
| } |
| catch ( final ComponentConfigurationException e ) |
| { |
| throw new InvalidAssemblerConfigurationException( |
| "Failed to configure handler: " + handler.getClass().getName(), e ); |
| } |
| catch ( final ComponentLookupException e ) |
| { |
| throw new InvalidAssemblerConfigurationException( |
| "Failed to lookup configurator for setup of handler: " + handler.getClass().getName(), e ); |
| } |
| |
| getLogger().debug( "-- end configuration --" ); |
| } |
| |
| private void configureArchiver( final Archiver archiver, final AssemblerConfigurationSource configSource ) |
| { |
| Xpp3Dom config; |
| try |
| { |
| config = Xpp3DomBuilder.build( new StringReader( configSource.getArchiverConfig() ) ); |
| } |
| catch ( final XmlPullParserException | IOException e ) |
| { |
| throw new ArchiverException( "Failed to parse archiver configuration for: " + archiver.getClass().getName(), |
| e ); |
| } |
| |
| getLogger().debug( "Configuring archiver: '" + archiver.getClass().getName() + "' -->" ); |
| |
| try |
| { |
| configureComponent( archiver, config, configSource ); |
| } |
| catch ( final ComponentConfigurationException e ) |
| { |
| throw new ArchiverException( "Failed to configure archiver: " + archiver.getClass().getName(), e ); |
| } |
| catch ( final ComponentLookupException e ) |
| { |
| throw new ArchiverException( |
| "Failed to lookup configurator for setup of archiver: " + archiver.getClass().getName(), e ); |
| } |
| |
| getLogger().debug( "-- end configuration --" ); |
| } |
| |
| private void configureComponent( final Object component, final Xpp3Dom config, |
| final AssemblerConfigurationSource configSource ) |
| throws ComponentLookupException, ComponentConfigurationException |
| { |
| final ComponentConfigurator configurator = container.lookup( ComponentConfigurator.class, "basic" ); |
| |
| final ConfigurationListener listener = new DebugConfigurationListener( getLogger() ); |
| |
| final ExpressionEvaluator expressionEvaluator = new AssemblyExpressionEvaluator( configSource ); |
| |
| final XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( config ); |
| |
| final Object[] containerRealm = getContainerRealm(); |
| |
| /* |
| * NOTE: The signature of configureComponent() has changed in Maven 3.x, the reflection prevents a linkage error |
| * and makes the code work with both Maven 2 and 3. |
| */ |
| try |
| { |
| final Method configureComponent = |
| ComponentConfigurator.class.getMethod( "configureComponent", Object.class, PlexusConfiguration.class, |
| ExpressionEvaluator.class, (Class<?>) containerRealm[1], |
| ConfigurationListener.class ); |
| |
| configureComponent.invoke( configurator, component, configuration, expressionEvaluator, containerRealm[0], |
| listener ); |
| } |
| catch ( final NoSuchMethodException | IllegalAccessException e ) |
| { |
| throw new RuntimeException( e ); |
| } |
| catch ( final InvocationTargetException e ) |
| { |
| if ( e.getCause() instanceof ComponentConfigurationException ) |
| { |
| throw (ComponentConfigurationException) e.getCause(); |
| } |
| throw new RuntimeException( e.getCause() ); |
| } |
| } |
| |
| private Object[] getContainerRealm() |
| { |
| /* |
| * NOTE: The return type of getContainerRealm() has changed in Maven 3.x, the reflection prevents a linkage |
| * error and makes the code work with both Maven 2 and 3. |
| */ |
| try |
| { |
| final Method getContainerRealm = container.getClass().getMethod( "getContainerRealm" ); |
| return new Object[]{ getContainerRealm.invoke( container ), getContainerRealm.getReturnType() }; |
| } |
| catch ( final NoSuchMethodException | IllegalAccessException e ) |
| { |
| throw new RuntimeException( e ); |
| } |
| catch ( final InvocationTargetException e ) |
| { |
| throw new RuntimeException( e.getCause() ); |
| } |
| } |
| |
| protected Archiver createWarArchiver() |
| throws NoSuchArchiverException |
| { |
| final WarArchiver warArchiver = (WarArchiver) archiverManager.getArchiver( "war" ); |
| warArchiver.setIgnoreWebxml( false ); // See MNG-1274 |
| |
| return warArchiver; |
| } |
| |
| protected Archiver createTarArchiver( final String format, final TarLongFileMode tarLongFileMode ) |
| throws NoSuchArchiverException |
| { |
| final TarArchiver tarArchiver = (TarArchiver) archiverManager.getArchiver( "tar" ); |
| final int index = format.indexOf( '.' ); |
| if ( index >= 0 ) |
| { |
| TarArchiver.TarCompressionMethod tarCompressionMethod; |
| // TODO: this should accept gz and bz2 as well so we can skip |
| // TODO: over the switch |
| final String compression = format.substring( index + 1 ); |
| if ( "gz".equals( compression ) ) |
| { |
| tarCompressionMethod = TarArchiver.TarCompressionMethod.gzip; |
| } |
| else if ( "bz2".equals( compression ) ) |
| { |
| tarCompressionMethod = TarArchiver.TarCompressionMethod.bzip2; |
| } |
| else if ( "xz".equals( compression ) ) |
| { |
| tarCompressionMethod = TarArchiver.TarCompressionMethod.xz; |
| } |
| else if ( "snappy".equals( compression ) ) |
| { |
| tarCompressionMethod = TarArchiver.TarCompressionMethod.snappy; |
| } |
| else |
| { |
| // TODO: better handling |
| throw new IllegalArgumentException( "Unknown compression format: " + compression ); |
| } |
| tarArchiver.setCompression( tarCompressionMethod ); |
| } |
| else if ( "tgz".equals( format ) ) |
| { |
| tarArchiver.setCompression( TarArchiver.TarCompressionMethod.gzip ); |
| } |
| else if ( "tbz2".equals( format ) ) |
| { |
| tarArchiver.setCompression( TarArchiver.TarCompressionMethod.bzip2 ); |
| } |
| else if ( "txz".equals( format ) ) |
| { |
| tarArchiver.setCompression( TarArchiver.TarCompressionMethod.xz ); |
| } |
| |
| tarArchiver.setLongfile( tarLongFileMode ); |
| |
| return tarArchiver; |
| } |
| |
| @Override |
| public void contextualize( final Context context ) |
| throws ContextException |
| { |
| container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY ); |
| } |
| |
| protected void setContainer( final PlexusContainer container ) |
| { |
| this.container = container; |
| } |
| |
| } |