Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
Expand All @@ -42,6 +46,7 @@
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.war.overlay.OverlayManager;
import org.apache.maven.plugins.war.packaging.AbstractWarPackagingTask;
import org.apache.maven.plugins.war.packaging.CopyUserManifestTask;
import org.apache.maven.plugins.war.packaging.OverlayPackagingTask;
import org.apache.maven.plugins.war.packaging.WarPackagingContext;
Expand All @@ -58,6 +63,7 @@
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.interpolation.InterpolationException;

/**
* Contains common jobs for WAR mojos.
Expand Down Expand Up @@ -655,6 +661,10 @@ private class DefaultWarPackagingContext implements WarPackagingContext {
outdatedCheckPath = outdatedCheckPath.replace('/', '\\');
}
}
// MWAR-443: Compute the expected file names of non-runtime-scope library artifacts
// so files placed by other plugins (e.g., maven-dependency-plugin) are not marked
// as outdated and subsequently deleted.
final Set<String> nonRuntimeArtifactFileNames = getNonRuntimeArtifactFileNames();
Files.walkFileTree(webappDirectory.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Expand All @@ -665,7 +675,17 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
.relativize(file)
.toString();
if (checkAllPathsForOutdated() || path.startsWith(outdatedCheckPath)) {
outdatedResources.add(path);
// MWAR-443: Files under the artifact lib directory that match a
// non-runtime-scope artifact may have been placed by another plugin
// (e.g., maven-dependency-plugin), so do not mark them as outdated.
// Stale files that are not part of the current project (e.g. outdated
// versions of runtime artifacts) are still removed.
String normalizedPath = path.replace('\\', '/');
if (!normalizedPath.startsWith(AbstractWarPackagingTask.LIB_PATH)
|| !nonRuntimeArtifactFileNames.contains(
file.toFile().getName())) {
outdatedResources.add(path);
}
}
}
return super.visitFile(file, attrs);
Expand All @@ -682,6 +702,31 @@ protected boolean checkAllPathsForOutdated() {
return outdatedCheckPath.equals("/");
}

/**
* Returns the set of expected target filenames in {@code WEB-INF/lib/} for non-runtime-scope library
* artifacts. Used to avoid marking files placed by other plugins (e.g., maven-dependency-plugin) as
* outdated and subsequently deleting them (MWAR-443).
*/
private Set<String> getNonRuntimeArtifactFileNames() {
Set<String> fileNames = new HashSet<>();
ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME);
if (project.getArtifacts() != null) {
for (Artifact artifact : project.getArtifacts()) {
if (!filter.include(artifact) && AbstractWarPackagingTask.isLibraryType(artifact.getType())) {
try {
String targetFileName = AbstractWarPackagingTask.evaluateFileNameMapping(
getOutputFileNameMapping(), artifact);
fileNames.add(
AbstractWarPackagingTask.getLibraryFileName(artifact.getType(), targetFileName));
} catch (InterpolationException e) {
getLog().debug("Could not compute expected filename for artifact: " + artifact, e);
}
}
}
}
return fileNames;
}

@Override
public MavenProject getProject() {
return project;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@
// fix for MWAR-36, ensures that the parent dir are created first
targetFile.getParentFile().mkdirs();

context.getMavenFileFilter().copyFile(file, targetFile, true, context.getFilterWrappers(), encoding);

Check warning on line 259 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-17-zulu 3.10.0-rc-1

copyFile(java.io.File,java.io.File,boolean,java.util.List<org.apache.maven.shared.filtering.FilterWrapper>,java.lang.String) in org.apache.maven.shared.filtering.MavenFileFilter has been deprecated

Check warning on line 259 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / macos-latest jdk-25-zulu 3.10.0-rc-1

copyFile(java.io.File,java.io.File,boolean,java.util.List<org.apache.maven.shared.filtering.FilterWrapper>,java.lang.String) in org.apache.maven.shared.filtering.MavenFileFilter has been deprecated

Check warning on line 259 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-21-zulu 3.10.0-rc-1

copyFile(java.io.File,java.io.File,boolean,java.util.List<org.apache.maven.shared.filtering.FilterWrapper>,java.lang.String) in org.apache.maven.shared.filtering.MavenFileFilter has been deprecated

Check warning on line 259 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-25-zulu 3.10.0-rc-1

copyFile(java.io.File,java.io.File,boolean,java.util.List<org.apache.maven.shared.filtering.FilterWrapper>,java.lang.String) in org.apache.maven.shared.filtering.MavenFileFilter has been deprecated

Check warning on line 259 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / macos-latest jdk-21-zulu 3.10.0-rc-1

copyFile(java.io.File,java.io.File,boolean,java.util.List<org.apache.maven.shared.filtering.FilterWrapper>,java.lang.String) in org.apache.maven.shared.filtering.MavenFileFilter has been deprecated

Check warning on line 259 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-8-zulu 3.10.0-rc-1

copyFile(java.io.File,java.io.File,boolean,java.util.List<org.apache.maven.shared.filtering.FilterWrapper>,java.lang.String) in org.apache.maven.shared.filtering.MavenFileFilter has been deprecated

Check warning on line 259 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / macos-latest jdk-8-zulu 3.10.0-rc-1

copyFile(java.io.File,java.io.File,boolean,java.util.List<org.apache.maven.shared.filtering.FilterWrapper>,java.lang.String) in org.apache.maven.shared.filtering.MavenFileFilter has been deprecated
} catch (MavenFilteringException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
Expand Down Expand Up @@ -334,7 +334,7 @@

try {
JarArchiver archiver = context.getJarArchiver();
archiver.addDirectory(source);

Check warning on line 337 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-17-zulu 3.10.0-rc-1

addDirectory(java.io.File) in org.codehaus.plexus.archiver.AbstractArchiver has been deprecated

Check warning on line 337 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / macos-latest jdk-25-zulu 3.10.0-rc-1

addDirectory(java.io.File) in org.codehaus.plexus.archiver.AbstractArchiver has been deprecated

Check warning on line 337 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-21-zulu 3.10.0-rc-1

addDirectory(java.io.File) in org.codehaus.plexus.archiver.AbstractArchiver has been deprecated

Check warning on line 337 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-25-zulu 3.10.0-rc-1

addDirectory(java.io.File) in org.codehaus.plexus.archiver.AbstractArchiver has been deprecated

Check warning on line 337 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / macos-latest jdk-21-zulu 3.10.0-rc-1

addDirectory(java.io.File) in org.codehaus.plexus.archiver.AbstractArchiver has been deprecated

Check warning on line 337 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / ubuntu-latest jdk-8-zulu 3.10.0-rc-1

addDirectory(java.io.File) in org.codehaus.plexus.archiver.AbstractArchiver has been deprecated

Check warning on line 337 in src/main/java/org/apache/maven/plugins/war/packaging/AbstractWarPackagingTask.java

View workflow job for this annotation

GitHub Actions / Verify / macos-latest jdk-8-zulu 3.10.0-rc-1

addDirectory(java.io.File) in org.codehaus.plexus.archiver.AbstractArchiver has been deprecated
archiver.setDestFile(destination);
archiver.createArchive();
} catch (ArchiverException e) {
Expand Down Expand Up @@ -428,8 +428,22 @@
*/
protected String getArtifactFinalName(WarPackagingContext context, Artifact artifact)
throws InterpolationException {
if (context.getOutputFileNameMapping() != null) {
return MappingUtils.evaluateFileNameMapping(context.getOutputFileNameMapping(), artifact);
return evaluateFileNameMapping(context.getOutputFileNameMapping(), artifact);
}

/**
* Evaluates the file name mapping for the given artifact, using the {@code outputFileNameMapping} when set,
* or the standard naming scheme otherwise.
*
* @param outputFileNameMapping the output file name mapping, may be {@code null}
* @param artifact the artifact
* @return the converted filename of the artifact
* @throws InterpolationException in case of interpolation problem
*/
public static String evaluateFileNameMapping(String outputFileNameMapping, Artifact artifact)
throws InterpolationException {
if (outputFileNameMapping != null) {
return MappingUtils.evaluateFileNameMapping(outputFileNameMapping, artifact);
}

String classifier = artifact.getClassifier();
Expand Down Expand Up @@ -496,4 +510,39 @@
}
return true;
}

/**
* Returns {@code true} if the artifact with the given type would be placed in {@code WEB-INF/lib/}.
*
* @param type the artifact type
* @return {@code true} if the type is a library type that goes to WEB-INF/lib
*/
public static boolean isLibraryType(String type) {
return "jar".equals(type)
|| "ejb".equals(type)
|| "ejb-client".equals(type)
|| "test-jar".equals(type)
|| "bundle".equals(type)
|| "par".equals(type);
}

/**
* Returns the file name the given artifact would have in {@code WEB-INF/lib/}, converting the extension
* for artifact types that are packaged under a different extension (e.g., {@code par} files are packaged
* as {@code .jar}).
*
* @param type the artifact type
* @param targetFileName the mapped file name of the artifact
* @return the file name the artifact will have in {@code WEB-INF/lib/}
*/
public static String getLibraryFileName(String type, String targetFileName) {
if ("par".equals(type)) {
int extensionIndex = targetFileName.lastIndexOf('.');
if (extensionIndex > 0) {
return targetFileName.substring(0, extensionIndex) + ".jar";
}
return targetFileName + ".jar";
}
return targetFileName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,12 @@ public void performPackaging(WarPackagingContext context) throws MojoExecutionEx
copyFile(id, context, artifact.getFile(), MODULES_PATH + targetFileName);
} else if ("xar".equals(type)) {
copyFile(id, context, artifact.getFile(), EXTENSIONS_PATH + targetFileName);
} else if ("jar".equals(type)
|| "ejb".equals(type)
|| "ejb-client".equals(type)
|| "test-jar".equals(type)
|| "bundle".equals(type)) {
copyFile(id, context, artifact.getFile(), LIB_PATH + targetFileName);
} else if ("par".equals(type)) {
targetFileName = targetFileName.substring(0, targetFileName.lastIndexOf('.')) + ".jar";
copyFile(id, context, artifact.getFile(), LIB_PATH + targetFileName);
} else if (isLibraryType(type)) {
copyFile(
id,
context,
artifact.getFile(),
LIB_PATH + getLibraryFileName(type, targetFileName));
} else if ("war".equals(type)) {
// Nothing to do here, it is an overlay and it's already handled
context.getLog()
Expand Down
112 changes: 112 additions & 0 deletions src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@
*/
package org.apache.maven.plugins.war;

import javax.inject.Inject;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import org.apache.maven.api.plugin.testing.InjectMojo;
import org.apache.maven.api.plugin.testing.MojoExtension;
import org.apache.maven.api.plugin.testing.MojoParameter;
import org.apache.maven.api.plugin.testing.MojoTest;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.testing.stubs.ArtifactStub;
import org.apache.maven.plugins.war.stub.AarArtifactStub;
import org.apache.maven.plugins.war.stub.EJBArtifactStub;
Expand All @@ -50,10 +55,14 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

@MojoTest
public class WarExplodedMojoTest {

@Inject
private MavenSession mavenSession;

@InjectMojo(goal = "exploded", pom = "src/test/resources/unit/warexplodedmojo/plugin-config.xml")
@MojoParameter(
name = "classesDirectory",
Expand Down Expand Up @@ -934,4 +943,107 @@ public void testExplodedWarWithOutputFileNameMappingAndDuplicateDependencies(War
expectedEJBArtifact.delete();
expectedEJBDupArtifact.delete();
}

/**
* Test for MWAR-443: Files placed by maven-dependency-plugin in WEB-INF/lib
* for provided-scope artifacts should not be deleted by the WAR plugin,
* while stale files not part of the current project are still removed.
*/
@InjectMojo(goal = "exploded", pom = "src/test/resources/unit/warexplodedmojo/plugin-config.xml")
@MojoParameter(
name = "classesDirectory",
value = "target/test-classes/unit/warexplodedmojo/SimpleExplodedWar-test-data/classes/")
@MojoParameter(
name = "warSourceDirectory",
value = "target/test-classes/unit/warexplodedmojo/SimpleExplodedWar-test-data/source/")
@MojoParameter(name = "webappDirectory", value = "target/test-classes/unit/warexplodedmojo/MWAR443Test")
@MojoParameter(name = "outdatedCheckPath", value = "WEB-INF/lib/")
@Test
public void testProvidedScopeArtifactPlacedByDependencyPluginShouldNotBeDeleted(WarExplodedMojo mojo)
throws Exception {
// Ensure session has a start time so the outdated resource detection is active.
// Uses same pattern as WarExplodedMojoFilteringTest which also calls
// when(mavenSession.get...()).thenReturn(...)
when(mavenSession.getStartTime()).thenReturn(new Date());

// A provided-scope library artifact is not copied by the WAR plugin itself, but may
// have been placed in WEB-INF/lib by another plugin (e.g., maven-dependency-plugin).
MavenProjectArtifactsStub project = new MavenProjectArtifactsStub();
JarArtifactStub providedArtifact = new JarArtifactStub(getBasedir(), new DefaultArtifactHandler("jar"));
providedArtifact.setScope(Artifact.SCOPE_PROVIDED);
providedArtifact.setArtifactId("derbyLocale_cs");
providedArtifact.setVersion("10.14.2.0");
project.addArtifact(providedArtifact);
mojo.setProject(project);

File webAppDirectory = mojo.getWebappDirectory();
File libDir = new File(webAppDirectory, "WEB-INF/lib");
// file placed by maven-dependency-plugin for the provided-scope artifact, older than the session
File providedJar = new File(libDir, "derbyLocale_cs-10.14.2.0.jar");
// stale file from a previous build that is not part of the current project
File staleRuntimeJar = new File(libDir, "stale-runtime-1.0.jar");
try {
libDir.mkdirs();
providedJar.createNewFile();
staleRuntimeJar.createNewFile();
// Set timestamps to the past (before session start)
providedJar.setLastModified(0L);
staleRuntimeJar.setLastModified(0L);

mojo.execute();

// The file placed by another plugin should NOT be deleted
assertTrue(providedJar.exists(), "provided-scope artifact should not be deleted by WAR plugin");
// Stale files that are not part of the current project are still removed
assertFalse(staleRuntimeJar.exists(), "stale runtime artifact should be deleted by WAR plugin");
} finally {
FileUtils.deleteDirectory(webAppDirectory);
}
}

/**
* Test for MWAR-443: With an {@code outputFileNameMapping} configured, files placed by
* maven-dependency-plugin for provided-scope artifacts should not be deleted by the WAR plugin.
*/
@InjectMojo(goal = "exploded", pom = "src/test/resources/unit/warexplodedmojo/plugin-config.xml")
@MojoParameter(
name = "classesDirectory",
value = "target/test-classes/unit/warexplodedmojo/SimpleExplodedWar-test-data/classes/")
@MojoParameter(
name = "warSourceDirectory",
value = "target/test-classes/unit/warexplodedmojo/SimpleExplodedWar-test-data/source/")
@MojoParameter(name = "webappDirectory", value = "target/test-classes/unit/warexplodedmojo/MWAR443TestStripVersion")
@MojoParameter(name = "outdatedCheckPath", value = "WEB-INF/lib/")
@MojoParameter(name = "outputFileNameMapping", value = "@{artifactId}@.@{extension}@")
@Test
public void testProvidedScopeArtifactPlacedByDependencyPluginWithFileNameMappingShouldNotBeDeleted(
WarExplodedMojo mojo) throws Exception {
when(mavenSession.getStartTime()).thenReturn(new Date());

MavenProjectArtifactsStub project = new MavenProjectArtifactsStub();
JarArtifactStub providedArtifact = new JarArtifactStub(getBasedir(), new DefaultArtifactHandler("jar"));
providedArtifact.setScope(Artifact.SCOPE_PROVIDED);
providedArtifact.setArtifactId("derbyLocale_cs");
providedArtifact.setVersion("10.14.2.0");
project.addArtifact(providedArtifact);
mojo.setProject(project);

File webAppDirectory = mojo.getWebappDirectory();
File libDir = new File(webAppDirectory, "WEB-INF/lib");
// with stripVersion-style mapping the dependency-plugin places the file without the version
File providedJar = new File(libDir, "derbyLocale_cs.jar");
try {
libDir.mkdirs();
providedJar.createNewFile();
// Set timestamp to the past (before session start)
providedJar.setLastModified(0L);

mojo.execute();

// The file placed by another plugin should NOT be deleted
assertTrue(providedJar.exists(), "provided-scope artifact should not be deleted by WAR plugin");
} finally {
FileUtils.deleteDirectory(webAppDirectory);
}
Comment on lines +1045 to +1047
}
}
Loading