Skip to content

Commit 3ce1aa3

Browse files
committed
ChangeDependencyGroupIdAndArtifactId updates version properties in parent POMs
Convert `ChangeDependencyGroupIdAndArtifactId` from `Recipe` to `ScanningRecipe` so that version properties defined in parent POMs are updated when the dependency referencing that property is in a child POM. The scanner phase identifies version properties inherited from parent POMs and records which source file defines each property. The visitor phase then applies property updates when visiting those parent POM files. All existing visitor logic is preserved unchanged, avoiding the recipe-chaining issues that caused the previous attempt (#5815) to be reverted. Fixes moderneinc/customer-requests#1374
1 parent 8cf8bcf commit 3ce1aa3

2 files changed

Lines changed: 213 additions & 2 deletions

File tree

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

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.openrewrite.xml.RemoveContentVisitor;
3030
import org.openrewrite.xml.tree.Xml;
3131

32+
import java.nio.file.Path;
3233
import java.util.*;
3334

3435
import static java.util.Collections.max;
@@ -41,7 +42,8 @@
4142

4243
@Value
4344
@EqualsAndHashCode(callSuper = false)
44-
public class ChangeDependencyGroupIdAndArtifactId extends Recipe {
45+
public class ChangeDependencyGroupIdAndArtifactId extends ScanningRecipe<ChangeDependencyGroupIdAndArtifactId.Accumulator> {
46+
@EqualsAndHashCode.Exclude
4547
transient MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this);
4648

4749
@Option(displayName = "Old groupId",
@@ -142,7 +144,93 @@ public Validated<Object> validate() {
142144
}
143145

144146
@Override
145-
public TreeVisitor<?, ExecutionContext> getVisitor() {
147+
public Accumulator getInitialValue(ExecutionContext ctx) {
148+
return new Accumulator();
149+
}
150+
151+
@Override
152+
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
153+
return new MavenIsoVisitor<ExecutionContext>() {
154+
final @Nullable VersionComparator versionComparator = newVersion != null ? Semver.validate(newVersion, versionPattern).getValue() : null;
155+
final boolean configuredToChangeManagedDependency = changeManagedDependency == null || changeManagedDependency;
156+
157+
@Override
158+
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
159+
if (isDependencyTag(oldGroupId, oldArtifactId) || isPluginDependencyTag(oldGroupId, oldArtifactId) || isAnnotationProcessorPathTag(oldGroupId, oldArtifactId)) {
160+
if (newVersion != null && versionComparator != null) {
161+
String currentVersion = tag.getChildValue("version").orElse(null);
162+
if (currentVersion != null && isProperty(currentVersion)) {
163+
String propertyName = currentVersion.substring(2, currentVersion.length() - 1);
164+
if (!getResolutionResult().getPom().getRequested().getProperties().containsKey(propertyName)) {
165+
// Property is inherited from a parent POM; check if it is safe to change
166+
Set<String> safeProperties = getSafeVersionPlaceholdersToChange(oldGroupId, oldArtifactId, ctx);
167+
if (safeProperties.contains(currentVersion)) {
168+
try {
169+
String groupId = Optional.ofNullable(newGroupId).orElse(tag.getChildValue("groupId").orElse(oldGroupId));
170+
String artifactId = Optional.ofNullable(newArtifactId).orElse(tag.getChildValue("artifactId").orElse(oldArtifactId));
171+
String resolvedVersion = resolveSemverVersion(ctx, groupId, artifactId, currentVersion);
172+
storeParentPomProperty(getResolutionResult().getParent(), propertyName, resolvedVersion, acc);
173+
} catch (MavenDownloadingException e) {
174+
return e.warn(tag);
175+
}
176+
}
177+
}
178+
}
179+
}
180+
}
181+
return super.visitTag(tag, ctx);
182+
}
183+
184+
private Set<String> getSafeVersionPlaceholdersToChange(String groupId, String artifactId, ExecutionContext ctx) {
185+
MavenResolutionResult result = getResolutionResult();
186+
ResolvedPom resolvedPom = result.getPom();
187+
Pom requestedPom = resolvedPom.getRequested();
188+
Set<String> relevantProperties = requestedPom.getDependencies().stream()
189+
.filter(d -> isProperty(d.getVersion()) &&
190+
matchesGlob(resolvedPom.getValue(d.getGroupId()), groupId) &&
191+
matchesGlob(resolvedPom.getValue(d.getArtifactId()), artifactId))
192+
.map(Dependency::getVersion)
193+
.collect(toSet());
194+
relevantProperties = filterPropertiesWithOverlapInDependencies(relevantProperties, groupId, artifactId, requestedPom, resolvedPom, configuredToChangeManagedDependency);
195+
relevantProperties = filterPropertiesWithOverlapInChildren(relevantProperties, groupId, artifactId, result, configuredToChangeManagedDependency);
196+
return filterPropertiesWithOverlapInParents(relevantProperties, groupId, artifactId, result, configuredToChangeManagedDependency, ctx);
197+
}
198+
199+
@SuppressWarnings("ConstantConditions")
200+
private String resolveSemverVersion(ExecutionContext ctx, String groupId, String artifactId, @Nullable String currentVersion) throws MavenDownloadingException {
201+
if (versionComparator == null) {
202+
return newVersion;
203+
}
204+
String finalCurrentVersion = currentVersion != null ? currentVersion : newVersion;
205+
List<String> availableVersions = new ArrayList<>();
206+
MavenMetadata mavenMetadata = metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, ctx));
207+
for (String v : mavenMetadata.getVersioning().getVersions()) {
208+
if (versionComparator.isValid(finalCurrentVersion, v)) {
209+
availableVersions.add(v);
210+
}
211+
}
212+
return availableVersions.isEmpty() ? newVersion : max(availableVersions, versionComparator);
213+
}
214+
215+
private void storeParentPomProperty(@Nullable MavenResolutionResult parent, String propertyName, String newValue, Accumulator acc) {
216+
if (parent == null) {
217+
return;
218+
}
219+
Pom pom = parent.getPom().getRequested();
220+
if (pom.getSourcePath() == null) {
221+
return;
222+
}
223+
if (pom.getProperties().containsKey(propertyName)) {
224+
acc.getPomProperties().add(new PomProperty(pom.getSourcePath(), propertyName, newValue));
225+
return;
226+
}
227+
storeParentPomProperty(parent.getParent(), propertyName, newValue, acc);
228+
}
229+
};
230+
}
231+
232+
@Override
233+
public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
146234
return new MavenVisitor<ExecutionContext>() {
147235
@Nullable
148236
final VersionComparator versionComparator = newVersion != null ? Semver.validate(newVersion, versionPattern).getValue() : null;
@@ -176,6 +264,22 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) {
176264
@Override
177265
public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
178266
Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx);
267+
268+
// Update version properties in parent POMs based on scanner results
269+
if (isPropertyTag()) {
270+
Path pomSourcePath = getResolutionResult().getPom().getRequested().getSourcePath();
271+
for (PomProperty prop : acc.getPomProperties()) {
272+
if (prop.getPomFilePath().equals(pomSourcePath) && prop.getPropertyName().equals(tag.getName())) {
273+
Optional<String> value = tag.getValue();
274+
if (!value.isPresent() || !value.get().equals(prop.getNewValue())) {
275+
doAfterVisit(new ChangeTagValueVisitor<>(tag, prop.getNewValue()));
276+
maybeUpdateModel();
277+
}
278+
break;
279+
}
280+
}
281+
}
282+
179283
boolean isOldDependencyTag = isDependencyTag(oldGroupId, oldArtifactId);
180284
if (isOldDependencyTag && isNewDependencyPresent) {
181285
doAfterVisit(new RemoveContentVisitor<>(tag, true, true));
@@ -321,4 +425,16 @@ private String resolveSemverVersion(ExecutionContext ctx, String groupId, String
321425
}
322426
};
323427
}
428+
429+
@Value
430+
public static class Accumulator {
431+
Set<PomProperty> pomProperties = new HashSet<>();
432+
}
433+
434+
@Value
435+
public static class PomProperty {
436+
Path pomFilePath;
437+
String propertyName;
438+
String newValue;
439+
}
324440
}

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2320,6 +2320,101 @@ void changeVersionPropertyInParentPom() {
23202320
);
23212321
}
23222322

2323+
@Issue("https://github.com/moderneinc/customer-requests/issues/1374")
2324+
@Test
2325+
void changeVersionPropertyInParentPomSimple() {
2326+
rewriteRun(
2327+
spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId(
2328+
"io.swagger",
2329+
"swagger-annotations",
2330+
"io.swagger.core.v3",
2331+
null,
2332+
"2.2.x",
2333+
null,
2334+
null,
2335+
null
2336+
)),
2337+
mavenProject("project",
2338+
//language=xml
2339+
pomXml(
2340+
"""
2341+
<project>
2342+
<groupId>com.mycompany.app</groupId>
2343+
<artifactId>parent-project</artifactId>
2344+
<version>1</version>
2345+
<properties>
2346+
<version.swagger>1.5.16</version.swagger>
2347+
</properties>
2348+
<modules>
2349+
<module>sub-project</module>
2350+
</modules>
2351+
</project>
2352+
""",
2353+
"""
2354+
<project>
2355+
<groupId>com.mycompany.app</groupId>
2356+
<artifactId>parent-project</artifactId>
2357+
<version>1</version>
2358+
<properties>
2359+
<version.swagger>2.2.42</version.swagger>
2360+
</properties>
2361+
<modules>
2362+
<module>sub-project</module>
2363+
</modules>
2364+
</project>
2365+
""",
2366+
spec -> spec.path("pom.xml")
2367+
),
2368+
mavenProject("sub-project",
2369+
//language=xml
2370+
pomXml(
2371+
"""
2372+
<project>
2373+
<groupId>com.mycompany.app</groupId>
2374+
<artifactId>sub-project</artifactId>
2375+
<version>1</version>
2376+
<parent>
2377+
<groupId>com.mycompany.app</groupId>
2378+
<artifactId>parent-project</artifactId>
2379+
<version>1</version>
2380+
<relativePath>../pom.xml</relativePath>
2381+
</parent>
2382+
<dependencies>
2383+
<dependency>
2384+
<groupId>io.swagger</groupId>
2385+
<artifactId>swagger-annotations</artifactId>
2386+
<version>${version.swagger}</version>
2387+
</dependency>
2388+
</dependencies>
2389+
</project>
2390+
""",
2391+
"""
2392+
<project>
2393+
<groupId>com.mycompany.app</groupId>
2394+
<artifactId>sub-project</artifactId>
2395+
<version>1</version>
2396+
<parent>
2397+
<groupId>com.mycompany.app</groupId>
2398+
<artifactId>parent-project</artifactId>
2399+
<version>1</version>
2400+
<relativePath>../pom.xml</relativePath>
2401+
</parent>
2402+
<dependencies>
2403+
<dependency>
2404+
<groupId>io.swagger.core.v3</groupId>
2405+
<artifactId>swagger-annotations</artifactId>
2406+
<version>${version.swagger}</version>
2407+
</dependency>
2408+
</dependencies>
2409+
</project>
2410+
""",
2411+
spec -> spec.path("sub-project/pom.xml")
2412+
)
2413+
)
2414+
)
2415+
);
2416+
}
2417+
23232418
@Issue("https://github.com/openrewrite/rewrite/issues/5965")
23242419
@Test
23252420
void changeDependencyGroupIdAndArtifactIdForEjbType() {

0 commit comments

Comments
 (0)