Skip to content

Commit 2f40b48

Browse files
Propagate updated dependency management model to child modules
When ChangeManagedDependencyGroupIdAndArtifactId changes a parent POM's managed dependency coordinates, child modules that inherit from it need their resolved model updated even though their XML doesn't change. UpdateMavenModel now stores resolved module results in the execution context via UPDATED_MODULES_KEY. ChangeManagedDependencyGroupIdAndArtifactId picks these up in visitDocument and applies them to child modules before visiting their tags. When a child module's dependency resolution fails during the parent's model update (e.g., the child has an unversioned dependency on the old coordinates that no longer have a managed version), the child's resolved POM model is stored before rethrowing so the model updates are still available for pickup. Fixes moderneinc/customer-requests#1522
1 parent 5a79346 commit 2f40b48

File tree

4 files changed

+288
-9
lines changed

4 files changed

+288
-9
lines changed

rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.openrewrite.xml.RemoveContentVisitor;
2929
import org.openrewrite.xml.tree.Xml;
3030

31+
import java.nio.file.Path;
3132
import java.util.*;
3233

3334
import static java.util.Collections.max;
@@ -132,8 +133,29 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
132133
private Collection<String> availableVersions;
133134
private Set<String> safeVersionPlaceholdersToChange = new HashSet<>();
134135

136+
private static final String MANAGED_DEP_CHANGED_KEY = "org.openrewrite.maven.ChangeManagedDependencyGroupIdAndArtifactId.changed";
137+
135138
@Override
139+
@SuppressWarnings("unchecked")
136140
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
141+
// Apply pending model updates from parent POM processing.
142+
// When a parent POM's managed dependency is changed, UpdateMavenModel re-resolves
143+
// the parent and its modules, storing updated module results in the execution context.
144+
// Child modules that don't have XML changes still need their resolved model updated.
145+
// Only apply if this recipe actually changed a managed dependency (tracked via MANAGED_DEP_CHANGED_KEY).
146+
Set<Path> changedPoms = ctx.getMessage(MANAGED_DEP_CHANGED_KEY);
147+
if (changedPoms != null && !changedPoms.isEmpty()) {
148+
Map<Path, MavenResolutionResult> updatedModules = ctx.getMessage(UpdateMavenModel.UPDATED_MODULES_KEY);
149+
if (updatedModules != null) {
150+
MavenResolutionResult pending = updatedModules.remove(document.getSourcePath());
151+
if (pending != null) {
152+
MavenResolutionResult current = document.getMarkers().findFirst(MavenResolutionResult.class).orElse(null);
153+
if (current != null) {
154+
document = document.withMarkers(document.getMarkers().computeByType(current, (original, ignored) -> pending));
155+
}
156+
}
157+
}
158+
}
137159
safeVersionPlaceholdersToChange = getSafeVersionPlaceholdersToChange(oldGroupId, oldArtifactId, ctx);
138160
return super.visitDocument(document, ctx);
139161
}
@@ -181,6 +203,7 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
181203
}
182204
}
183205
if (t != tag) {
206+
ctx.putMessageInSet(MANAGED_DEP_CHANGED_KEY, getResolutionResult().getPom().getRequested().getSourcePath());
184207
maybeUpdateModel();
185208
doAfterVisit(new RemoveRedundantDependencyVersions(null, null, null, null).getVisitor());
186209
String effectiveGroupId = newGroupId != null ? newGroupId : tag.getChildValue("groupId").orElse(null);

rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenModel.java

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,14 @@
2323
import org.openrewrite.xml.tree.Xml;
2424

2525
import java.nio.file.Path;
26-
import java.util.ArrayList;
27-
import java.util.List;
28-
import java.util.Map;
29-
import java.util.Optional;
26+
import java.util.*;
3027
import java.util.concurrent.atomic.AtomicReference;
3128

3229
import static java.util.Collections.emptyList;
3330
import static java.util.stream.Collectors.toList;
3431

3532
public class UpdateMavenModel<P> extends MavenVisitor<P> {
33+
static final String UPDATED_MODULES_KEY = "org.openrewrite.maven.UpdateMavenModel.updatedModules";
3634

3735
@Override
3836
public Xml visitDocument(Xml.Document document, P p) {
@@ -145,14 +143,31 @@ public Xml visitDocument(Xml.Document document, P p) {
145143
projectPoms.put(sourcePath, requested);
146144
}
147145
MavenResolutionResult updated = updateResult(ctx, resolutionResult.withPom(resolutionResult.getPom().withRequested(requested)),
148-
projectPoms);
146+
projectPoms, false);
147+
storeModuleResults(ctx, updated);
149148
return document.withMarkers(document.getMarkers().computeByType(getResolutionResult(),
150149
(original, ignored) -> updated));
151150
} catch (MavenDownloadingExceptions e) {
152151
return e.warn(document);
153152
}
154153
}
155154

155+
@SuppressWarnings("unchecked")
156+
private static void storeModuleResults(ExecutionContext ctx, MavenResolutionResult result) {
157+
Map<Path, MavenResolutionResult> map = ctx.getMessage(UPDATED_MODULES_KEY);
158+
if (map == null) {
159+
map = new HashMap<>();
160+
ctx.putMessage(UPDATED_MODULES_KEY, map);
161+
}
162+
for (MavenResolutionResult module : result.getModules()) {
163+
Path modulePath = module.getPom().getRequested().getSourcePath();
164+
if (modulePath != null) {
165+
map.put(modulePath, module);
166+
}
167+
storeModuleResults(ctx, module);
168+
}
169+
}
170+
156171
private @Nullable List<GroupArtifact> mapExclusions(Xml.Tag tag) {
157172
return tag.getChild("exclusions")
158173
.map(exclusions -> {
@@ -169,7 +184,8 @@ public Xml visitDocument(Xml.Document document, P p) {
169184
.orElse(null);
170185
}
171186

172-
private MavenResolutionResult updateResult(ExecutionContext ctx, MavenResolutionResult resolutionResult, Map<Path, Pom> projectPoms) throws MavenDownloadingExceptions {
187+
private MavenResolutionResult updateResult(ExecutionContext ctx, MavenResolutionResult resolutionResult,
188+
Map<Path, Pom> projectPoms, boolean isChildModule) throws MavenDownloadingExceptions {
173189
MavenPomDownloader downloader = new MavenPomDownloader(projectPoms, ctx, getResolutionResult().getMavenSettings(),
174190
getResolutionResult().getActiveProfiles());
175191

@@ -180,13 +196,24 @@ private MavenResolutionResult updateResult(ExecutionContext ctx, MavenResolution
180196
.withPom(resolved)
181197
.withModules(ListUtils.map(resolutionResult.getModules(), module -> {
182198
try {
183-
return updateResult(ctx, module, projectPoms);
199+
return updateResult(ctx, module, projectPoms, true);
184200
} catch (MavenDownloadingExceptions e) {
185201
exceptions.set(MavenDownloadingExceptions.append(exceptions.get(), e));
186202
return module;
187203
}
188-
}))
189-
.resolveDependencies(downloader, ctx);
204+
}));
205+
try {
206+
mrr = mrr.resolveDependencies(downloader, ctx);
207+
} catch (MavenDownloadingExceptions e) {
208+
if (isChildModule) {
209+
// Store the resolved POM model before rethrowing so that this
210+
// child module's model updates (e.g., updated dependency management
211+
// inherited from the parent) can be picked up when the recipe
212+
// visits this child's document
213+
storeSelfResult(ctx, mrr);
214+
}
215+
throw MavenDownloadingExceptions.append(exceptions.get(), e);
216+
}
190217
if (exceptions.get() != null) {
191218
throw exceptions.get();
192219
}
@@ -197,4 +224,17 @@ private MavenResolutionResult updateResult(ExecutionContext ctx, MavenResolution
197224
throw MavenDownloadingExceptions.append(exceptions.get(), e);
198225
}
199226
}
227+
228+
private static void storeSelfResult(ExecutionContext ctx, MavenResolutionResult result) {
229+
Path path = result.getPom().getRequested().getSourcePath();
230+
if (path != null) {
231+
@SuppressWarnings("unchecked")
232+
Map<Path, MavenResolutionResult> map = ctx.getMessage(UPDATED_MODULES_KEY);
233+
if (map == null) {
234+
map = new HashMap<>();
235+
ctx.putMessage(UPDATED_MODULES_KEY, map);
236+
}
237+
map.put(path, result);
238+
}
239+
}
200240
}

rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3539,4 +3539,103 @@ void shouldNotChangeDependencyWithImplicitlyDefinedVersionProperty() {
35393539
);
35403540
}
35413541

3542+
@Issue("https://github.com/moderneinc/customer-requests/issues/1522")
3543+
@Test
3544+
void childModuleWithUnversionedDependencyOnParentManagedDep() {
3545+
rewriteRun(
3546+
spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId(
3547+
"javax.activation", "javax.activation-api",
3548+
"jakarta.activation", "jakarta.activation-api",
3549+
"2.1.0", null
3550+
)),
3551+
mavenProject("parent-project",
3552+
pomXml(
3553+
"""
3554+
<project>
3555+
<modelVersion>4.0.0</modelVersion>
3556+
<groupId>com.mycompany.app</groupId>
3557+
<artifactId>parent-project</artifactId>
3558+
<version>1.0.0</version>
3559+
<packaging>pom</packaging>
3560+
<modules>
3561+
<module>child-module</module>
3562+
</modules>
3563+
<dependencyManagement>
3564+
<dependencies>
3565+
<dependency>
3566+
<groupId>javax.activation</groupId>
3567+
<artifactId>javax.activation-api</artifactId>
3568+
<version>1.2.0</version>
3569+
</dependency>
3570+
</dependencies>
3571+
</dependencyManagement>
3572+
</project>
3573+
""",
3574+
"""
3575+
<project>
3576+
<modelVersion>4.0.0</modelVersion>
3577+
<groupId>com.mycompany.app</groupId>
3578+
<artifactId>parent-project</artifactId>
3579+
<version>1.0.0</version>
3580+
<packaging>pom</packaging>
3581+
<modules>
3582+
<module>child-module</module>
3583+
</modules>
3584+
<dependencyManagement>
3585+
<dependencies>
3586+
<dependency>
3587+
<groupId>jakarta.activation</groupId>
3588+
<artifactId>jakarta.activation-api</artifactId>
3589+
<version>2.1.0</version>
3590+
</dependency>
3591+
</dependencies>
3592+
</dependencyManagement>
3593+
</project>
3594+
"""
3595+
),
3596+
mavenProject("child-module",
3597+
pomXml(
3598+
"""
3599+
<project>
3600+
<modelVersion>4.0.0</modelVersion>
3601+
<parent>
3602+
<groupId>com.mycompany.app</groupId>
3603+
<artifactId>parent-project</artifactId>
3604+
<version>1.0.0</version>
3605+
<relativePath>../pom.xml</relativePath>
3606+
</parent>
3607+
<artifactId>child-module</artifactId>
3608+
<dependencies>
3609+
<dependency>
3610+
<groupId>javax.activation</groupId>
3611+
<artifactId>javax.activation-api</artifactId>
3612+
</dependency>
3613+
</dependencies>
3614+
</project>
3615+
""",
3616+
"""
3617+
<project>
3618+
<modelVersion>4.0.0</modelVersion>
3619+
<parent>
3620+
<groupId>com.mycompany.app</groupId>
3621+
<artifactId>parent-project</artifactId>
3622+
<version>1.0.0</version>
3623+
<relativePath>../pom.xml</relativePath>
3624+
</parent>
3625+
<artifactId>child-module</artifactId>
3626+
<dependencies>
3627+
<dependency>
3628+
<groupId>jakarta.activation</groupId>
3629+
<artifactId>jakarta.activation-api</artifactId>
3630+
</dependency>
3631+
</dependencies>
3632+
</project>
3633+
""",
3634+
spec -> spec.path("child-module/pom.xml")
3635+
)
3636+
)
3637+
)
3638+
);
3639+
}
3640+
35423641
}

rewrite-maven/src/test/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactIdTest.java

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import org.openrewrite.DocumentExample;
2020
import org.openrewrite.Issue;
2121
import org.openrewrite.Validated;
22+
import org.openrewrite.maven.tree.MavenResolutionResult;
2223
import org.openrewrite.test.RewriteTest;
2324

2425
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.openrewrite.java.Assertions.mavenProject;
2527
import static org.openrewrite.maven.Assertions.pomXml;
2628

2729
class ChangeManagedDependencyGroupIdAndArtifactIdTest implements RewriteTest {
@@ -679,6 +681,121 @@ void globArtifactIdRetainedWhenNewArtifactIdIsNull() {
679681
);
680682
}
681683

684+
@Issue("https://github.com/moderneinc/customer-requests/issues/1522")
685+
@Test
686+
void childModuleSeesUpdatedManagedDependencyFromParent() {
687+
rewriteRun(
688+
spec -> spec.recipe(new ChangeManagedDependencyGroupIdAndArtifactId(
689+
"javax.activation",
690+
"javax.activation-api",
691+
"jakarta.activation",
692+
"jakarta.activation-api",
693+
"2.1.0"
694+
)),
695+
mavenProject(
696+
"parent-project",
697+
pomXml(
698+
"""
699+
<project>
700+
<modelVersion>4.0.0</modelVersion>
701+
<groupId>com.mycompany.app</groupId>
702+
<artifactId>parent-project</artifactId>
703+
<version>1.0.0</version>
704+
<packaging>pom</packaging>
705+
<modules>
706+
<module>child-module</module>
707+
</modules>
708+
<dependencyManagement>
709+
<dependencies>
710+
<dependency>
711+
<groupId>javax.activation</groupId>
712+
<artifactId>javax.activation-api</artifactId>
713+
<version>1.2.0</version>
714+
</dependency>
715+
</dependencies>
716+
</dependencyManagement>
717+
</project>
718+
""",
719+
"""
720+
<project>
721+
<modelVersion>4.0.0</modelVersion>
722+
<groupId>com.mycompany.app</groupId>
723+
<artifactId>parent-project</artifactId>
724+
<version>1.0.0</version>
725+
<packaging>pom</packaging>
726+
<modules>
727+
<module>child-module</module>
728+
</modules>
729+
<dependencyManagement>
730+
<dependencies>
731+
<dependency>
732+
<groupId>jakarta.activation</groupId>
733+
<artifactId>jakarta.activation-api</artifactId>
734+
<version>2.1.0</version>
735+
</dependency>
736+
</dependencies>
737+
</dependencyManagement>
738+
</project>
739+
"""
740+
),
741+
mavenProject(
742+
"child-module",
743+
pomXml(
744+
"""
745+
<project>
746+
<modelVersion>4.0.0</modelVersion>
747+
<parent>
748+
<groupId>com.mycompany.app</groupId>
749+
<artifactId>parent-project</artifactId>
750+
<version>1.0.0</version>
751+
<relativePath>../pom.xml</relativePath>
752+
</parent>
753+
<artifactId>child-module</artifactId>
754+
<dependencies>
755+
<dependency>
756+
<groupId>javax.activation</groupId>
757+
<artifactId>javax.activation-api</artifactId>
758+
</dependency>
759+
</dependencies>
760+
</project>
761+
""",
762+
"""
763+
<project>
764+
<modelVersion>4.0.0</modelVersion>
765+
<parent>
766+
<groupId>com.mycompany.app</groupId>
767+
<artifactId>parent-project</artifactId>
768+
<version>1.0.0</version>
769+
<relativePath>../pom.xml</relativePath>
770+
</parent>
771+
<artifactId>child-module</artifactId>
772+
<dependencies>
773+
<dependency>
774+
<groupId>javax.activation</groupId>
775+
<artifactId>javax.activation-api</artifactId>
776+
</dependency>
777+
</dependencies>
778+
</project>
779+
""",
780+
spec -> spec.path("child-module/pom.xml").afterRecipe(doc -> {
781+
MavenResolutionResult result = doc.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow();
782+
// The child's resolved dependency management should reflect the parent's change
783+
assertThat(result.getPom().getDependencyManagement())
784+
.describedAs("Child module should see jakarta.activation:jakarta.activation-api:2.1.0 from parent")
785+
.anyMatch(dep -> "jakarta.activation".equals(dep.getGroupId())
786+
&& "jakarta.activation-api".equals(dep.getArtifactId())
787+
&& "2.1.0".equals(dep.getVersion()));
788+
assertThat(result.getPom().getDependencyManagement())
789+
.describedAs("Child module should no longer see javax.activation:javax.activation-api")
790+
.noneMatch(dep -> "javax.activation".equals(dep.getGroupId())
791+
&& "javax.activation-api".equals(dep.getArtifactId()));
792+
})
793+
)
794+
)
795+
)
796+
);
797+
}
798+
682799
@Test
683800
void shouldNotChangeManagedDependencyWithImplicitlyDefinedVersionProperty() {
684801
rewriteRun(

0 commit comments

Comments
 (0)