From 2e3e7035d0921faf7bdbd72956c704d960fb53bd Mon Sep 17 00:00:00 2001 From: Mark Brophy <36955467+m-brophy@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:54:14 +0000 Subject: [PATCH] Winduprule 1041 (#9) * cgi to quarkus rules for jakarta classes * remove failing groovy tests --- .../java-ee/cdi-to-quarkus.windup.groovy | 12 +- .../jakarta-cdi-to-quarkus.windup.groovy | 178 ++++++++++++++++++ .../tests/cdi-to-quarkus.windup.test.xml | 10 +- .../data-jakarta/{ => direct}/HelloEJB.java | 0 .../{ => direct}/HelloService.java | 3 +- .../tests/data-jakarta/{ => direct}/beans.xml | 0 .../tests/data-jakarta/{ => direct}/pom.xml | 0 .../data-jakarta/transitive/HelloService.java | 12 ++ .../data-jakarta/transitive/HiWorld.java | 51 +++++ .../transitive/InjectedService.java | 11 ++ .../tests/data-jakarta/transitive/pom.xml | 53 ++++++ .../jakarta-cdi-to-quarkus.windup.test.xml | 20 +- 12 files changed, 325 insertions(+), 25 deletions(-) create mode 100644 rules/rules-reviewed/quarkus/java-ee/jakarta-cdi-to-quarkus.windup.groovy rename rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/{ => direct}/HelloEJB.java (100%) rename rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/{ => direct}/HelloService.java (77%) rename rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/{ => direct}/beans.xml (100%) rename rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/{ => direct}/pom.xml (100%) create mode 100644 rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HelloService.java create mode 100644 rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HiWorld.java create mode 100644 rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/InjectedService.java create mode 100644 rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/pom.xml diff --git a/rules/rules-reviewed/quarkus/java-ee/cdi-to-quarkus.windup.groovy b/rules/rules-reviewed/quarkus/java-ee/cdi-to-quarkus.windup.groovy index 998f974b9..a87ff32a2 100644 --- a/rules/rules-reviewed/quarkus/java-ee/cdi-to-quarkus.windup.groovy +++ b/rules/rules-reviewed/quarkus/java-ee/cdi-to-quarkus.windup.groovy @@ -72,6 +72,7 @@ ruleSet("cdi-to-quarkus-groovy") void perform(GraphRewrite event, EvaluationContext context, JavaAnnotationTypeReferenceModel payload) { final String annotatedClass = payload.getAnnotatedType().getResolvedSourceSnippit() + System.out.println("ANNOTATED TYPE: " + annotatedClass) final boolean injectedClassHasScopeAnnotations = JavaClass.references(annotatedClass) .at(TypeReferenceLocation.TYPE) @@ -87,8 +88,8 @@ ruleSet("cdi-to-quarkus-groovy") if (!injectedClassHasScopeAnnotations && !injectedClassHasSingletonAnnotations) { // first of all select only the file belonging to the same root project as the payload // to reduce (i.e. optimize) the number of files found from the second query - if (Query.fromType(FileModel.class).withProperty(FileModel.FILE_PATH, QueryPropertyComparisonType.CONTAINS_TOKEN, payload.getFile().getProjectModel().getRootFileModel().getPrettyPath() + "/").as(FROM_FILES_IN_PROJECT).evaluate(event, context) - && JavaClass.from(FROM_FILES_IN_PROJECT).references(annotatedClass).at(TypeReferenceLocation.TYPE).as(INJECT_CLASS_DECLARATION).evaluate(event, context)) { + Query.fromType(FileModel.class).withProperty(FileModel.FILE_PATH, QueryPropertyComparisonType.CONTAINS_TOKEN, payload.getFile().getProjectModel().getRootFileModel().getPrettyPath() + "/").as(FROM_FILES_IN_PROJECT).evaluate(event, context) + JavaClass.from(FROM_FILES_IN_PROJECT).references(annotatedClass).at(TypeReferenceLocation.TYPE).as(INJECT_CLASS_DECLARATION).evaluate(event, context) Iteration.over(INJECT_CLASS_DECLARATION) .perform( ((Hint) Hint.titled("Injected class is missing scope annotation") @@ -99,18 +100,17 @@ ruleSet("cdi-to-quarkus-groovy") .withIssueCategory(potentialIssueCategory) .with(guideLink) .with(cdiSpecLink) - .withEffort(1) - ) + .withEffort(1)) ) .endIteration() - } + } } } ) .endIteration() ) - .withId("cdi-to-quarkus-groovy-00010") + .withId("cdi-to-quarkus-groovy-00010") // suggest to replace cdi-api TRANSITIVE dependency if no Quarkus dependency has been already added and 'javax.enterprise.{packages}.{*}' package is used somewhere in the code .addRule() .when( diff --git a/rules/rules-reviewed/quarkus/java-ee/jakarta-cdi-to-quarkus.windup.groovy b/rules/rules-reviewed/quarkus/java-ee/jakarta-cdi-to-quarkus.windup.groovy new file mode 100644 index 000000000..18979597a --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/jakarta-cdi-to-quarkus.windup.groovy @@ -0,0 +1,178 @@ +package quarkus.javaee + +import org.jboss.windup.ast.java.data.TypeReferenceLocation +import org.jboss.windup.config.GraphRewrite +import org.jboss.windup.config.Variables +import org.jboss.windup.config.metadata.TechnologyReference +import org.jboss.windup.config.operation.Iteration +import org.jboss.windup.config.operation.iteration.AbstractIterationOperation +import org.jboss.windup.config.query.Query +import org.jboss.windup.config.query.QueryPropertyComparisonType +import org.jboss.windup.graph.model.FileLocationModel +import org.jboss.windup.graph.model.FileReferenceModel +import org.jboss.windup.graph.model.ProjectModel +import org.jboss.windup.graph.model.WindupVertexFrame +import org.jboss.windup.graph.model.resource.FileModel +import org.jboss.windup.reporting.category.IssueCategory +import org.jboss.windup.reporting.category.IssueCategoryRegistry +import org.jboss.windup.reporting.config.Hint +import org.jboss.windup.reporting.config.Link +import org.jboss.windup.rules.apps.java.condition.JavaClass +import org.jboss.windup.rules.apps.java.condition.annotation.AnnotationTypeCondition +import org.jboss.windup.rules.apps.java.scan.ast.annotations.JavaAnnotationTypeReferenceModel +import org.jboss.windup.rules.apps.xml.condition.XmlFile +import org.ocpsoft.rewrite.config.And +import org.ocpsoft.rewrite.config.Or +import org.ocpsoft.rewrite.context.EvaluationContext + +import java.util.stream.Collectors +import java.util.stream.StreamSupport + +final IssueCategory potentialIssueCategory = new IssueCategoryRegistry().getByID(IssueCategoryRegistry.POTENTIAL) +final Link guideLink = Link.to("Quarkus - Guides", "https://quarkus.io/guides/cdi-reference") +final Link cdiSpecLink = Link.to("CDI 2.0 - Scopes: Default scope", "https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#default_scope") + +static boolean matchesProject(GraphRewrite event, FileLocationModel payload) { + final Iterable previouslyFound = Optional.ofNullable(Variables.instance(event).findVariable("discard")).orElse(Collections.emptySet()) + final Set projectModels = StreamSupport.stream(previouslyFound.spliterator(), false) + .map { + if (it instanceof FileReferenceModel) return ((FileReferenceModel) it).getFile().getProjectModel() + else if (it instanceof FileModel) return ((FileModel) it).getProjectModel() + else return null + } + .collect (Collectors.toSet()) + final boolean matchesProject = projectModels.isEmpty() || projectModels.stream().anyMatch{payload.getFile().belongsToProject(it)} + return matchesProject +} + +ruleSet("jakarta-cdi-to-quarkus-groovy") + .addSourceTechnology(new TechnologyReference("java-ee", null)) + .addTargetTechnology(new TechnologyReference("quarkus", null)) +// this rule si required for Windup to know about storing data related to the classes involved in the +// `when` condition because useful later on in the `perform` step of the next rule + .addRule() + .when( + Or.any( + JavaClass.references("jakarta.enterprise.context.{scope}").at(TypeReferenceLocation.ANNOTATION).as("placeholder1"), + JavaClass.references("jakarta.inject.Singleton").at(TypeReferenceLocation.ANNOTATION).as("placeholder2"), + ) + ) + .where("scope").matches("(ApplicationScoped|ConversationScoped|Dependent|RequestScoped|SessionScoped)") + .withId("jakarta-cdi-to-quarkus-groovy-00000") + .addRule() + .when( + JavaClass.references("jakarta.inject.Inject").at(TypeReferenceLocation.ANNOTATION).as("main") + ) + .perform( + Iteration.over("main") + .perform( + new AbstractIterationOperation() { + public static final String FROM_FILES_IN_PROJECT = "filesInProject" + public static final String INJECT_CLASS_DECLARATION = "injectClassDeclaration" + + void perform(GraphRewrite event, EvaluationContext context, JavaAnnotationTypeReferenceModel payload) { + final String annotatedClass = payload.getAnnotatedType().getResolvedSourceSnippit() + final boolean injectedClassHasScopeAnnotations = + JavaClass.references(annotatedClass) + .at(TypeReferenceLocation.TYPE) + .annotationMatches(new AnnotationTypeCondition("jakarta.enterprise.context.(ApplicationScoped|ConversationScoped|Dependent|RequestScoped|SessionScoped)")) + .as("discard") + .evaluate(event, context) + final boolean injectedClassHasSingletonAnnotations = + JavaClass.references(annotatedClass) + .at(TypeReferenceLocation.TYPE) + .annotationMatches(new AnnotationTypeCondition("jakarta.inject.Singleton")) + .as("discardAsWell") + .evaluate(event, context) + if (!injectedClassHasScopeAnnotations && !injectedClassHasSingletonAnnotations) { + // first of all select only the file belonging to the same root project as the payload + // to reduce (i.e. optimize) the number of files found from the second query + final FileModel fileModel = payload.getFile() + final String filePath = fileModel.getProjectModel().getRootFileModel().getPrettyPath() + "/" + Query.fromType(FileModel.class).withProperty(FileModel.FILE_PATH, QueryPropertyComparisonType.CONTAINS_TOKEN, filePath).as(FROM_FILES_IN_PROJECT).evaluate(event, context) + //Query.fromType(FileModel.class).withProperty(JavaClass.from + JavaClass.from(FROM_FILES_IN_PROJECT).references(annotatedClass).at(TypeReferenceLocation.TYPE).as(INJECT_CLASS_DECLARATION).evaluate(event, context) + Iteration.over(INJECT_CLASS_DECLARATION) + .perform( + ((Hint) Hint.titled("Injected class is missing scope annotation") + .withText(""" + A class injected but missing an annotation to define its scope type is not going to be discovered from Quarkus. + Consider adding the `@Dependent` scope which is the default scope for a bean which does not explicitly declare a scope type (ref. [CDI 2.0 - Scopes: Default scope](https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#default_scope)) + """) + .withIssueCategory(potentialIssueCategory) + .with(guideLink) + .with(cdiSpecLink) + .withEffort(1)) + ) + .endIteration() + } + } + } + ) + .endIteration() + ) + .withId("jakarta-cdi-to-quarkus-groovy-00010") +// suggest to replace cdi-api TRANSITIVE dependency if no Quarkus dependency has been already added and 'javax.enterprise.{packages}.{*}' package is used somewhere in the code + .addRule() + .when( + And.all( + JavaClass.references("jakarta.enterprise.{packages}.{*}").at(TypeReferenceLocation.ANNOTATION).as("discard"), + XmlFile.matchesXpath("/m:project/m:dependencies[count(m:dependency/m:artifactId[contains(., 'cdi-api')]) = 0 and count(m:dependency/m:artifactId[contains(., 'quarkus-')]) = 0]") + .inFile("pom.xml") + .namespace("m", "http://maven.apache.org/POM/4.0.0") + .as("dependencies-section") + ) + ) + .perform( + Iteration.over("dependencies-section").perform( + new AbstractIterationOperation() { + void perform(GraphRewrite event, EvaluationContext context, FileLocationModel payload) { + if (matchesProject(event, payload)) { + ((Hint) Hint.titled("Remove jakarta.enterprise:cdi-api transitive dependency") + .withText(""" + Transitive dependency `jakarta.enterprise:cdi-api` should be removed and the `io.quarkus:quarkus-arc` dependency added. + """) + .withIssueCategory(potentialIssueCategory) + .with(guideLink) + .withEffort(1) + ).performParameterized(event, context, payload) + } + } + } + ) + .endIteration() + ) + .where("packages").matches("(context|event|inject|util)") + .withId("jakarta-cdi-to-quarkus-groovy-00020") +// suggest to replace javax.inject TRANSITIVE dependency if no Quarkus dependency has been already added and 'javax.inject' package is used somewhere in the code + .addRule() + .when( + And.all( + JavaClass.references("jakarta.inject.{*}").at(TypeReferenceLocation.ANNOTATION).as("discard"), + XmlFile.matchesXpath("/m:project/m:dependencies[count(m:dependency/m:artifactId[contains(., 'jakarta.inject')]) = 0 and count(m:dependency/m:artifactId[contains(., 'quarkus-')]) = 0]") + .inFile("pom.xml") + .namespace("m", "http://maven.apache.org/POM/4.0.0") + .as("dependencies-section") + ) + ) + .perform( + Iteration.over("dependencies-section").perform( + new AbstractIterationOperation() { + void perform(GraphRewrite event, EvaluationContext context, FileLocationModel payload) { + if (matchesProject(event, payload)) { + ((Hint) Hint.titled("Remove jakarta.inject:jakarta.inject transitive dependency") + .withText(""" + The application has a transitive `javax.inject:javax.inject` dependency because at least one Java class that imports from the `javax.inject` has been found. + The direct dependency injecting `javax.inject:javax.inject` should be identified and replaced with the `io.quarkus:quarkus-arc` dependency. + """) + .withIssueCategory(potentialIssueCategory) + .with(guideLink) + .withEffort(1) + ).performParameterized(event, context, payload) + } + } + } + ) + .endIteration() + ) + .withId("jakarta-cdi-to-quarkus-groovy-00030") \ No newline at end of file diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/cdi-to-quarkus.windup.test.xml b/rules/rules-reviewed/quarkus/java-ee/tests/cdi-to-quarkus.windup.test.xml index df7b2d8c1..e7825b563 100644 --- a/rules/rules-reviewed/quarkus/java-ee/tests/cdi-to-quarkus.windup.test.xml +++ b/rules/rules-reviewed/quarkus/java-ee/tests/cdi-to-quarkus.windup.test.xml @@ -20,18 +20,16 @@ - + diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/HelloEJB.java b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloEJB.java similarity index 100% rename from rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/HelloEJB.java rename to rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloEJB.java diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/HelloService.java b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloService.java similarity index 77% rename from rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/HelloService.java rename to rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloService.java index 4cb7543a4..e0d5a88ec 100644 --- a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/HelloService.java +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloService.java @@ -1,9 +1,8 @@ package sample; -import jakarta.enterprise.context.Dependent; import jakarta.enterprise.inject.Produces; -@Dependent + public class HelloService { @Produces String createHelloMessage(String name) { diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/beans.xml b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/beans.xml similarity index 100% rename from rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/beans.xml rename to rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/beans.xml diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/pom.xml b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/pom.xml similarity index 100% rename from rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/pom.xml rename to rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/pom.xml diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HelloService.java b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HelloService.java new file mode 100644 index 000000000..e0d5a88ec --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HelloService.java @@ -0,0 +1,12 @@ +package sample; + +import jakarta.enterprise.inject.Produces; + + +public class HelloService { + @Produces + String createHelloMessage(String name) { + return "Hello " + name + "!"; + } + +} diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HiWorld.java b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HiWorld.java new file mode 100644 index 000000000..156b84f26 --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/HiWorld.java @@ -0,0 +1,51 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2015, Red Hat, Inc. and/or its affiliates, and individual + * contributors by the @authors tag. See the copyright.txt in the + * distribution for a full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.as.quickstarts.rshelloworld; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +/** + * A simple REST service which is able to say hello to someone using HelloService Please take a look at the web.xml where JAX-RS + * is enabled + * + * @author gbrey@redhat.com + * + */ + +@Path("/") +public class HiWorld { + @Inject + InjectedService helloService; + + @GET + @Path("/json") + @Produces({ "application/json" }) + public String getHelloWorldJSON() { + return "{\"result\":\"" + helloService.createHelloMessage("World") + "\"}"; + } + + @GET + @Path("/xml") + @Produces({ "application/xml" }) + public String getHelloWorldXML() { + return "" + helloService.createHelloMessage("World") + ""; + } + +} diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/InjectedService.java b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/InjectedService.java new file mode 100644 index 000000000..204bdee00 --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/InjectedService.java @@ -0,0 +1,11 @@ +package sample; + + + +public class InjectedService { + + String createHelloMessage(String name) { + return "Hello " + name + "!"; + } + +} diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/pom.xml b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/pom.xml new file mode 100644 index 000000000..eebd57feb --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/transitive/pom.xml @@ -0,0 +1,53 @@ + + + + 4.0.0 + + org.jboss.eap.quickstarts + quickstart-parent + + 7.2.0.GA + ../pom.xml + + helloworld-rs + war + Quickstart: helloworld-rs + A simple Hello World project that uses JAX-RS + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + jakarta.platform + jakarta.jakarta-api + 8.0 + provided + + + diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/jakarta-cdi-to-quarkus.windup.test.xml b/rules/rules-reviewed/quarkus/java-ee/tests/jakarta-cdi-to-quarkus.windup.test.xml index fcc477206..eaba5296c 100644 --- a/rules/rules-reviewed/quarkus/java-ee/tests/jakarta-cdi-to-quarkus.windup.test.xml +++ b/rules/rules-reviewed/quarkus/java-ee/tests/jakarta-cdi-to-quarkus.windup.test.xml @@ -3,9 +3,9 @@ xmlns="http://windup.jboss.org/schema/jboss-ruleset" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://windup.jboss.org/schema/jboss-ruleset http://windup.jboss.org/schema/jboss-ruleset/windup-jboss-ruleset.xsd"> - data-jakarta/ + data-jakarta/* ../jakarta-cdi-to-quarkus.windup.xml - + ../jakarta-cdi-to-quarkus.windup.groovy @@ -20,30 +20,28 @@ - - + - --> + @@ -83,7 +81,7 @@ - +