diff --git a/rules/pom.xml b/rules/pom.xml index 281fada02..1dc6a72eb 100644 --- a/rules/pom.xml +++ b/rules/pom.xml @@ -378,6 +378,7 @@ eap6/api-jars/*.jar eap7/api-jars/*.jar openjdk11/api-jars/*.jar + quarkus/api-jars/*.jar **/*.xsl **/rewrite.yml diff --git a/rules/rules-reviewed/quarkus/api-jars/jakarta.ejb-api-4.0.0.jar b/rules/rules-reviewed/quarkus/api-jars/jakarta.ejb-api-4.0.0.jar new file mode 100644 index 000000000..0ba845463 Binary files /dev/null and b/rules/rules-reviewed/quarkus/api-jars/jakarta.ejb-api-4.0.0.jar differ diff --git a/rules/rules-reviewed/quarkus/api-jars/jakarta.enterprise.cdi-api-4.0.0.jar b/rules/rules-reviewed/quarkus/api-jars/jakarta.enterprise.cdi-api-4.0.0.jar new file mode 100644 index 000000000..461eb235e Binary files /dev/null and b/rules/rules-reviewed/quarkus/api-jars/jakarta.enterprise.cdi-api-4.0.0.jar differ 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/jakarta-cdi-to-quarkus.windup.xml b/rules/rules-reviewed/quarkus/java-ee/jakarta-cdi-to-quarkus.windup.xml new file mode 100644 index 000000000..643ea088d --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/jakarta-cdi-to-quarkus.windup.xml @@ -0,0 +1,97 @@ + + + + + This ruleset gives hints to migrate CDI usage to Quarkus + + + + + + + + + + + + + + + + + + + + Dependency `jakarta.enterprise:jakarta.enterprise.cdi-api` has to be replaced with `io.quarkus:quarkus-arc` artifact. + + + + + + + + + + + + + + + + Dependency `jakarta.inject:jakarta.inject-api` has to be replaced with `io.quarkus:quarkus-arc` artifact. + + + + + + + + + + + + + + + + + The `beans.xml` descriptor content is ignored and it could be removed from the application. + Refer to the guide referenced below to check the supported CDI feature in Quarkus. + + + + + + + + + + ANNOTATION + + + + + In Quarkus you can skip the @Produces annotation completely if the producer method is annotated with a scope annotation, a stereotype or a qualifier.. + This field could be accessed using a `@Named` getter method instead. + + + + + + + + + ANNOTATION + + + + + The Stateless EJBs can be converted to a cdi bean by replacing the `@Stateless` annotation with a scope eg `@ApplicationScoped` + + + + + + + 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/direct/HelloEJB.java b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloEJB.java new file mode 100644 index 000000000..e53a0805f --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloEJB.java @@ -0,0 +1,10 @@ +import jakarta.ejb.Stateless; + +@Stateless +public class HelloEJB { + + String createHelloMessage(String name) { + return "Hello " + name + "!"; + } + +} \ No newline at end of file diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloService.java b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/HelloService.java new file mode 100644 index 000000000..e0d5a88ec --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/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/direct/beans.xml b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/beans.xml new file mode 100644 index 000000000..24fb02c90 --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/beans.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/pom.xml b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/pom.xml new file mode 100644 index 000000000..dd5f9e3c0 --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/data-jakarta/direct/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + foo + bar + 999 + + 3.10.1 + 11 + + + + jakarta.enterprise + jakarta.enterprise.cdi-api + 4.0.1 + + + jakarta.inject + jakarta.inject-api + 2.0.1 + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + foo + + + + + + \ No newline at end of file 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 new file mode 100644 index 000000000..eaba5296c --- /dev/null +++ b/rules/rules-reviewed/quarkus/java-ee/tests/jakarta-cdi-to-quarkus.windup.test.xml @@ -0,0 +1,107 @@ + + + data-jakarta/* + ../jakarta-cdi-to-quarkus.windup.xml + ../jakarta-cdi-to-quarkus.windup.groovy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rules/rules-reviewed/quarkus/quarkus.technology.metadata.xml b/rules/rules-reviewed/quarkus/quarkus.technology.metadata.xml new file mode 100644 index 000000000..7d7028a62 --- /dev/null +++ b/rules/rules-reviewed/quarkus/quarkus.technology.metadata.xml @@ -0,0 +1,6 @@ + + + + ./api-jars/jakarta.ejb-api-4.0.0.jar + ./api-jars/jakarta.enterprise.cdi-api-4.0.0.jar + \ No newline at end of file