From 08d35e413afe049515ddc727e53c68cbc9d1c496 Mon Sep 17 00:00:00 2001 From: Cai Date: Sat, 4 Dec 2021 17:39:58 -0800 Subject: [PATCH] allow to create war archive without copying massive files to exploded war --- .../maven/plugins/war/AbstractWarMojo.java | 62 ++- .../org/apache/maven/plugins/war/WarMojo.java | 89 +++- .../packaging/AbstractWarPackagingTask.java | 16 +- .../war/packaging/ClassesPackagingTask.java | 5 +- .../war/packaging/WarPackagingContext.java | 18 +- .../packaging/WarProjectPackagingTask.java | 40 +- .../plugins/war/util/ClassesPackager.java | 27 + .../SourceTargetMappingResourceFilter.java | 502 ++++++++++++++++++ .../plugins/war/util/WarResourceCopy.java | 84 +++ 9 files changed, 794 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/apache/maven/plugins/war/util/SourceTargetMappingResourceFilter.java create mode 100644 src/main/java/org/apache/maven/plugins/war/util/WarResourceCopy.java diff --git a/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java b/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java index dc1ecb36..833875e8 100644 --- a/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java +++ b/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java @@ -49,6 +49,7 @@ import org.apache.maven.plugins.war.packaging.WarPackagingContext; import org.apache.maven.plugins.war.packaging.WarPackagingTask; import org.apache.maven.plugins.war.packaging.WarProjectPackagingTask; +import org.apache.maven.plugins.war.util.WarResourceCopy; import org.apache.maven.plugins.war.util.WebappStructure; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.filtering.MavenFileFilter; @@ -373,6 +374,15 @@ public abstract class AbstractWarMojo @Parameter( defaultValue = "WEB-INF/lib/" ) private String outdatedCheckPath; + /** + * You can skip the exploded war creation when building the war archive. + * War archive are created based on the source paths. + * + * @since 3.3.3 + */ + @Parameter( defaultValue = "false", name = "maven.war.exploded.skip" ) + private boolean skipExplodedWarCreation; + private final Overlay currentProjectOverlay = Overlay.createInstance(); /** @@ -421,6 +431,16 @@ protected String[] getIncludes() return StringUtils.split( StringUtils.defaultString( warSourceIncludes ), "," ); } + /** + * For mojos want to enable skipExplodedWarCreation, override this method + * + * @return + */ + protected boolean supportSkipExplodedWarCreation() + { + return false; + } + /** * Returns a string array of the excludes to be used when adding dependent WAR as an overlay onto this WAR. * @@ -446,14 +466,15 @@ protected String[] getDependentWarIncludes() * @throws MojoExecutionException In case of failure. * @throws MojoFailureException In case of failure. */ - public void buildExplodedWebapp( File webapplicationDirectory ) + public WarPackagingContext buildExplodedWebapp( File webapplicationDirectory ) throws MojoExecutionException, MojoFailureException { + skipExplodedWarCreation = supportSkipExplodedWarCreation() ? skipExplodedWarCreation : false; webapplicationDirectory.mkdirs(); try { - buildWebapp( project, webapplicationDirectory ); + return buildWebapp( project, webapplicationDirectory ); } catch ( IOException e ) { @@ -471,7 +492,7 @@ public void buildExplodedWebapp( File webapplicationDirectory ) * @throws MojoFailureException if an unexpected error occurred while packaging the webapp * @throws IOException if an error occurred while copying the files */ - public void buildWebapp( MavenProject mavenProject, File webapplicationDirectory ) + public WarPackagingContext buildWebapp( MavenProject mavenProject, File webapplicationDirectory ) throws MojoExecutionException, MojoFailureException, IOException { @@ -524,7 +545,7 @@ public void buildWebapp( MavenProject mavenProject, File webapplicationDirectory new DefaultWarPackagingContext( webapplicationDirectory, structure, overlayManager, defaultFilterWrappers, getNonFilteredFileExtensions(), filteringDeploymentDescriptors, this.artifactFactory, resourceEncoding, useJvmChmod, failOnMissingWebXml, - outputTimestamp ); + skipExplodedWarCreation, outputTimestamp ); final List packagingTasks = getPackagingTasks( overlayManager ); @@ -534,7 +555,7 @@ public void buildWebapp( MavenProject mavenProject, File webapplicationDirectory } getLog().debug( "Webapp assembled in [" + ( System.currentTimeMillis() - startTime ) + " msecs]" ); - + return context; } /** @@ -586,6 +607,10 @@ private class DefaultWarPackagingContext private final List filterWrappers; + private final boolean skipExplodedWarCreation; + + private final WarResourceCopy warResourceCopy; + private List nonFilteredFileExtensions; private boolean filteringDeploymentDescriptors; @@ -609,21 +634,25 @@ private class DefaultWarPackagingContext * @param resourceEncoding The resource encoding. * @param useJvmChmod use Jvm chmod or not. * @param failOnMissingWebXml Flag to check whether we should ignore missing web.xml or not + * @param skipExplodedWarCreation * @param outputTimestamp the output timestamp for reproducible archive creation */ DefaultWarPackagingContext( final File webappDirectory, final WebappStructure webappStructure, - final OverlayManager overlayManager, - List filterWrappers, - List nonFilteredFileExtensions, - boolean filteringDeploymentDescriptors, ArtifactFactory artifactFactory, - String resourceEncoding, boolean useJvmChmod, - final Boolean failOnMissingWebXml, String outputTimestamp ) + final OverlayManager overlayManager, + List filterWrappers, + List nonFilteredFileExtensions, + boolean filteringDeploymentDescriptors, ArtifactFactory artifactFactory, + String resourceEncoding, boolean useJvmChmod, + final Boolean failOnMissingWebXml, boolean skipExplodedWarCreation, + String outputTimestamp ) { this.webappDirectory = webappDirectory; this.webappStructure = webappStructure; this.overlayManager = overlayManager; this.filterWrappers = filterWrappers; this.artifactFactory = artifactFactory; + this.skipExplodedWarCreation = skipExplodedWarCreation; + this.warResourceCopy = new WarResourceCopy( skipExplodedWarCreation ); this.filteringDeploymentDescriptors = filteringDeploymentDescriptors; this.nonFilteredFileExtensions = nonFilteredFileExtensions == null ? Collections.emptyList() : nonFilteredFileExtensions; @@ -743,6 +772,17 @@ public String[] getWebappSourceExcludes() return getExcludes(); } + public boolean skipExplodedWarCreation() + { + return skipExplodedWarCreation; + } + + @Override + public WarResourceCopy getWarResourceCopy() + { + return warResourceCopy; + } + @Override public boolean isWebappSourceIncludeEmptyDirectories() { diff --git a/src/main/java/org/apache/maven/plugins/war/WarMojo.java b/src/main/java/org/apache/maven/plugins/war/WarMojo.java index 3dda4c98..e333470f 100644 --- a/src/main/java/org/apache/maven/plugins/war/WarMojo.java +++ b/src/main/java/org/apache/maven/plugins/war/WarMojo.java @@ -19,14 +19,6 @@ * under the License. */ -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.List; - import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; @@ -37,15 +29,31 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.plugins.war.packaging.AbstractWarPackagingTask; +import org.apache.maven.plugins.war.packaging.WarPackagingContext; import org.apache.maven.plugins.war.util.ClassesPackager; +import org.apache.maven.plugins.war.util.SourceTargetMappingResourceFilter; import org.apache.maven.project.MavenProjectHelper; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.ArchiverException; import org.codehaus.plexus.archiver.jar.ManifestException; import org.codehaus.plexus.archiver.war.WarArchiver; +import org.codehaus.plexus.components.io.resources.PlexusIoResource; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.StringUtils; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Build a WAR file. * @@ -155,10 +163,18 @@ public class WarMojo @Parameter( property = "maven.war.skip", defaultValue = "false" ) private boolean skip; + // ---------------------------------------------------------------------- // Implementation // ---------------------------------------------------------------------- + + @Override + protected boolean supportSkipExplodedWarCreation() + { + return true; + } + /** * Executes the WarMojo on the current project. * @@ -209,7 +225,7 @@ private void performPackaging( File warFile ) { getLog().info( "Packaging webapp" ); - buildExplodedWebapp( getWebappDirectory() ); + WarPackagingContext context = buildExplodedWebapp( getWebappDirectory() ); MavenArchiver archiver = new MavenArchiver(); @@ -226,16 +242,48 @@ private void performPackaging( File warFile ) + " from the generated webapp archive." ); getLog().debug( "Including " + Arrays.asList( getPackagingIncludes() ) + " in the generated webapp archive." ); - warArchiver.addDirectory( getWebappDirectory(), getPackagingIncludes(), getPackagingExcludes() ); - final File webXmlFile = new File( getWebappDirectory(), "WEB-INF/web.xml" ); if ( webXmlFile.exists() ) { warArchiver.setWebxml( webXmlFile ); } - warArchiver.setRecompressAddedZips( isRecompressZippedFiles() ); + if ( context.skipExplodedWarCreation() ) + { + Map map = context.getWarResourceCopy().getSourceTargetMappings(); + if ( map.containsKey( "WEB-INF/web.xml" ) ) + { + warArchiver.setWebxml( map.get( "WEB-INF/web.xml" ) ); + map.remove( "WEB-INF/web.xml" ); + } + + SourceTargetMappingResourceFilter filter = new SourceTargetMappingResourceFilter( warArchiver ); + Map plexusIoResourceMap = + filter.filteredResources( getPackagingIncludes(), getPackagingExcludes(), "", map ); + Iterator> it = plexusIoResourceMap.entrySet().iterator(); + while ( it.hasNext() ) + { + Map.Entry rez = it.next(); + warArchiver.addResource( rez.getValue(), rez.getKey(), -1 ); + } + //copy from exploded war + //case 1: user uses some other plugins to copy files to exploded war + //case 2: copy all filtered files, filtered resources are not tracked and still synced to exploded war + //case 3: web.xml using variables that would be filtered by filteringDeploymentDescriptors + Set targetPaths = context.getWarResourceCopy().getSourceTargetMappings().keySet(); + Set newExcludes = new HashSet<>( targetPaths ); + newExcludes.addAll( Arrays.asList( getPackagingExcludes() ) ); + warArchiver + .addDirectory( getWebappDirectory(), getPackagingIncludes(), newExcludes.toArray( new String[0] ) ); + } + else + { + warArchiver.addDirectory( getWebappDirectory(), getPackagingIncludes(), getPackagingExcludes() ); + } + + + warArchiver.setRecompressAddedZips( isRecompressZippedFiles() ); warArchiver.setIncludeEmptyDirs( isIncludeEmptyDirectories() ); if ( Boolean.FALSE.equals( failOnMissingWebXml ) @@ -265,8 +313,19 @@ private void performPackaging( File warFile ) if ( classesDirectory.exists() ) { getLog().info( "Packaging classes" ); - packager.packageClasses( classesDirectory, getTargetClassesFile(), getJarArchiver(), getSession(), - getProject(), getArchive(), outputTimestamp ); + if ( context.skipExplodedWarCreation() ) + { + Map classes = context.getWarResourceCopy() + .getFilesWithPrefix( AbstractWarPackagingTask.CLASSES_PATH, true ); + packager.packageClasses( classes, getTargetClassesFile(), getJarArchiver(), getSession(), + getProject(), getArchive(), outputTimestamp ); + } + else + { + packager.packageClasses( classesDirectory, getTargetClassesFile(), getJarArchiver(), + getSession(), + getProject(), getArchive(), outputTimestamp ); + } projectHelper.attachArtifact( getProject(), "jar", getClassesClassifier(), getTargetClassesFile() ); } } @@ -290,6 +349,8 @@ else if ( artifact.getFile() == null || artifact.getFile().isDirectory() ) } } + + /** * Determines if the current Maven project being built uses the Servlet 3.0 API (JSR 315) * or Jakarta Servlet API. diff --git a/src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java b/src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java index e230f6cf..0fceb77b 100644 --- a/src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java +++ b/src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java @@ -19,9 +19,6 @@ * under the License. */ -import java.io.File; -import java.io.IOException; - import org.apache.commons.io.input.XmlStreamReader; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; @@ -37,6 +34,9 @@ import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.FileUtils; +import java.io.File; +import java.io.IOException; + /** * @author Stephane Nicoll */ @@ -105,6 +105,7 @@ protected void copyFiles( String sourceId, WarPackagingContext context, File sou destinationFileName = targetPrefix + fileToCopyName; } + if ( filtered && !context.isNonFilteredExtension( sourceFile.getName() ) ) { copyFilteredFile( sourceId, context, sourceFile, destinationFileName ); @@ -283,6 +284,7 @@ protected boolean copyFilteredFile( String sourceId, final WarPackagingContext c } } + /** * Unpacks the specified file to the specified directory. * @@ -336,6 +338,7 @@ protected boolean copyFile( WarPackagingContext context, File source, File desti throws IOException { context.addResource( targetFilename ); + context.getWarResourceCopy().addFileCopy( targetFilename, source ); if ( onlyIfModified && destination.lastModified() >= source.lastModified() ) { @@ -366,21 +369,20 @@ protected boolean copyFile( WarPackagingContext context, File source, File desti } else { - FileUtils.copyFile( source.getCanonicalFile(), destination ); - // preserve timestamp - destination.setLastModified( source.lastModified() ); + context.getWarResourceCopy().copy( source, destination ); context.getLog().debug( " + " + targetFilename + " has been copied." ); } return true; } } + /** * Get the encoding from an XML-file. * * @param webXml the XML-file * @return The encoding of the XML-file, or UTF-8 if it's not specified in the file - * @throws java.io.IOException if an error occurred while reading the file + * @throws IOException if an error occurred while reading the file */ protected String getEncoding( File webXml ) throws IOException diff --git a/src/main/java/org/apache/maven/plugins/war/packaging/ClassesPackagingTask.java b/src/main/java/org/apache/maven/plugins/war/packaging/ClassesPackagingTask.java index ea136a69..84ab0b73 100644 --- a/src/main/java/org/apache/maven/plugins/war/packaging/ClassesPackagingTask.java +++ b/src/main/java/org/apache/maven/plugins/war/packaging/ClassesPackagingTask.java @@ -107,13 +107,14 @@ protected void generateJarArchive( WarPackagingContext context ) + ":" + artifact.getArtifactId() + ":" + artifact.getVersion() + "]", e ); } final String targetFilename = LIB_PATH + archiveName; + final File libDirectory = new File( context.getWebappDirectory(), LIB_PATH ); + final File jarFile = new File( libDirectory, archiveName ); if ( context.getWebappStructure().registerFile( currentProjectOverlay.getId(), targetFilename ) ) { context.addResource( targetFilename ); + context.getWarResourceCopy().addFileCopy( targetFilename, jarFile ); - final File libDirectory = new File( context.getWebappDirectory(), LIB_PATH ); - final File jarFile = new File( libDirectory, archiveName ); final ClassesPackager packager = new ClassesPackager(); packager.packageClasses( context.getClassesDirectory(), jarFile, context.getJarArchiver(), context.getSession(), project, context.getArchive(), diff --git a/src/main/java/org/apache/maven/plugins/war/packaging/WarPackagingContext.java b/src/main/java/org/apache/maven/plugins/war/packaging/WarPackagingContext.java index 29336a23..70bd83e3 100644 --- a/src/main/java/org/apache/maven/plugins/war/packaging/WarPackagingContext.java +++ b/src/main/java/org/apache/maven/plugins/war/packaging/WarPackagingContext.java @@ -19,13 +19,11 @@ * under the License. */ -import java.io.File; -import java.util.List; - import org.apache.maven.archiver.MavenArchiveConfiguration; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.war.util.WarResourceCopy; import org.apache.maven.plugins.war.util.WebappStructure; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.filtering.MavenFileFilter; @@ -33,6 +31,9 @@ import org.codehaus.plexus.archiver.jar.JarArchiver; import org.codehaus.plexus.archiver.manager.ArchiverManager; +import java.io.File; +import java.util.List; + /** * The packaging context. * @@ -96,6 +97,17 @@ public interface WarPackagingContext */ boolean archiveClasses(); + /** + * Skip explded war creation, build web archive on source paths. + */ + boolean skipExplodedWarCreation(); + + /** + * File Copier + * @return + */ + WarResourceCopy getWarResourceCopy(); + /** * Returns the logger to use to output logging event. * diff --git a/src/main/java/org/apache/maven/plugins/war/packaging/WarProjectPackagingTask.java b/src/main/java/org/apache/maven/plugins/war/packaging/WarProjectPackagingTask.java index 59937062..d8296495 100644 --- a/src/main/java/org/apache/maven/plugins/war/packaging/WarProjectPackagingTask.java +++ b/src/main/java/org/apache/maven/plugins/war/packaging/WarProjectPackagingTask.java @@ -19,10 +19,6 @@ * under the License. */ -import java.io.File; -import java.io.IOException; -import java.util.Objects; - import org.apache.maven.model.Resource; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -32,6 +28,10 @@ import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.StringUtils; +import java.io.File; +import java.io.IOException; +import java.util.Objects; + /** * Handles the project own resources, that is: *
    @@ -88,8 +88,8 @@ public void performPackaging( WarPackagingContext context ) // Prepare the INF directories File webinfDir = new File( context.getWebappDirectory(), WEB_INF_PATH ); - webinfDir.mkdirs(); File metainfDir = new File( context.getWebappDirectory(), META_INF_PATH ); + webinfDir.mkdirs(); metainfDir.mkdirs(); handleWebResources( context ); @@ -172,7 +172,15 @@ protected void handleWebAppSourceDirectory( WarPackagingContext context ) } else if ( !context.getWebappSourceDirectory().getAbsolutePath().equals( context.getWebappDirectory().getPath() ) ) { - context.getLog().info( "Copying webapp resources [" + context.getWebappSourceDirectory() + "]" ); + if ( context.skipExplodedWarCreation() ) + { + context.getLog().info( "Collecting webapp resources [" + context.getWebappSourceDirectory() + "]" ); + } + else + { + context.getLog().info( "Copying webapp resources [" + context.getWebappSourceDirectory() + "]" ); + } + final PathSet sources = getFilesToIncludes( context.getWebappSourceDirectory(), context.getWebappSourceIncludes(), context.getWebappSourceExcludes(), context.isWebappSourceIncludeEmptyDirectories() ); @@ -248,7 +256,7 @@ protected void handleDeploymentDescriptors( WarPackagingContext context, File we if ( context.isFilteringDeploymentDescriptors() ) { context.getMavenFileFilter().copyFile( webXml, new File( webinfDir, "web.xml" ), true, - context.getFilterWrappers(), getEncoding( webXml ) ); + context.getFilterWrappers(), getEncoding( webXml ) ); } else { @@ -264,7 +272,7 @@ protected void handleDeploymentDescriptors( WarPackagingContext context, File we { context.getWebappStructure().registerFile( id, WEB_INF_PATH + "/web.xml" ); context.getMavenFileFilter().copyFile( defaultWebXml, new File( webinfDir, "web.xml" ), true, - context.getFilterWrappers(), getEncoding( defaultWebXml ) ); + context.getFilterWrappers(), getEncoding( defaultWebXml ) ); } } @@ -277,8 +285,8 @@ protected void handleDeploymentDescriptors( WarPackagingContext context, File we if ( context.isFilteringDeploymentDescriptors() ) { context.getMavenFileFilter().copyFile( containerConfigXML, new File( metainfDir, xmlFileName ), - true, context.getFilterWrappers(), - getEncoding( containerConfigXML ) ); + true, context.getFilterWrappers(), + getEncoding( containerConfigXML ) ); } else { @@ -318,8 +326,16 @@ public void copyResources( WarPackagingContext context, Resource resource ) + "] does not exist!" ); } - context.getLog().info( "Copying webapp webResources [" + resource.getDirectory() + "] to [" - + context.getWebappDirectory().getAbsolutePath() + "]" ); + if ( context.skipExplodedWarCreation() ) + { + context.getLog().info( "Collecting webapp webResources [" + resource.getDirectory() + "]" ); + } + else + { + context.getLog().info( "Copying webapp webResources [" + resource.getDirectory() + "] to [" + + context.getWebappDirectory().getAbsolutePath() + "]" ); + } + String[] fileNames = getFilesToCopy( resource ); for ( String fileName : fileNames ) { diff --git a/src/main/java/org/apache/maven/plugins/war/util/ClassesPackager.java b/src/main/java/org/apache/maven/plugins/war/util/ClassesPackager.java index 2b14617c..31dd150c 100644 --- a/src/main/java/org/apache/maven/plugins/war/util/ClassesPackager.java +++ b/src/main/java/org/apache/maven/plugins/war/util/ClassesPackager.java @@ -32,6 +32,7 @@ import java.io.File; import java.io.IOException; +import java.util.Map; /** * Packages the content of the classes directory. @@ -41,6 +42,32 @@ public class ClassesPackager { + + public void packageClasses( Map allClassFiles, File targetFile, JarArchiver jarArchiver, + MavenSession session, + MavenProject project, MavenArchiveConfiguration archiveConfiguration, + String outputTimestamp ) + throws MojoExecutionException + { + try + { + final MavenArchiver archiver = new MavenArchiver(); + archiver.setArchiver( jarArchiver ); + archiver.setOutputFile( targetFile ); + archiver.setCreatedBy( "Maven WAR Plugin", "org.apache.maven.plugins", "maven-war-plugin" ); + archiver.configureReproducible( outputTimestamp ); + for ( Map.Entry entry : allClassFiles.entrySet() ) + { + archiver.getArchiver().addFile( entry.getValue(), entry.getKey() ); + } + archiver.createArchive( session, project, archiveConfiguration ); + } + catch ( ArchiverException | ManifestException | IOException | DependencyResolutionRequiredException e ) + { + throw new MojoExecutionException( "Could not create classes archive", e ); + } + } + /** * Package the classes * diff --git a/src/main/java/org/apache/maven/plugins/war/util/SourceTargetMappingResourceFilter.java b/src/main/java/org/apache/maven/plugins/war/util/SourceTargetMappingResourceFilter.java new file mode 100644 index 00000000..30bacf86 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/war/util/SourceTargetMappingResourceFilter.java @@ -0,0 +1,502 @@ +package org.apache.maven.plugins.war.util; + +/* + * 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.codehaus.plexus.archiver.war.WarArchiver; +import org.codehaus.plexus.components.io.attributes.FileAttributes; +import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes; +import org.codehaus.plexus.components.io.resources.PlexusIoFileResourceCollection; +import org.codehaus.plexus.components.io.resources.PlexusIoResource; +import org.codehaus.plexus.components.io.resources.ResourceFactory; +import org.codehaus.plexus.util.AbstractScanner; +import org.codehaus.plexus.util.NioFiles; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * Filter resources for resources collected as SourceTargetMappings + */ +public class SourceTargetMappingResourceFilter +{ + + private WarArchiver warArchiver; + + public SourceTargetMappingResourceFilter( WarArchiver archiver ) + { + this.warArchiver = archiver; + } + + public Map filteredResources( String[] includes, String[] excludes, String prefix, + Map mappings ) throws IOException + { + + // The PlexusIoFileResourceCollection contains platform-specific File.separatorChar which + // is an interesting cause of grief, see PLXCOMP-192 + final ResourceCollection collection = + new ResourceCollection( mappings, warArchiver.getFilenameComparator() ); + collection.setFollowingSymLinks( false ); + + collection.setIncludes( includes ); + collection.setExcludes( excludes ); + collection.setIncludingEmptyDirectories( warArchiver.getIncludeEmptyDirs() ); + collection.setPrefix( prefix ); + collection.setCaseSensitive( true ); + collection.setUsingDefaultExcludes( true ); + + if ( warArchiver.getOverrideDirectoryMode() > -1 || warArchiver.getOverrideFileMode() > -1 + || warArchiver.getOverrideUid() > -1 + || warArchiver.getOverrideGid() > -1 || warArchiver.getOverrideUserName() != null + || warArchiver.getOverrideGroupName() != null ) + { + collection.setOverrideAttributes( warArchiver.getOverrideUid(), warArchiver.getOverrideUserName(), + warArchiver.getOverrideGid(), + warArchiver.getOverrideGroupName(), warArchiver.getOverrideFileMode(), + warArchiver.getOverrideDirectoryMode() ); + } + + if ( warArchiver.getDefaultDirectoryMode() > -1 || warArchiver.getDefaultFileMode() > -1 ) + { + collection.setDefaultAttributes( -1, null, -1, null, warArchiver.getDefaultFileMode(), + warArchiver.getDefaultDirectoryMode() ); + } + + return collection.getResourceMap(); + } + + + private static class ResourceCollection extends PlexusIoFileResourceCollection + { + + private Map sourceTargetMappings; + private Comparator fileNameComparator; + + ResourceCollection( Map mappings, Comparator fileNameComparator ) + { + this.sourceTargetMappings = mappings; + this.fileNameComparator = fileNameComparator; + } + + public Map getResourceMap() throws IOException + { + final SourceTargetMappingResourcesScanner ds = new SourceTargetMappingResourcesScanner(); + ds.setMappings( this.sourceTargetMappings ); + final String[] inc = getIncludes(); + if ( inc != null && inc.length > 0 ) + { + ds.setIncludes( inc ); + } + final String[] exc = getExcludes(); + if ( exc != null && exc.length > 0 ) + { + ds.setExcludes( exc ); + } + if ( isUsingDefaultExcludes() ) + { + ds.addDefaultExcludes(); + } + ds.setCaseSensitive( isCaseSensitive() ); + ds.setFollowSymlinks( isFollowingSymLinks() ); + ds.setFilenameComparator( fileNameComparator ); + ds.scan(); + + final Map result = new HashMap<>(); + if ( isIncludingEmptyDirectories() ) + { + String[] dirs = ds.getIncludedDirectories(); + addResources( result, dirs ); + } + + String[] files = ds.getIncludedFiles(); + addResources( result, files ); + return result; + } + + private void addResources( Map result, String[] resources ) + throws IOException + { + + final HashMap cache1 = new HashMap<>(); + final HashMap cache2 = new HashMap<>(); + for ( String name : resources ) + { + File f = sourceTargetMappings.get( name ); + if ( f != null ) + { + PlexusIoResourceAttributes attrs = new FileAttributes( f, cache1, cache2 ); + attrs = mergeAttributes( attrs, f.isDirectory() ); + + String remappedName = getName( name ); + + PlexusIoResource resource = + ResourceFactory.createResource( f, remappedName, null, getStreamTransformer(), attrs ); + + if ( isSelected( resource ) ) + { + result.put( name, resource ); + } + } + + } + } + } + + + /** + * A revised version + *

    + * filter with target path, but mapping file from source path + */ + private static class SourceTargetMappingResourcesScanner + extends AbstractScanner + { + + + /** + * The files which matched at least one include and no excludes and were selected. + */ + protected Vector filesIncluded; + + /** + * The files which did not match any includes or selectors. + */ + protected Vector filesNotIncluded; + + /** + * The files which matched at least one include and at least one exclude. + */ + protected Vector filesExcluded; + + /** + * The directories which matched at least one include and no excludes and were selected. + */ + protected Vector dirsIncluded; + + /** + * The directories which were found and did not match any includes. + */ + protected Vector dirsNotIncluded; + + /** + * The directories which matched at least one include and at least one exclude. + */ + protected Vector dirsExcluded; + + /** + * The files which matched at least one include and no excludes and which a selector discarded. + */ + protected Vector filesDeselected; + + /** + * The directories which matched at least one include and no excludes but which a selector discarded. + */ + protected Vector dirsDeselected; + + /** + * Whether or not symbolic links should be followed. + * + * @since Ant 1.5 + */ + private boolean followSymlinks = true; + + /** + * Whether or not everything tested so far has been included. + */ + protected boolean everythingIncluded = true; + + private final String[] tokenizedEmpty = tokenizePathToString( "", File.separator ); + + private Map sourceTargetMappings = new HashMap<>(); + + public void setMappings( Map mappings ) + { + this.sourceTargetMappings = mappings; + } + + /** + * Sole constructor. + */ + SourceTargetMappingResourcesScanner() + { + } + + + /** + * Sets whether or not symbolic links should be followed. + * + * @param followSymlinks whether or not symbolic links should be followed + */ + public void setFollowSymlinks( boolean followSymlinks ) + { + this.followSymlinks = followSymlinks; + } + + /** + * Scans the base directory for files which match at least one include pattern and don't match any exclude + * patterns. If there are selectors then the files must pass muster there, as well. + * + * @throws IllegalStateException if the base directory was set incorrectly (i.e. if it is null, + * doesn't exist, or isn't a directory). + */ + public void scan() + throws IllegalStateException + { + + setupDefaultFilters(); + setupMatchPatterns(); + + filesIncluded = new Vector(); + filesNotIncluded = new Vector(); + filesExcluded = new Vector(); + filesDeselected = new Vector(); + dirsIncluded = new Vector(); + dirsNotIncluded = new Vector(); + dirsExcluded = new Vector(); + dirsDeselected = new Vector(); + + if ( isIncluded( "", tokenizedEmpty ) ) + { + + if ( !isExcluded( "", tokenizedEmpty ) ) + { + dirsIncluded.addElement( "" ); + } + else + { + dirsExcluded.addElement( "" ); + } + } + else + { + dirsNotIncluded.addElement( "" ); + } + scanAllFiles( "", true ); + } + + + /** + * Scans the given directory for files and directories. Found files and directories are placed in their + * respective collections, based on the matching of includes, excludes, and the selectors. When a directory is + * found, it is scanned recursively. + * + * @param vpath The path relative to the base directory (needed to prevent problems with an absolute path when + * using dir). Must not be null. + * @param fast Whether or not this call is part of a fast scan. + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + */ + protected void scanAllFiles( String vpath, boolean fast ) + { + String[] newfiles = sourceTargetMappings.keySet().toArray( new String[0] ); + if ( newfiles == null ) + { + newfiles = new String[0]; + } + + if ( !followSymlinks ) + { + ArrayList noLinks = new ArrayList(); + for ( String newfile : newfiles ) + { + try + { + File sourceFile = sourceTargetMappings.get( newfile ); + File dir = sourceFile.getParentFile(); + if ( isParentSymbolicLink( dir, newfile ) ) + { + String name = vpath + newfile; + if ( sourceFile.isDirectory() ) + { + dirsExcluded.addElement( name ); + } + else + { + filesExcluded.addElement( name ); + } + } + else + { + noLinks.add( newfile ); + } + } + catch ( IOException ioe ) + { + String msg = "IOException caught while checking " + "for links, couldn't get canonical path!"; + // will be caught and redirected to Ant's logging system + System.err.println( msg ); + noLinks.add( newfile ); + } + } + newfiles = noLinks.toArray( new String[noLinks.size()] ); + } + + if ( filenameComparator != null ) + { + Arrays.sort( newfiles, filenameComparator ); + } + + for ( String newfile : newfiles ) + { + File file = sourceTargetMappings.get( newfile ); + String name = vpath + newfile; + String[] tokenizedName = tokenizePathToString( name, "/" ); + if ( file.isFile() ) + { + if ( isIncluded( name, tokenizedName ) ) + { + if ( !isExcluded( name, tokenizedName ) ) + { + filesIncluded.addElement( name ); + } + else + { + everythingIncluded = false; + filesExcluded.addElement( name ); + } + } + else + { + everythingIncluded = false; + filesNotIncluded.addElement( name ); + } + } + else + { + throw new IllegalStateException( "Should not be here" ); + } + } + } + + + /** + * Returns the names of the files which matched at least one of the include patterns and none of the exclude + * patterns. The names are relative to the base directory. + * + * @return the names of the files which matched at least one of the include patterns and none of the exclude + * patterns. + */ + public String[] getIncludedFiles() + { + String[] files = new String[filesIncluded.size()]; + filesIncluded.copyInto( files ); + return files; + } + + /** + * Returns the names of the directories which matched at least one of the include patterns and none of the + * exclude patterns. The names are relative to the base directory. + * + * @return the names of the directories which matched at least one of the include patterns and none of the + * exclude patterns. + */ + public String[] getIncludedDirectories() + { + String[] directories = new String[dirsIncluded.size()]; + dirsIncluded.copyInto( directories ); + return directories; + } + + @Override + public File getBasedir() + { + throw new IllegalStateException( "Should not be here" ); + } + + /** + *

    Checks whether the parent of this file is a symbolic link.

    + * + *

    For java versions prior to 7 It doesn't really test for symbolic links but whether the canonical and + * absolute paths of the file are identical - this may lead to false positives on some platforms.

    + * + * @param parent the parent directory of the file to test + * @param name the name of the file to test. + * @return true if it's a symbolic link + * @throws IOException . + * @since Ant 1.5 + */ + public boolean isParentSymbolicLink( File parent, String name ) + throws IOException + { + if ( Java7Detector.isJava7() ) + { + return NioFiles.isSymbolicLink( parent ); + } + File resolvedParent = new File( parent.getCanonicalPath() ); + File toTest = new File( resolvedParent, name ); + return !toTest.getAbsolutePath().equals( toTest.getCanonicalPath() ); + } + + static String[] tokenizePathToString( @Nonnull String path, @Nonnull String separator ) + { + List ret = new ArrayList(); + StringTokenizer st = new StringTokenizer( path, separator ); + while ( st.hasMoreTokens() ) + { + ret.add( st.nextToken() ); + } + return ret.toArray( new String[ret.size()] ); + } + } + + /** + * Java7 feature detection + * + * @author Kristian Rosenvold + */ + static class Java7Detector + { + + private static final boolean IS_JAVA_7; + + static + { + boolean isJava7x = true; + try + { + Class.forName( "java.nio.file.Files" ); + } + catch ( Exception e ) + { + isJava7x = false; + } + IS_JAVA_7 = isJava7x; + } + + public static boolean isJava7() + { + return IS_JAVA_7; + } + } + + +} diff --git a/src/main/java/org/apache/maven/plugins/war/util/WarResourceCopy.java b/src/main/java/org/apache/maven/plugins/war/util/WarResourceCopy.java new file mode 100644 index 00000000..92258d40 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/war/util/WarResourceCopy.java @@ -0,0 +1,84 @@ +package org.apache.maven.plugins.war.util; + +/* + * 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.codehaus.plexus.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Skip Copying file when build war directly from source paths. Does not register filtered files that copy with + * copyWithMavenFilter, + */ +public class WarResourceCopy +{ + + private boolean skipCopy; + + private Map sourceTargetMappings; + + public WarResourceCopy( boolean skipCopy ) + { + this.skipCopy = skipCopy; + this.sourceTargetMappings = new HashMap<>(); + } + + public void addFileCopy( String targetPath, File sourceFile ) + { + if ( skipCopy ) + { + this.sourceTargetMappings.put( targetPath, sourceFile ); + } + } + + public Map getSourceTargetMappings() + { + return this.sourceTargetMappings; + } + + public Map getFilesWithPrefix( String prefix, boolean removePrefix ) + { + Map files = new HashMap<>(); + Set keys = sourceTargetMappings.keySet(); + for ( String key : keys ) + { + if ( key.startsWith( prefix ) ) + { + files.put( removePrefix ? key.substring( prefix.length() ) : key, sourceTargetMappings.get( key ) ); + } + } + + return files; + } + + public void copy( File source, File targetFile ) throws IOException + { + if ( !skipCopy ) + { + FileUtils.copyFile( source.getCanonicalFile(), targetFile ); + // preserve timestamp + targetFile.setLastModified( source.lastModified() ); + } + } +}