diff --git a/recipes-unit-tests/src/test/java/io/quarkus/updates/core/CoreUpdate37Test.java b/recipes-unit-tests/src/test/java/io/quarkus/updates/core/CoreUpdate37Test.java index 48e9b3d7e9..d34d139d4d 100644 --- a/recipes-unit-tests/src/test/java/io/quarkus/updates/core/CoreUpdate37Test.java +++ b/recipes-unit-tests/src/test/java/io/quarkus/updates/core/CoreUpdate37Test.java @@ -250,4 +250,569 @@ void testMavenPluginUpdatesDefaultGroupId() { """)); } + + @Test + void testJpaModelgenNoChanges() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + + """)); + } + + @Test + void testJpaModelgenOldDependency() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + org.hibernate + hibernate-jpamodelgen + 5.4.3.Final + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } + + @Test + void testJpaModelgenNewDependency() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + org.hibernate.orm + hibernate-jpamodelgen + 6.1.2.Final + provided + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } + + @Test + void testJpaModelgenNewDependencyManagedByQuarkusBom() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + io.quarkus + quarkus-bom + 3.6.4 + pom + import + + + + + + org.hibernate.orm + hibernate-jpamodelgen + 6.1.2.Final + provided + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + io.quarkus + quarkus-bom + 3.6.4 + pom + import + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } + + @Test + void testJpaModelgenExistingConfig() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + org.hibernate.orm + hibernate-jpamodelgen + 6.1.2.Final + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + none + + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + none + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } + + @Test + void testJpaModelgenAnotherProcessor() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + org.hibernate.orm + hibernate-jpamodelgen + 6.1.2.Final + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + none + + + another.annotation + processor + + + + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + none + + + another.annotation + processor + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } + + @Test + void testJpaModelgenAnnotationProcessorAlreadyPresent() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + org.hibernate.orm + hibernate-jpamodelgen + 6.1.2.Final + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } + + @Test + void testJpaModelgenOldJpaModelgenAnnotationProcessorAlreadyPresent() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + io.quarkus + quarkus-bom + 3.6.4 + pom + import + + + + + + org.hibernate.orm + hibernate-jpamodelgen + 6.1.2.Final + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate + hibernate-jpamodelgen + + + + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + io.quarkus + quarkus-bom + 3.6.4 + pom + import + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } + + @Test + void testJpaModelgenOldJpaModelgenAnnotationProcessorAlreadyPresentWithOutdatedVersion() { + //language=xml + rewriteRun(pomXml(""" + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + io.quarkus + quarkus-bom + 3.6.4 + pom + import + + + + + + org.hibernate.orm + hibernate-jpamodelgen + 6.1.2.Final + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate + hibernate-jpamodelgen + 5.6.15.Final + + + + + + + + """, + """ + + 4.0.0 + io.quarkus.bot + release + 999-SNAPSHOT + + + + io.quarkus + quarkus-bom + 3.6.4 + pom + import + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + + + + """)); + } } diff --git a/recipes/src/main/java/io/quarkus/updates/core/quarkus37/AddMavenCompilerAnnotationProcessor.java b/recipes/src/main/java/io/quarkus/updates/core/quarkus37/AddMavenCompilerAnnotationProcessor.java new file mode 100644 index 0000000000..52b69e727f --- /dev/null +++ b/recipes/src/main/java/io/quarkus/updates/core/quarkus37/AddMavenCompilerAnnotationProcessor.java @@ -0,0 +1,84 @@ +package io.quarkus.updates.core.quarkus37; + +import static org.openrewrite.xml.AddToTagVisitor.addToTag; + +import java.util.List; +import java.util.Optional; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.MavenVisitor; +import org.openrewrite.xml.tree.Xml; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper = true) +public class AddMavenCompilerAnnotationProcessor extends Recipe { + + @Option(displayName = "Annotation processor groupId", description = "The groupId of the annotation processor.", example = "org.hibernate.orm", required = true) + String groupId; + + @Option(displayName = "Annotation processor artifactId", description = "The artifactId of the annotation processor.", example = "hibernate-jpamodelgen", required = true) + String artifactId; + + @Override + public String getDisplayName() { + return "Add an annotation processor to the Maven Compiler plugin configuration"; + } + + @Override + public String getDescription() { + return "Add an annotation processor to the Maven Compiler plugin configuration."; + } + + @Override + public TreeVisitor getVisitor() { + return new MavenVisitor() { + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag mavenCompilerPluginTag = (Xml.Tag) super.visitTag(tag, ctx); + + if (isPluginTag("org.apache.maven.plugins", "maven-compiler-plugin")) { + Optional configuration = mavenCompilerPluginTag.getChild("configuration"); + + String annotationProcessorPath = "" + groupId + "\n" + artifactId + + ""; + + if (configuration.isPresent()) { + Optional annotationProcessorPathsWrapper = configuration.get() + .getChild("annotationProcessorPaths"); + if (annotationProcessorPathsWrapper.isPresent()) { + List annotationProcessorPaths = annotationProcessorPathsWrapper.get().getChildren(); + String childName = annotationProcessorPaths.size() > 0 ? annotationProcessorPaths.get(0).getName() + : "path"; + + if (!annotationProcessorPaths.stream() + .anyMatch(t -> groupId.equals(t.getChildValue("groupId").orElse(null)) + && artifactId.equals(t.getChildValue("artifactId").orElse(null)))) { + mavenCompilerPluginTag = addToTag(mavenCompilerPluginTag, annotationProcessorPathsWrapper.get(), + Xml.Tag.build("<" + childName + ">\n" + annotationProcessorPath + "\n"), + getCursor().getParentOrThrow()); + } + } else { + mavenCompilerPluginTag = addToTag(mavenCompilerPluginTag, configuration.get(), + Xml.Tag.build("\n\n" + annotationProcessorPath + + "\n\n"), + getCursor().getParentOrThrow()); + } + } else { + mavenCompilerPluginTag = addToTag(mavenCompilerPluginTag, + Xml.Tag.build("\n\n\n" + annotationProcessorPath + + "\n\n\n"), + getCursor().getParentOrThrow()); + } + } + return mavenCompilerPluginTag; + } + }; + } +} diff --git a/recipes/src/main/java/io/quarkus/updates/core/quarkus37/ChangeMavenCompilerAnnotationProcessorGroupIdAndArtifactId.java b/recipes/src/main/java/io/quarkus/updates/core/quarkus37/ChangeMavenCompilerAnnotationProcessorGroupIdAndArtifactId.java new file mode 100644 index 0000000000..eb3e5a7331 --- /dev/null +++ b/recipes/src/main/java/io/quarkus/updates/core/quarkus37/ChangeMavenCompilerAnnotationProcessorGroupIdAndArtifactId.java @@ -0,0 +1,220 @@ +package io.quarkus.updates.core.quarkus37; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.Validated; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.MavenTagInsertionComparator; +import org.openrewrite.maven.MavenVisitor; +import org.openrewrite.maven.table.MavenMetadataFailures; +import org.openrewrite.maven.tree.MavenMetadata; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedManagedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; +import org.openrewrite.xml.AddToTagVisitor; +import org.openrewrite.xml.ChangeTagValueVisitor; +import org.openrewrite.xml.RemoveContentVisitor; +import org.openrewrite.xml.tree.Xml; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper = true) +public class ChangeMavenCompilerAnnotationProcessorGroupIdAndArtifactId extends Recipe { + + @EqualsAndHashCode.Exclude + MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); + + @Option(displayName = "Old groupId", description = "The old groupId to replace. The groupId is the first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob expressions.", example = "org.hibernate") + String oldGroupId; + + @Option(displayName = "Old artifactId", description = "The old artifactId to replace. The artifactId is the second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob expressions.", example = "hibernate-jpamodelgen") + String oldArtifactId; + + @Option(displayName = "New groupId", description = "The new groupId to use. Defaults to the existing group id.", example = "org.hibernate.orm", required = false) + @Nullable + String newGroupId; + + @Option(displayName = "New artifactId", description = "The new artifactId to use. Defaults to the existing artifact id.", example = "hibernate-jpamodelgen", required = false) + @Nullable + String newArtifactId; + + @Option(displayName = "New version", description = "An exact version number or node-style semver selector used to select the version number.", example = "29.X", required = false) + @Nullable + String newVersion; + + @Option(displayName = "Version pattern", description = "Allows version selection to be extended beyond the original Node Semver semantics. So for example," + + + "Setting 'version' to \"25-29\" can be paired with a metadata pattern of \"-jre\" to select Guava 29.0-jre", example = "-jre", required = false) + @Nullable + String versionPattern; + + @Option(displayName = "Override managed version", description = "If the new annotation processor has a managed version, this flag can be used to explicitly set the version on the annotation processor. The default for this flag is `false`.", required = false) + @Nullable + Boolean overrideManagedVersion; + + @Override + public String getDisplayName() { + return "Change Maven Compiler plugin annotation processor groupId, artifactId and/or the version"; + } + + @Override + public String getDescription() { + return "Change the groupId, artifactId and/or the version of a specified Maven Compiler plugin annotation processor."; + } + + @Override + public Validated validate() { + Validated validated = super.validate(); + if (newVersion != null) { + validated = validated.and(Semver.validate(newVersion, versionPattern)); + } + return validated; + } + + @Override + public TreeVisitor getVisitor() { + + return new MavenVisitor() { + @Nullable + final VersionComparator versionComparator = newVersion != null + ? Semver.validate(newVersion, versionPattern).getValue() + : null; + @Nullable + private Collection availableVersions; + + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag mavenCompilerPluginTag = (Xml.Tag) super.visitTag(tag, ctx); + + if (isPluginTag("org.apache.maven.plugins", "maven-compiler-plugin")) { + Optional configuration = mavenCompilerPluginTag.getChild("configuration"); + if (configuration.isPresent()) { + Optional annotationProcessorPathsWrapper = configuration.get() + .getChild("annotationProcessorPaths"); + if (annotationProcessorPathsWrapper.isPresent()) { + Optional annotationProcessorPathCandidate = annotationProcessorPathsWrapper.get() + .getChildren().stream() + .filter(t -> oldGroupId.equals(t.getChildValue("groupId").orElse(null)) + && oldArtifactId.equals(t.getChildValue("artifactId").orElse(null))) + .findFirst(); + + if (annotationProcessorPathCandidate.isPresent()) { + Xml.Tag annotationProcessorPath = annotationProcessorPathCandidate.get(); + String groupId = newGroupId; + if (groupId != null) { + mavenCompilerPluginTag = changeChildTagValue(mavenCompilerPluginTag, annotationProcessorPath, "groupId", groupId, + ctx); + } else { + groupId = annotationProcessorPathCandidate.get().getChildValue("groupId") + .orElseThrow(NoSuchElementException::new); + } + String artifactId = newArtifactId; + if (artifactId != null) { + mavenCompilerPluginTag = changeChildTagValue(mavenCompilerPluginTag, annotationProcessorPath, "artifactId", + artifactId, ctx); + } else { + artifactId = annotationProcessorPathCandidate.get().getChildValue("artifactId") + .orElseThrow(NoSuchElementException::new); + } + + if (newVersion != null) { + try { + String resolvedNewVersion = resolveSemverVersion(ctx, groupId, artifactId); + Scope scope = Scope.Compile; + // starting with Maven Compiler plugin 3.12.0, the annotation processor versions will honor the dependency management + Optional versionTag = annotationProcessorPath.getChild("version"); + if (!versionTag.isPresent() && + (Boolean.TRUE.equals(overrideManagedVersion) + || !isDependencyManaged(groupId, artifactId))) { + //If the version is not present, add the version if we are explicitly overriding a managed version or if no managed version exists. + Xml.Tag newVersionTag = Xml.Tag + .build("" + resolvedNewVersion + ""); + //noinspection ConstantConditions + mavenCompilerPluginTag = (Xml.Tag) new AddToTagVisitor( + annotationProcessorPath, newVersionTag, + new MavenTagInsertionComparator(annotationProcessorPath.getChildren())) + .visitNonNull(mavenCompilerPluginTag, ctx, + getCursor().getParent()); + } else if (versionTag.isPresent()) { + if (isDependencyManaged(groupId, artifactId) + && !Boolean.TRUE.equals(overrideManagedVersion)) { + //If the previous dependency had a version but the new artifact is managed, removed the + //version tag. + mavenCompilerPluginTag = (Xml.Tag) new RemoveContentVisitor<>(versionTag.get(), + false).visit(mavenCompilerPluginTag, ctx); + } else { + //Otherwise, change the version to the new value. + mavenCompilerPluginTag = changeChildTagValue(mavenCompilerPluginTag, annotationProcessorPath, + "version", resolvedNewVersion, ctx); + } + } + } catch (MavenDownloadingException e) { + return e.warn(tag); + } + } + } + } + } + } + + //noinspection ConstantConditions + return mavenCompilerPluginTag; + } + + private Xml.Tag changeChildTagValue(Xml.Tag parentScope, Xml.Tag tag, String childTagName, String newValue, ExecutionContext ctx) { + Optional childTag = tag.getChild(childTagName); + if (childTag.isPresent() && !newValue.equals(childTag.get().getValue().orElse(null))) { + tag = (Xml.Tag) new ChangeTagValueVisitor<>(childTag.get(), newValue).visitNonNull(parentScope, ctx); + } + return tag; + } + + private boolean isDependencyManaged(String groupId, String artifactId) { + + MavenResolutionResult result = getResolutionResult(); + for (ResolvedManagedDependency managedDependency : result.getPom().getDependencyManagement()) { + if (groupId.equals(managedDependency.getGroupId()) + && artifactId.equals(managedDependency.getArtifactId())) { + return Scope.Compile.isInClasspathOf(managedDependency.getScope()) || + Scope.Runtime.isInClasspathOf(managedDependency.getScope()) || + Scope.Provided.isInClasspathOf(managedDependency.getScope()); + } + } + return false; + } + + @SuppressWarnings("ConstantConditions") + private String resolveSemverVersion(ExecutionContext ctx, String groupId, String artifactId) + throws MavenDownloadingException { + if (versionComparator == null) { + return newVersion; + } + if (availableVersions == null) { + availableVersions = new ArrayList<>(); + MavenMetadata mavenMetadata = metadataFailures.insertRows(ctx, + () -> downloadMetadata(groupId, artifactId, ctx)); + for (String v : mavenMetadata.getVersioning().getVersions()) { + if (versionComparator.isValid(newVersion, v)) { + availableVersions.add(v); + } + } + + } + return availableVersions.isEmpty() ? newVersion : Collections.max(availableVersions, versionComparator); + } + }; + } +} diff --git a/recipes/src/main/resources/quarkus-updates/core/3.7.yaml b/recipes/src/main/resources/quarkus-updates/core/3.7.yaml index ab5582f5a5..2ae847301f 100644 --- a/recipes/src/main/resources/quarkus-updates/core/3.7.yaml +++ b/recipes/src/main/resources/quarkus-updates/core/3.7.yaml @@ -30,6 +30,51 @@ recipeList: artifactId: maven-surefire-plugin newVersion: 3.2.3 +##### +# Adjust jpamodelgen annotation processor +##### +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.quarkus.updates.core.quarkus37.ReplaceJpaModelgenAnnotationProcessor +recipeList: + - io.quarkus.updates.core.quarkus37.ChangeMavenCompilerAnnotationProcessorGroupIdAndArtifactId: + oldGroupId: org.hibernate + oldArtifactId: hibernate-jpamodelgen + newGroupId: org.hibernate.orm + newVersion: 6.x +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.quarkus.updates.core.quarkus37.AddJpaModelgenAnnotationProcessor +recipeList: + - io.quarkus.updates.core.quarkus37.AddMavenCompilerAnnotationProcessor: + groupId: org.hibernate.orm + artifactId: hibernate-jpamodelgen +preconditions: + - org.openrewrite.maven.search.FindDependency: + groupId: org.hibernate.orm + artifactId: hibernate-jpamodelgen +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.quarkus.updates.core.quarkus37.AddJpaModelgenAnnotationProcessorIfOldArtifact +recipeList: + - io.quarkus.updates.core.quarkus37.AddMavenCompilerAnnotationProcessor: + groupId: org.hibernate.orm + artifactId: hibernate-jpamodelgen +preconditions: + - org.openrewrite.maven.search.FindDependency: + groupId: org.hibernate + artifactId: hibernate-jpamodelgen +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.quarkus.updates.core.quarkus37.RemoveJpaModelgenDependencies +recipeList: + - org.openrewrite.maven.RemoveDependency: + groupId: org.hibernate + artifactId: hibernate-jpamodelgen + - org.openrewrite.maven.RemoveDependency: + groupId: org.hibernate.orm + artifactId: hibernate-jpamodelgen + ##### # Upgrade to Java 17 #