Skip to content

Commit

Permalink
WINDUPRULE-1041 jakarta cdi to quarkus rules (#1040)
Browse files Browse the repository at this point in the history
* WINDUPRULE-1041 Work in progress

* Update jakarta-cdi-to-quarkus.windup.test.xml

attempt to fix test failures

* Winduprule 1041 (#9)

* cgi to quarkus rules for jakarta classes

* remove failing groovy tests

---------

Co-authored-by: Phil Cattanach <[email protected]>
Co-authored-by: Mark Brophy <[email protected]>
  • Loading branch information
3 people authored Jan 16, 2024
1 parent fa0bfa5 commit 31dd0b8
Show file tree
Hide file tree
Showing 17 changed files with 591 additions and 12 deletions.
1 change: 1 addition & 0 deletions rules/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
<include>eap6/api-jars/*.jar</include>
<include>eap7/api-jars/*.jar</include>
<include>openjdk11/api-jars/*.jar</include>
<include>quarkus/api-jars/*.jar</include>
<include>**/*.xsl</include>
<include>**/rewrite.yml</include>
</includes>
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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")
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<? extends WindupVertexFrame> previouslyFound = Optional.ofNullable(Variables.instance(event).findVariable("discard")).orElse(Collections.emptySet())
final Set<ProjectModel> 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<JavaAnnotationTypeReferenceModel>() {
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<FileLocationModel>() {
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<FileLocationModel>() {
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")
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?xml version="1.0"?>
<ruleset xmlns="http://windup.jboss.org/schema/jboss-ruleset" id="jakarta-cdi-to-quarkus"
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">
<metadata>
<description>
This ruleset gives hints to migrate CDI usage to Quarkus
</description>
<dependencies>
<addon id="org.jboss.windup.rules,windup-rules-javaee,3.0.0.Final" />
<addon id="org.jboss.windup.rules,windup-rules-java,3.0.0.Final" />
<addon id="org.jboss.windup.rules,windup-rules-xml,3.0.0.Final" />
</dependencies>
<sourceTechnology id="java-ee" />
<targetTechnology id="quarkus" />
</metadata>
<rules>
<rule id="jakarta-cdi-to-quarkus-00000">
<when>
<xmlfile in="pom.xml" matches="/m:project/m:dependencies/m:dependency[m:artifactId/text() = 'jakarta.enterprise.cdi-api' and m:groupId/text() = 'jakarta.enterprise' and (count(../m:dependency/m:groupId[contains(., 'io.quarkus')]) = 0)]" >
<namespace prefix="m" uri="http://maven.apache.org/POM/4.0.0" />
</xmlfile>
</when>
<perform>
<hint title="Replace jakarta.enterprise:jakarta.enterprise.cdi-api dependency" effort="1" category-id="mandatory">
<message>
Dependency `jakarta.enterprise:jakarta.enterprise.cdi-api` has to be replaced with `io.quarkus:quarkus-arc` artifact.
</message>
<link title="Quarkus - Guide" href="https://quarkus.io/guides/cdi-reference" />
</hint>
</perform>
</rule>
<!-- suggest to replace jakarta.inject dependency if no Quarkus dependency has been already added -->
<rule id="jakarta-cdi-to-quarkus-00020">
<when>
<xmlfile in="pom.xml" matches="/m:project/m:dependencies/m:dependency[m:artifactId/text() = 'jakarta.inject-api' and m:groupId/text() = 'jakarta.inject' and (count(../m:dependency/m:groupId[contains(., 'io.quarkus')]) = 0)]" >
<namespace prefix="m" uri="http://maven.apache.org/POM/4.0.0" />
</xmlfile>
</when>
<perform>
<hint title="Replace jakarta.inject:jakarta.inject-api dependency" effort="1" category-id="mandatory">
<message>
Dependency `jakarta.inject:jakarta.inject-api` has to be replaced with `io.quarkus:quarkus-arc` artifact.
</message>
<link title="Quarkus - Guide" href="https://quarkus.io/guides/cdi-reference" />
</hint>
</perform>
</rule>
<!-- explain beans.xml descriptor content is ignored -->
<rule id="jakarta-cdi-to-quarkus-00030">
<when>
<xmlfile in="beans.xml" matches="/b:beans" as="root">
<namespace prefix="b" uri="https://jakarta.ee/xml/ns/jakartaee" />
</xmlfile>
</when>
<perform>
<iteration over="root">
<hint title="`beans.xml` descriptor content is ignored" effort="3" category-id="potential">
<message>
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.
</message>
<link title="Quarkus - Guide" href="https://quarkus.io/guides/cdi-reference#limitations" />
</hint>
</iteration>
</perform>
</rule>
<rule id="jakarta-cdi-to-quarkus-00040">
<when>
<javaclass references="jakarta.enterprise.inject.Produces">
<location>ANNOTATION</location>
</javaclass>
</when>
<perform>
<hint title="Producer annotation no longer required" effort="1" category-id="potential">
<message>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.
</message>
<link title="Quarkus Simplified Producer Method Declaration" href="https://quarkus.io/guides/cdi-reference#simplified-producer-method-declaration"/>
</hint>
</perform>
</rule>
<rule id="jakarta-cdi-to-quarkus-00050">
<when>
<javaclass references="jakarta.ejb.Stateless">
<location>ANNOTATION</location>
</javaclass>
</when>
<perform>
<hint title="Stateless annotation can be replaced with scope" effort="1" category-id="potential">
<message>The Stateless EJBs can be converted to a cdi bean by replacing the `@Stateless` annotation with a scope eg `@ApplicationScoped`
</message>
<link title="Quarkus CDI reference" href="https://quarkus.io/guides/cdi-reference"/>
</hint>
</perform>
</rule>
</rules>
</ruleset>
Loading

0 comments on commit 31dd0b8

Please sign in to comment.