From bd0199260ab467b1947be0893371be182907e79d Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Tue, 28 Jul 2026 14:01:05 +0000 Subject: [PATCH 1/2] MWAR-443: Fix deletion of files placed by maven-dependency-plugin Fixes #522 Address code review comments: - Extract shared isLibraryType() in AbstractWarPackagingTask to avoid duplicating artifact type list across the codebase - Use outputFileNameMapping in getRuntimeArtifactFileNames() when set - Use isLibraryType() in both ArtifactsPackagingTask and the outdated resource detection logic - Fix test cleanup with try/finally and recursive delete --- .../maven/plugins/war/AbstractWarMojo.java | 65 ++++++++++++++++++- .../packaging/AbstractWarPackagingTask.java | 15 +++++ .../war/packaging/ArtifactsPackagingTask.java | 12 ++-- .../plugins/war/WarExplodedMojoTest.java | 51 +++++++++++++++ 4 files changed, 134 insertions(+), 9 deletions(-) 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 56b1e6c0..5b05e695 100644 --- a/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java +++ b/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java @@ -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; @@ -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; @@ -54,6 +59,7 @@ import org.apache.maven.shared.filtering.MavenFilteringException; import org.apache.maven.shared.filtering.MavenResourcesExecution; import org.apache.maven.shared.filtering.MavenResourcesFiltering; +import org.apache.maven.shared.mapping.MappingUtils; import org.apache.maven.shared.utils.StringUtils; import org.codehaus.plexus.archiver.jar.JarArchiver; import org.codehaus.plexus.archiver.manager.ArchiverManager; @@ -655,6 +661,10 @@ private class DefaultWarPackagingContext implements WarPackagingContext { outdatedCheckPath = outdatedCheckPath.replace('/', '\\'); } } + // MWAR-443: Compute expected filenames for runtime-scope artifacts + // so we don't mark files placed by other plugins (e.g., maven-dependency-plugin) + // as outdated and subsequently delete them. + final Set runtimeArtifactFileNames = getRuntimeArtifactFileNames(); Files.walkFileTree(webappDirectory.toPath(), new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { @@ -665,7 +675,19 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO .relativize(file) .toString(); if (checkAllPathsForOutdated() || path.startsWith(outdatedCheckPath)) { - outdatedResources.add(path); + // MWAR-443: For files under the artifact lib directory, + // only mark as outdated if they match a runtime-scope artifact + // that the WAR plugin would copy. This prevents deleting files + // placed by other plugins (e.g., maven-dependency-plugin) for + // non-runtime-scope dependencies. + String normalizedPath = path.replace('\\', '/'); + if (normalizedPath.startsWith("WEB-INF/lib/") + && !runtimeArtifactFileNames.contains( + file.toFile().getName())) { + // Skip: file was placed by another plugin, not managed by WAR plugin + } else { + outdatedResources.add(path); + } } } return super.visitFile(file, attrs); @@ -682,6 +704,47 @@ protected boolean checkAllPathsForOutdated() { return outdatedCheckPath.equals("/"); } + /** + * Returns the set of expected target filenames for runtime-scope 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 getRuntimeArtifactFileNames() { + Set fileNames = new HashSet<>(); + ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME); + if (project.getArtifacts() != null) { + for (Artifact artifact : project.getArtifacts()) { + if (!artifact.isOptional() + && filter.include(artifact) + && AbstractWarPackagingTask.isLibraryType(artifact.getType())) { + try { + String targetFileName; + if (getOutputFileNameMapping() != null) { + targetFileName = + MappingUtils.evaluateFileNameMapping(getOutputFileNameMapping(), artifact); + } else { + String classifier = artifact.getClassifier(); + if (classifier != null && !classifier.trim().isEmpty()) { + targetFileName = MappingUtils.evaluateFileNameMapping( + MappingUtils.DEFAULT_FILE_NAME_MAPPING_CLASSIFIER, artifact); + } else { + targetFileName = MappingUtils.evaluateFileNameMapping( + MappingUtils.DEFAULT_FILE_NAME_MAPPING, artifact); + } + } + if ("par".equals(artifact.getType())) { + targetFileName = targetFileName.substring(0, targetFileName.lastIndexOf('.')) + ".jar"; + } + fileNames.add(targetFileName); + } catch (Exception e) { + getLog().debug("Could not compute expected filename for artifact: " + artifact, e); + } + } + } + } + return fileNames; + } + @Override public MavenProject getProject() { return project; 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 ee0fa8d1..b5540eb0 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 @@ -496,4 +496,19 @@ private boolean isExcluded(String targetFilename, List packagingIncludes } 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); + } } diff --git a/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java b/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java index df33c7e7..d4cf5e8f 100644 --- a/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java +++ b/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java @@ -98,14 +98,10 @@ 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"; + } else if (isLibraryType(type)) { + if ("par".equals(type)) { + targetFileName = targetFileName.substring(0, targetFileName.lastIndexOf('.')) + ".jar"; + } copyFile(id, context, artifact.getFile(), LIB_PATH + targetFileName); } else if ("war".equals(type)) { // Nothing to do here, it is an overlay and it's already handled diff --git a/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java b/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java index fda13e50..0a884601 100644 --- a/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java @@ -18,8 +18,11 @@ */ 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; @@ -27,6 +30,7 @@ import org.apache.maven.api.plugin.testing.MojoParameter; import org.apache.maven.api.plugin.testing.MojoTest; 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; @@ -50,10 +54,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", @@ -934,4 +942,47 @@ 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. + */ + @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()); + + File webAppDirectory = mojo.getWebappDirectory(); + File libDir = new File(webAppDirectory, "WEB-INF/lib"); + File providedJar = new File(libDir, "derbyLocale_cs-10.14.2.0.jar"); + try { + // Setup: Create a file in WEB-INF/lib with an old timestamp, + // simulating a file placed by maven-dependency-plugin for a provided-scope artifact + libDir.mkdirs(); + providedJar.createNewFile(); + // Set timestamp to something in the past (before session start) + providedJar.setLastModified(0L); + + mojo.execute(); + + // The file should NOT be deleted - it was placed by another plugin + assertTrue(providedJar.exists(), "provided-scope artifact should not be deleted by WAR plugin"); + } finally { + // Cleanup + providedJar.delete(); + libDir.delete(); + } + } } From f7b84d5129bad4ddb446d65caf38652e7791fdd3 Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Fri, 31 Jul 2026 11:43:57 +0000 Subject: [PATCH 2/2] MWAR-443: Address code review comments on outdated resource detection - Invert the outdated resource detection to protect non-runtime-scope library artifacts instead of filtering out runtime-scope artifacts: stale files that are not part of the current project (e.g. outdated versions of runtime artifacts) are still removed. - Move the fileNameMapping evaluation and the par to jar conversion into shared helpers in AbstractWarPackagingTask and reuse them from both the outdated resource detection and ArtifactsPackagingTask. - Catch InterpolationException instead of Exception when evaluating the file name mapping. - Strengthen the tests: use a real project stub, cover the outputFileNameMapping case and the removal of stale runtime files, and clean up test resources recursively. --- .../maven/plugins/war/AbstractWarMojo.java | 62 ++++++--------- .../packaging/AbstractWarPackagingTask.java | 38 ++++++++- .../war/packaging/ArtifactsPackagingTask.java | 9 ++- .../plugins/war/WarExplodedMojoTest.java | 77 +++++++++++++++++-- 4 files changed, 132 insertions(+), 54 deletions(-) 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 5b05e695..6f847f9a 100644 --- a/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java +++ b/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java @@ -59,11 +59,11 @@ import org.apache.maven.shared.filtering.MavenFilteringException; import org.apache.maven.shared.filtering.MavenResourcesExecution; import org.apache.maven.shared.filtering.MavenResourcesFiltering; -import org.apache.maven.shared.mapping.MappingUtils; import org.apache.maven.shared.utils.StringUtils; 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. @@ -661,10 +661,10 @@ private class DefaultWarPackagingContext implements WarPackagingContext { outdatedCheckPath = outdatedCheckPath.replace('/', '\\'); } } - // MWAR-443: Compute expected filenames for runtime-scope artifacts - // so we don't mark files placed by other plugins (e.g., maven-dependency-plugin) - // as outdated and subsequently delete them. - final Set runtimeArtifactFileNames = getRuntimeArtifactFileNames(); + // 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 nonRuntimeArtifactFileNames = getNonRuntimeArtifactFileNames(); Files.walkFileTree(webappDirectory.toPath(), new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { @@ -675,17 +675,15 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO .relativize(file) .toString(); if (checkAllPathsForOutdated() || path.startsWith(outdatedCheckPath)) { - // MWAR-443: For files under the artifact lib directory, - // only mark as outdated if they match a runtime-scope artifact - // that the WAR plugin would copy. This prevents deleting files - // placed by other plugins (e.g., maven-dependency-plugin) for - // non-runtime-scope dependencies. + // 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("WEB-INF/lib/") - && !runtimeArtifactFileNames.contains( + if (!normalizedPath.startsWith(AbstractWarPackagingTask.LIB_PATH) + || !nonRuntimeArtifactFileNames.contains( file.toFile().getName())) { - // Skip: file was placed by another plugin, not managed by WAR plugin - } else { outdatedResources.add(path); } } @@ -705,38 +703,22 @@ protected boolean checkAllPathsForOutdated() { } /** - * Returns the set of expected target filenames for runtime-scope artifacts. - * Used to avoid marking files placed by other plugins (e.g., maven-dependency-plugin) - * as outdated and subsequently deleting them (MWAR-443). + * 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 getRuntimeArtifactFileNames() { + private Set getNonRuntimeArtifactFileNames() { Set fileNames = new HashSet<>(); ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME); if (project.getArtifacts() != null) { for (Artifact artifact : project.getArtifacts()) { - if (!artifact.isOptional() - && filter.include(artifact) - && AbstractWarPackagingTask.isLibraryType(artifact.getType())) { + if (!filter.include(artifact) && AbstractWarPackagingTask.isLibraryType(artifact.getType())) { try { - String targetFileName; - if (getOutputFileNameMapping() != null) { - targetFileName = - MappingUtils.evaluateFileNameMapping(getOutputFileNameMapping(), artifact); - } else { - String classifier = artifact.getClassifier(); - if (classifier != null && !classifier.trim().isEmpty()) { - targetFileName = MappingUtils.evaluateFileNameMapping( - MappingUtils.DEFAULT_FILE_NAME_MAPPING_CLASSIFIER, artifact); - } else { - targetFileName = MappingUtils.evaluateFileNameMapping( - MappingUtils.DEFAULT_FILE_NAME_MAPPING, artifact); - } - } - if ("par".equals(artifact.getType())) { - targetFileName = targetFileName.substring(0, targetFileName.lastIndexOf('.')) + ".jar"; - } - fileNames.add(targetFileName); - } catch (Exception e) { + 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); } } 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 b5540eb0..a7149894 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 @@ -428,8 +428,22 @@ protected PathSet getFilesToIncludes( */ 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(); @@ -511,4 +525,24 @@ public static boolean isLibraryType(String 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; + } } diff --git a/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java b/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java index d4cf5e8f..6d00a3df 100644 --- a/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java +++ b/src/main/java/org/apache/maven/plugins/war/packaging/ArtifactsPackagingTask.java @@ -99,10 +99,11 @@ public void performPackaging(WarPackagingContext context) throws MojoExecutionEx } else if ("xar".equals(type)) { copyFile(id, context, artifact.getFile(), EXTENSIONS_PATH + targetFileName); } else if (isLibraryType(type)) { - if ("par".equals(type)) { - targetFileName = targetFileName.substring(0, targetFileName.lastIndexOf('.')) + ".jar"; - } - copyFile(id, context, artifact.getFile(), LIB_PATH + targetFileName); + 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() diff --git a/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java b/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java index 0a884601..c964cb70 100644 --- a/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/war/WarExplodedMojoTest.java @@ -29,6 +29,7 @@ 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; @@ -945,7 +946,8 @@ public void testExplodedWarWithOutputFileNameMappingAndDuplicateDependencies(War /** * 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. + * 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( @@ -964,25 +966,84 @@ public void testProvidedScopeArtifactPlacedByDependencyPluginShouldNotBeDeleted( // 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 { - // Setup: Create a file in WEB-INF/lib with an old timestamp, - // simulating a file placed by maven-dependency-plugin for a provided-scope artifact libDir.mkdirs(); providedJar.createNewFile(); - // Set timestamp to something in the past (before session start) + // Set timestamp to the past (before session start) providedJar.setLastModified(0L); mojo.execute(); - // The file should NOT be deleted - it was placed by another plugin + // The file placed by another plugin should NOT be deleted assertTrue(providedJar.exists(), "provided-scope artifact should not be deleted by WAR plugin"); } finally { - // Cleanup - providedJar.delete(); - libDir.delete(); + FileUtils.deleteDirectory(webAppDirectory); } } }