Skip to content

Commit 6d84663

Browse files
rzo1slachiewicz
authored andcommitted
Restore "first <resource> entry wins" for overlapping declarations
When two <resource> entries cover the same source directory, the file written by the first entry must not be silently replaced by the second. 3.3.1 produced this behaviour as a side effect of the timestamp-based overwrite gate in FilteringUtils.copyFile, which MSHARED-1216 (commit 1371dcf, "Use a caching output stream when filtering resources") removed when it switched to CachingOutputStream / CachingWriter — every write now goes through unconditionally if the bytes differ, so an unfiltered second entry clobbers the filtered first entry. DefaultMavenResourcesFiltering now tracks destination paths already written within a single filterResources(...) call and skips subsequent writes that would land on the same path. Explicit opt-in (MavenResourcesExecution.setOverwrite(true), i.e. ChangeDetection.ALWAYS) keeps the previous "second entry clobbers" behaviour for callers that relied on it. The existing flatten-collision policy is left untouched — a duplicate flattened name still triggers the warn-or-throw path, since that case is conceptually distinct. Adds a unit test in DefaultMavenResourcesFilteringTest covering the explicit-overwrite escape hatch. The OverlappingResourcesFilteringTest introduced in the previous commit now passes against this code.
1 parent 306db11 commit 6d84663

3 files changed

Lines changed: 71 additions & 2 deletions

File tree

‎src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@
3232
import java.nio.file.Paths;
3333
import java.util.ArrayList;
3434
import java.util.Arrays;
35+
import java.util.HashSet;
3536
import java.util.Iterator;
3637
import java.util.List;
3738
import java.util.Locale;
39+
import java.util.Set;
3840

3941
import org.apache.maven.model.Resource;
4042
import org.codehaus.plexus.util.DirectoryScanner;
@@ -144,6 +146,13 @@ public void filterResources(MavenResourcesExecution mavenResourcesExecution) thr
144146
boolean isFilteringUsed = false;
145147
List<File> propertiesFiles = new ArrayList<>();
146148

149+
// Destinations already written by a previous <resource> entry in this call.
150+
// Used to preserve "first <resource> entry wins" semantics for overlapping
151+
// declarations — the behaviour 3.3.1 produced as a side effect of the
152+
// timestamp-based overwrite gate in FilteringUtils.copyFile, lost in
153+
// MSHARED-1216 / GH-333.
154+
Set<Path> alreadyCopied = new HashSet<>();
155+
147156
for (Resource resource : mavenResourcesExecution.getResources()) {
148157

149158
if (LOGGER.isDebugEnabled()) {
@@ -281,6 +290,20 @@ private File getTargetFile(File file) throws MavenFilteringException {
281290

282291
File destinationFile = getDestinationFile(outputDirectory, targetPath, name, mavenResourcesExecution);
283292

293+
// Track destinations already written by a previous <resource> entry so we
294+
// can preserve "first <resource> entry wins" semantics for overlapping
295+
// declarations. The flatten case has its own collision policy below
296+
// (warn-or-throw based on overwrite), so we deliberately do not suppress
297+
// it here.
298+
Path destinationPath = destinationFile.getAbsoluteFile().toPath();
299+
boolean firstOccurrence = alreadyCopied.add(destinationPath);
300+
if (!firstOccurrence
301+
&& !mavenResourcesExecution.isFlatten()
302+
&& mavenResourcesExecution.getChangeDetection() != ChangeDetection.ALWAYS) {
303+
LOGGER.debug("skipping {} — destination already written by a previous <resource> entry", name);
304+
continue;
305+
}
306+
284307
if (mavenResourcesExecution.isFlatten() && destinationFile.exists()) {
285308
if (mavenResourcesExecution.getChangeDetection() == ChangeDetection.ALWAYS) {
286309
LOGGER.warn("existing file " + destinationFile.getName() + " will be overwritten by " + name);

‎src/test/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFilteringTest.java‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,4 +1112,50 @@ void isPropertiesFile() {
11121112
assertFalse(DefaultMavenResourcesFiltering.isPropertiesFile(new File("file.xml")));
11131113
assertFalse(DefaultMavenResourcesFiltering.isPropertiesFile(new File("some/parent/path", "file.xml")));
11141114
}
1115+
1116+
/**
1117+
* Regression guard for the maven-resources-plugin issue 471 / GH-333 scenario: when the same
1118+
* destination is reached by a later <resource> entry while overwrite is explicitly enabled,
1119+
* the later entry must still be allowed to replace the earlier one — the "first entry wins"
1120+
* skip introduced for the default case must not apply here.
1121+
*/
1122+
@Test
1123+
void overlappingEntriesWithExplicitOverwriteStillReplace() throws Exception {
1124+
Properties projectProperties = new Properties();
1125+
projectProperties.put("repro.value", "REPLACED_BY_FILTERING");
1126+
mavenProject.setProperties(projectProperties);
1127+
1128+
String unitFilesDir = getBasedir() + "/src/test/units-files/MRP-471";
1129+
1130+
Resource filtered = new Resource();
1131+
filtered.setDirectory(unitFilesDir);
1132+
filtered.setFiltering(true);
1133+
filtered.addInclude("config/filtered.xml");
1134+
1135+
Resource unfiltered = new Resource();
1136+
unfiltered.setDirectory(unitFilesDir);
1137+
unfiltered.setFiltering(false);
1138+
unfiltered.addInclude("**");
1139+
1140+
MavenResourcesExecution execution = new MavenResourcesExecution(
1141+
Arrays.asList(filtered, unfiltered),
1142+
outputDirectory,
1143+
mavenProject,
1144+
"UTF-8",
1145+
Collections.emptyList(),
1146+
Collections.emptyList(),
1147+
new StubMavenSession());
1148+
execution.setUseDefaultFilterWrappers(true);
1149+
execution.setOverwrite(true);
1150+
1151+
mavenResourcesFiltering.filterResources(execution);
1152+
1153+
File filteredOut = new File(outputDirectory, "config/filtered.xml");
1154+
String content = new String(java.nio.file.Files.readAllBytes(filteredOut.toPath()), "UTF-8");
1155+
// With overwrite=true the unfiltered second pass still wins — preserve the historical
1156+
// 3.3.1 behaviour for callers that opt in to ChangeDetection.ALWAYS.
1157+
assertTrue(
1158+
content.contains("${repro.value}"),
1159+
"with overwrite=true the second <resource> entry must still clobber; got:\n" + content);
1160+
}
11151161
}

‎src/test/java/org/apache/maven/shared/filtering/OverlappingResourcesFilteringTest.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ void overlappingResourceEntriesShouldStillFilter() throws Exception {
142142
// this guards against a fix that just turns filtering on for everything.
143143
assertTrue(
144144
unfilteredContent.contains("${repro.value}"),
145-
"static/keep-as-is.txt is matched by filtering=false and must keep the literal "
146-
+ "placeholder; got:\n" + unfilteredContent);
145+
"static/keep-as-is.txt is matched by filtering=false and must keep the literal " + "placeholder; got:\n"
146+
+ unfilteredContent);
147147
}
148148
}

0 commit comments

Comments
 (0)