|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 Oracle and/or its affiliates. |
| 3 | + * |
| 4 | + * Licensed under the Universal Permissive License v 1.0 as shown at |
| 5 | + * https://oss.oracle.com/licenses/upl. |
| 6 | + * |
| 7 | + */ |
| 8 | +package com.oracle.weblogic.rewrite.jakarta; |
| 9 | + |
| 10 | +import static org.openrewrite.internal.StringUtils.matchesGlob; |
| 11 | + |
| 12 | +import java.util.ArrayList; |
| 13 | +import java.util.Collection; |
| 14 | +import java.util.Collections; |
| 15 | +import java.util.Optional; |
| 16 | + |
| 17 | +import org.jspecify.annotations.Nullable; |
| 18 | + |
| 19 | +import org.openrewrite.ExecutionContext; |
| 20 | +import org.openrewrite.maven.MavenDownloadingException; |
| 21 | +import org.openrewrite.maven.MavenIsoVisitor; |
| 22 | +import org.openrewrite.Option; |
| 23 | +import org.openrewrite.Recipe; |
| 24 | +import org.openrewrite.TreeVisitor; |
| 25 | + |
| 26 | +import org.openrewrite.maven.table.MavenMetadataFailures; |
| 27 | +import org.openrewrite.maven.tree.MavenMetadata; |
| 28 | +import org.openrewrite.semver.Semver; |
| 29 | +import org.openrewrite.semver.VersionComparator; |
| 30 | +import org.openrewrite.xml.XPathMatcher; |
| 31 | +import org.openrewrite.xml.tree.Xml; |
| 32 | + |
| 33 | +import lombok.EqualsAndHashCode; |
| 34 | +import lombok.Value; |
| 35 | + |
| 36 | +/** |
| 37 | + * UpgradeMavenPluginArtifactItems is an imperative recipe to upgrade the groupId, artifactId and version of an artifactIem, |
| 38 | + * of a Maven plugin. |
| 39 | + */ |
| 40 | + |
| 41 | +@Value |
| 42 | +@EqualsAndHashCode(callSuper = false) |
| 43 | +public class UpgradeMavenPluginArtifactItems extends Recipe { |
| 44 | + |
| 45 | + @EqualsAndHashCode.Exclude |
| 46 | + transient MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); |
| 47 | + |
| 48 | + @Option(displayName = "Old group ID", |
| 49 | + description = "The old group ID to replace.", |
| 50 | + example = "javax") |
| 51 | + String oldGroupId; |
| 52 | + |
| 53 | + @Option(displayName = "Old artifact ID", |
| 54 | + description = "The old artifact ID to replace.", |
| 55 | + example = "javax") |
| 56 | + String oldArtifactId; |
| 57 | + |
| 58 | + @Option(displayName = "New group ID", |
| 59 | + description = "The new group ID to use.", |
| 60 | + example = "jakarta.platform") |
| 61 | + String newGroupId; |
| 62 | + |
| 63 | + @Option(displayName = "New artifact ID", |
| 64 | + description = "The new artifact ID to use.", |
| 65 | + example = "javaee-api") |
| 66 | + String newArtifactId; |
| 67 | + |
| 68 | + @Option(displayName = "New version", |
| 69 | + description = "An exact version number.", |
| 70 | + example = "9.1", |
| 71 | + required = false) |
| 72 | + @Nullable |
| 73 | + String newVersion; |
| 74 | + |
| 75 | + private static final XPathMatcher PLUGIN_ARTIFACT_ITEM_MATCHER = new XPathMatcher("/project/build/plugins/plugin/executions/execution/configuration/artifactItems/artifactItem"); |
| 76 | + |
| 77 | + private static final String GROUP_ID = "groupId"; |
| 78 | + private static final String ARTIFACT_ID = "artifactId"; |
| 79 | + private static final String ARTIFACT_VERSION = "version"; |
| 80 | + |
| 81 | + @Override |
| 82 | + public String getDescription() { |
| 83 | + return "Change the groupId and the artifactId of an artifactItem in the configuration section of a plugin's execution. " + |
| 84 | + "This recipe does not perform any validation and assumes all values passed are valid."; |
| 85 | + } |
| 86 | + |
| 87 | + @Override |
| 88 | + public String getDisplayName() { |
| 89 | + return "Upgrade group, artifact ID and version of an artifactItem, of a maven plugin execution configuration"; |
| 90 | + } |
| 91 | + |
| 92 | + @Override |
| 93 | + public TreeVisitor<?, ExecutionContext> getVisitor() { |
| 94 | + return new MavenIsoVisitor<ExecutionContext>() { |
| 95 | + @Nullable |
| 96 | + final VersionComparator versionComparator = newVersion != null ? Semver.validate(newVersion, null).getValue() : null; |
| 97 | + @Nullable |
| 98 | + private Collection<String> availableVersions; |
| 99 | + |
| 100 | + @Override |
| 101 | + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { |
| 102 | + Xml.Tag t = super.visitTag(tag, ctx); |
| 103 | + if (PLUGIN_ARTIFACT_ITEM_MATCHER.matches(getCursor())) { |
| 104 | + // Find out whether oldGroupId really exists, consider the value defined as a project property as well |
| 105 | + boolean isGroupIdFound = tag.getChildValue(GROUP_ID) |
| 106 | + .map(a -> matchesGlob(a, oldGroupId)) |
| 107 | + .orElse(oldGroupId == null); |
| 108 | + isGroupIdFound = isElementFound(tag, isGroupIdFound, GROUP_ID, oldGroupId); |
| 109 | + |
| 110 | + // Find out whether oldArtifactId really exists, consider the value defined as a project property as well |
| 111 | + boolean isArtifactIdFound = tag.getChildValue(ARTIFACT_ID) |
| 112 | + .map(a -> matchesGlob(a, oldArtifactId)) |
| 113 | + .orElse(oldArtifactId == null); |
| 114 | + isArtifactIdFound = isElementFound(tag, isArtifactIdFound, ARTIFACT_ID, oldArtifactId); |
| 115 | + |
| 116 | + // Change the child tag value only when oldGroupId and oldArtifactId is found |
| 117 | + if (isGroupIdFound && isArtifactIdFound) { |
| 118 | + if (newGroupId != null) { |
| 119 | + t = changeChildTagValue(t, GROUP_ID, newGroupId, ctx); |
| 120 | + } |
| 121 | + |
| 122 | + if (newArtifactId != null) { |
| 123 | + t = changeChildTagValue(t, ARTIFACT_ID, newArtifactId, ctx); |
| 124 | + } |
| 125 | + |
| 126 | + String currentVersion = t.getChildValue(ARTIFACT_VERSION).orElse(null); |
| 127 | + if (newVersion != null) { |
| 128 | + try { |
| 129 | + String resolvedNewVersion = resolveSemverVersion(ctx, newGroupId, newArtifactId, currentVersion); |
| 130 | + Optional<Xml.Tag> versionTag = t.getChild(ARTIFACT_VERSION); |
| 131 | + boolean versionTagPresent = versionTag.isPresent(); |
| 132 | + if (versionTagPresent) { |
| 133 | + t = changeChildTagValue(t, ARTIFACT_VERSION, resolvedNewVersion, ctx); |
| 134 | + } |
| 135 | + } catch (MavenDownloadingException e) { |
| 136 | + return e.warn(tag); |
| 137 | + } |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + return t; |
| 142 | + } |
| 143 | + |
| 144 | + private boolean isElementFound(Xml.Tag tag, boolean isElementFound, String groupId, String oldGroupId) { |
| 145 | + if (!isElementFound) { |
| 146 | + if (tag.getChildValue(groupId).isPresent() && tag.getChildValue(groupId).get().trim().startsWith("${")) { |
| 147 | + String propertyKey = tag.getChildValue(groupId).get().trim(); |
| 148 | + String value = getResolutionResult().getPom().getValue(propertyKey); |
| 149 | + isElementFound = value != null && matchesGlob(value, oldGroupId); |
| 150 | + } |
| 151 | + } |
| 152 | + return isElementFound; |
| 153 | + } |
| 154 | + |
| 155 | + @SuppressWarnings("ConstantConditions") |
| 156 | + private String resolveSemverVersion(ExecutionContext ctx, String groupId, String artifactId, @Nullable String currentVersion) throws MavenDownloadingException { |
| 157 | + if (versionComparator == null) { |
| 158 | + return newVersion; |
| 159 | + } |
| 160 | + String finalCurrentVersion = currentVersion != null ? currentVersion : newVersion; |
| 161 | + if (availableVersions == null) { |
| 162 | + availableVersions = new ArrayList<>(); |
| 163 | + MavenMetadata mavenMetadata = metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, ctx)); |
| 164 | + for (String v : mavenMetadata.getVersioning().getVersions()) { |
| 165 | + if (versionComparator.isValid(finalCurrentVersion, v)) { |
| 166 | + availableVersions.add(v); |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + return availableVersions.isEmpty() ? newVersion : Collections.max(availableVersions, versionComparator); |
| 171 | + } |
| 172 | + }; |
| 173 | + } |
| 174 | +} |
0 commit comments