diff --git a/recipes/pom.xml b/recipes/pom.xml index e25283232b..40e22839ab 100644 --- a/recipes/pom.xml +++ b/recipes/pom.xml @@ -22,6 +22,15 @@ 4.46.0 5.40.6 + + 1.16.0 + + 5.4.0 + 3.1.0 + + 4.5.14 1.18.28 @@ -74,6 +83,12 @@ rewrite-java-17 provided + + org.openrewrite.recipe + rewrite-migrate-java + ${rewrite-migrate-java.version} + provided + @@ -103,6 +118,13 @@ provided + + + org.openrewrite + rewrite-maven + provided + + org.projectlombok @@ -117,6 +139,18 @@ rewrite-test test + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + @@ -187,6 +221,40 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + maven-surefire-plugin + ${surefire.plugin.version} + + + org.apache.maven.surefire + surefire-api + ${surefire.plugin.version} + + + + + ${settings.localRepository} + ${project.build.directory} + + + + - + \ No newline at end of file diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/AbstractCamelQuarkusJavaVisitor.java b/recipes/src/main/java/org/apache/camel/quarkus/update/AbstractCamelQuarkusJavaVisitor.java new file mode 100644 index 0000000000..07bcbb9585 --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/AbstractCamelQuarkusJavaVisitor.java @@ -0,0 +1,155 @@ +package org.apache.camel.quarkus.update; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Parent of Camel visitors, skips visit methods in case that there is no camel package imported. + *

+ * Every method visit* is marked as final and methods doVisit* are used instead. + *

+ *

+ * Simple cache for methodMatchers is implemented here. Usage: call MethodMatcher getMethodMatcher(String signature). + *

+ */ +public abstract class AbstractCamelQuarkusJavaVisitor extends JavaIsoVisitor { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCamelQuarkusJavaVisitor.class); + //flag that camel package is imported to the file + private boolean camel = false; + + private LinkedList implementsList = new LinkedList<>(); + + //There is no need to initialize all patterns at the class start. + //Map is a cache for created patterns + private static Map methodMatchers = new HashMap(); + + @Override + public final J.Import visitImport(J.Import _import, ExecutionContext context) { + //if there is at least one import of camel class, the camel recipe should be executed + if(_import.getTypeName().contains("org.apache.camel")) { + camel = true; + } + + if(!camel) { + //skip recipe if file does not contain camel + return _import; + } + + return executeVisitWithCatch(() -> doVisitImport(_import, context), _import, context); + } + + @Override + public final J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) { + if (classDecl.getImplements() != null && !classDecl.getImplements().isEmpty()) { + implementsList.addAll(classDecl.getImplements().stream().map(i -> i.getType()).collect(Collectors.toList())); + } + return executeVisitWithCatch(() -> doVisitClassDeclaration(classDecl, context), classDecl, context); + } + + @Override + public final J.FieldAccess visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext context) { + if(!camel) { + //skip recipe if file does not contain camel + return fieldAccess; + } + + return executeVisitWithCatch(() -> doVisitFieldAccess(fieldAccess, context), fieldAccess, context); + } + + @Override + public final J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext context) { + if(!camel) { + //skip recipe if file does not contain camel + return method; + } + + return executeVisitWithCatch(() -> doVisitMethodDeclaration(method, context), method, context); + } + + @Override + public final J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext context) { + if(!camel) { + //skip recipe if file does not contain camel + return method; + } + + return executeVisitWithCatch(() -> doVisitMethodInvocation(method, context), method, context); + } + + @Override + public final J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext context) { + if(!camel) { + //skip recipe if file does not contain camel + return annotation; + } + + return executeVisitWithCatch(() -> doVisitAnnotation(annotation, context), annotation, context); + } + + + //-------------------------------- internal methods used by children--------------------------------- + + protected J.Import doVisitImport(J.Import _import, ExecutionContext context) { + return super.visitImport(_import, context); + } + + protected J.ClassDeclaration doVisitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) { + return super.visitClassDeclaration(classDecl, context); + } + + protected J.FieldAccess doVisitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext context) { + return super.visitFieldAccess(fieldAccess, context); + } + + protected J.MethodDeclaration doVisitMethodDeclaration(J.MethodDeclaration method, ExecutionContext context) { + return super.visitMethodDeclaration(method, context); + } + + protected J.MethodInvocation doVisitMethodInvocation(J.MethodInvocation method, ExecutionContext context) { + return super.visitMethodInvocation(method, context); + } + + protected J.Annotation doVisitAnnotation(J.Annotation annotation, ExecutionContext context) { + return super.visitAnnotation(annotation, context); + } + + // ------------------------------------------ helper methods ------------------------------------------- + + protected LinkedList getImplementsList() { + return implementsList; + } + + // If the migration fails - do not fail whole migration process, only this one recipe + protected T executeVisitWithCatch(Supplier visitMethod, T origValue, ExecutionContext context) { + try { + return visitMethod.get(); + } catch (Exception e) { + LOGGER.warn(String.format("Internal error detected in %s, recipe is skipped.",context.getMessage("org.openrewrite.currentRecipe").getClass().getSimpleName()), e); + return origValue; + } + } + + protected MethodMatcher getMethodMatcher(String signature) { + synchronized (methodMatchers) { + MethodMatcher matcher = methodMatchers.get(signature); + + if (matcher == null) { + matcher = new MethodMatcher(signature); + methodMatchers.put(signature, matcher); + } + + return matcher; + } + } +} diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/RecipesUtil.java b/recipes/src/main/java/org/apache/camel/quarkus/update/RecipesUtil.java new file mode 100644 index 0000000000..7bdd9d4a9b --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/RecipesUtil.java @@ -0,0 +1,245 @@ +package org.apache.camel.quarkus.update; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Tree; +import org.openrewrite.java.tree.Comment; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JContainer; +import org.openrewrite.java.tree.JRightPadded; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.TextComment; +import org.openrewrite.marker.Markers; +import org.openrewrite.xml.tree.Xml; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; + +import static org.openrewrite.Tree.randomId; + +public class RecipesUtil { + + private static String CAMEL_PRESENT_KEY = RecipesUtil.class.getSimpleName(); + + //---------------- annotations helpers + + public static J.Annotation createAnnotation(J.Annotation annotation, String name, Function argMatcher, String args) { + + LinkedList originalArguments = annotation.getArguments() == null ? new LinkedList<>() : new LinkedList(annotation.getArguments()); + + String newArgName = args.replaceAll("=.*", "").trim(); + + //remove argument with the same name as the new one + if(argMatcher == null) { + originalArguments.add(new J.Empty(randomId(), Space.format(args), Markers.EMPTY)); + } else { + for (ListIterator iter = originalArguments.listIterator(); iter.hasNext(); ) { + Expression expr = iter.next(); + if (argMatcher.apply(expr.toString().replaceAll("\\s", ""))) { + iter.set(new J.Empty(randomId(), Space.format(args), Markers.EMPTY)); + } + } + } + + //construct arguments for the new annotation + List> newArgs = new LinkedList<>(); + for(Expression e: originalArguments) { + newArgs.add(new JRightPadded(e, Space.EMPTY, Markers.EMPTY)); + } + + J.Identifier newAnnotationIdentifier = new J.Identifier(randomId(), annotation.getPrefix(), Markers.EMPTY, name, + JavaType.ShallowClass.build("java.lang.Object"), null); + JContainer arguments = JContainer.build( + Space.EMPTY, + newArgs, + Markers.EMPTY); + return new J.Annotation(UUID.randomUUID(), annotation.getPrefix(), Markers.EMPTY, + newAnnotationIdentifier, arguments); + } + + public static Optional getValueOfArgs(List expressions, String parameter) { + if(expressions == null || expressions.isEmpty()) { + return Optional.empty(); + } + return expressions.stream() + .filter(e -> e.toString().replaceAll("\\s", "").startsWith(parameter + "=")) + .map(e -> e.toString().replaceAll("\\s", "").replaceFirst(parameter + "=", "")) + .findFirst(); + } + + //-------------- methods helping with comments ---- + + public static Comment createMultinlineComment(String text) { + return new TextComment(true, text, null, Markers.EMPTY); + } + public static Comment createComment(String text) { + return new TextComment(false, text, null, Markers.EMPTY); + } + public static Xml.Comment createXmlComment(String text) { + return new Xml.Comment(UUID.randomUUID(), null, Markers.EMPTY, text); + } + + //--------------- typeCast helper -------------------------------- + + public static J createTypeCast(Object type, Expression arg) { + return new J.TypeCast( + Tree.randomId(), + Space.EMPTY, + Markers.EMPTY, + RecipesUtil.createParentheses(type), + arg); + } + + // -------------------- other helper methods + + public static J.ControlParentheses createParentheses(T t) { + return new J.ControlParentheses( + Tree.randomId(), + Space.EMPTY, + Markers.EMPTY, + padRight(t)); + } + + public static J.Identifier createIdentifier(Space prefix, String name, String type) { + return new J.Identifier(randomId(), prefix, Markers.EMPTY, name, + JavaType.ShallowClass.build(type), null); + } + + private static JRightPadded padRight(T tree) { + return new JRightPadded<>(tree, Space.EMPTY, Markers.EMPTY); + } + + public static String getProperty(Cursor cursor) { + StringBuilder asProperty = new StringBuilder(); + Iterator path = cursor.getPath(); + int i = 0; + while (path.hasNext()) { + Object next = path.next(); + if (next instanceof Yaml.Mapping.Entry) { + Yaml.Mapping.Entry entry = (Yaml.Mapping.Entry) next; + if (i++ > 0) { + asProperty.insert(0, '.'); + } + asProperty.insert(0, entry.getKey().getValue()); + } + if (next instanceof Xml.Tag) { + Xml.Tag t = (Xml.Tag) next; + if (i++ > 0) { + asProperty.insert(0, '/'); + } + asProperty.insert(0, t.getName()); + } + } + return asProperty.toString(); + } + + public enum Category { + DATAMINING("datamining"), + AI("ai"), + API("api"), + AZURE("azure"), + BATCH("batch"), + BIGDATA("bigdata"), + BITCOIN("bitcoin"), + BLOCKCHAIN("blockchain"), + CACHE("cache"), + CHAT("chat"), + CLOUD("cloud"), + CLUSTERING("clustering"), + CMS("cms"), + COMPUTE("compute"), + COMPUTING("computing"), + CONTAINER("container"), + CORE("core"), + CRM("crm"), + DATA("data"), + DATABASE("database"), + DATAGRID("datagrid"), + DEEPLEARNING("deeplearning"), + DEPLOYMENT("deployment"), + DOCUMENT("document"), + ENDPOINT("endpoint"), + ENGINE("engine"), + EVENTBUS("eventbus"), + FILE("file"), + HADOOP("hadoop"), + HCM("hcm"), + HL7("hl7"), + HTTP("http"), + IOT("iot"), + IPFS("ipfs"), + JAVA("java"), + LDAP("ldap"), + LEDGER("ledger"), + LOCATION("location"), + LOG("log"), + MAIL("mail"), + MANAGEMENT("management"), + MESSAGING("messaging"), + MLLP("mllp"), + MOBILE("mobile"), + MONITORING("monitoring"), + NETWORKING("networking"), + NOSQL("nosql"), + OPENAPI("openapi"), + PAAS("paas"), + PAYMENT("payment"), + PLANNING("planning"), + PRINTING("printing"), + PROCESS("process"), + QUEUE("queue"), + REACTIVE("reactive"), + REPORTING("reporting"), + REST("rest"), + RPC("rpc"), + RSS("rss"), + SAP("sap"), + SCHEDULING("scheduling"), + SCRIPT("script"), + SEARCH("search"), + SECURITY("security"), + SERVERLESS("serverless"), + SHEETS("sheets"), + SOAP("soap"), + SOCIAL("social"), + SPRING("spring"), + SQL("sql"), + STREAMS("streams"), + SUPPORT("support"), + SWAGGER("swagger"), + SYSTEM("system"), + TCP("tcp"), + TESTING("testing"), + TRANSFORMATION("transformation"), + UDP("udp"), + VALIDATION("validation"), + VOIP("voip"), + WEBSERVICE("webservice"), + WEBSOCKET("websocket"), + WORKFLOW("workflow"); + + private final String value; + + Category(final String value) { + this.value = value; + } + + /** + * Returns the string representation of this value + * + * @return Returns the string representation of this value + */ + public String getValue() { + return this.value; + } + } + +} diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/CamelQuarkusMigrationRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/CamelQuarkusMigrationRecipe.java new file mode 100644 index 0000000000..3af9e34c06 --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/CamelQuarkusMigrationRecipe.java @@ -0,0 +1,136 @@ +package org.apache.camel.quarkus.update.v3_0; + +import org.apache.camel.quarkus.update.RecipesUtil; +import org.apache.camel.quarkus.update.v3_0.java.CamelAPIsRecipe; +import org.apache.camel.quarkus.update.v3_0.java.CamelBeanRecipe; +import org.apache.camel.quarkus.update.v3_0.java.CamelEIPRecipe; +import org.apache.camel.quarkus.update.v3_0.java.CamelHttpRecipe; +import org.apache.camel.quarkus.update.v3_0.maven.RemovedComponentsRecipe; +import org.apache.camel.quarkus.update.v3_0.properties.CamelQuarkusAPIsPropertiesRecipe; +import org.apache.camel.quarkus.update.v3_0.xml.XmlDslRecipe; +import org.apache.camel.quarkus.update.v3_0.yaml.CamelQuarkusYamlRouteConfigurationSequenceRecipe; +import org.apache.camel.quarkus.update.v3_0.yaml.CamelQuarkusYamlStepsInFromRecipe; +import org.jetbrains.annotations.NotNull; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.migrate.UpgradeJavaVersion; +import org.openrewrite.maven.MavenVisitor; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Root recipe of all camel-quarkus related recipes. + * + * 2 main functionalities: + *
    + *
  • + * All Camel-quarkus recipes are gathered here. + * See the constructor, recipes have to be registered via the doNext() method.
  • + *
  • + * Main recipe implements MavenVisitor for detection of camel-quarkus dependencies. + * If no camel-quarkus dependency is detected, no camel-quarkus recipe is executed + *
  • + *
+ * + */ +public class CamelQuarkusMigrationRecipe extends Recipe { + + //GroupId of camel-quarkus dependencies. + private final static String GROUP_ID = "org.apache.camel.quarkus"; + + private final static XPathMatcher DEPENDENCY_MATCHER = new XPathMatcher("//dependencies/dependency"); + + private final boolean skipCamelDetection; + + private final Collection recipes; + + public CamelQuarkusMigrationRecipe() { + this(false); + } + + //Test do not require the detection of camel-quarkus dependencies (in the majority of the cases) + //This package protected constructor is designed for the tests only. + CamelQuarkusMigrationRecipe(boolean skipCamelDetection) { + this.skipCamelDetection = skipCamelDetection; + this.recipes = Arrays.asList( + //pom recipes + new RemovedComponentsRecipe(), + //upgrade to J17 if camel-quarkus is present + new UpgradeJavaVersion(17), + //xml recipe + new XmlDslRecipe(), + //properties recipes + new CamelQuarkusAPIsPropertiesRecipe(), + //yaml recipes + new CamelQuarkusYamlRouteConfigurationSequenceRecipe(), + new CamelQuarkusYamlStepsInFromRecipe(), + //java recipes + new CamelAPIsRecipe(), + new CamelEIPRecipe(), + new CamelBeanRecipe(), + new CamelHttpRecipe() + ); + } + + @Override + public String getDisplayName() { + return "Recipe for the Camel-quarkus migration"; + } + + @Override + public String getDescription() { + return "Recipe for the Camel-quarkus migration. Takes care of Java, YAML, properties and maven automatic migration."; + } + + protected List visit(List before, ExecutionContext ctx) { + //if skipCamelDetection == true, there is no need to detect existence of Camel + //and all recipes could be registered + if(skipCamelDetection) { + recipes.forEach(r -> doNext(r)); + //return an empty visitor + return before; + } + + //detection of camel-dependency existence + MavenCamelQuarkusDetectorVisitor visitor = new MavenCamelQuarkusDetectorVisitor(); + before = ListUtils.map(before, (s) -> (SourceFile)visitor.visit(s, ctx)); + + //if camel-quarkus was detected, register recipes + if(visitor.camelPresent) { + recipes.forEach(r -> doNext(r)); + } + + return before; + } + + private class MavenCamelQuarkusDetectorVisitor extends MavenVisitor { + + private boolean camelPresent = false; + private MavenCamelQuarkusDetectorVisitor() { + } + + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx); + + if(camelPresent || !DEPENDENCY_MATCHER.matches(getCursor())) { + return t; + } + + if (GROUP_ID.equals(t.getChildValue("groupId").orElse(""))){ + camelPresent = true; + } + return t; + } + + } +} + diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelAPIsRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelAPIsRecipe.java new file mode 100644 index 0000000000..b08c1952fd --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelAPIsRecipe.java @@ -0,0 +1,352 @@ +package org.apache.camel.quarkus.update.v3_0.java; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.camel.quarkus.update.AbstractCamelQuarkusJavaVisitor; +import org.apache.camel.quarkus.update.RecipesUtil; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.AddImport; +import org.openrewrite.java.ChangePackage; +import org.openrewrite.java.ImplementInterface; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.RemoveImplements; +import org.openrewrite.java.tree.Comment; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.marker.Markers; + +import java.beans.SimpleBeanInfo; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * Recipe migrating changes between Camel 3.x to 4.x, for more details see the + * documentation. + */ +@EqualsAndHashCode(callSuper = true) +@Value +public class CamelAPIsRecipe extends Recipe { + + private static final String MATCHER_CONTEXT_GET_ENDPOINT_MAP = "org.apache.camel.CamelContext getEndpointMap()"; + private static final String MATCHER_CONTEXT_GET_EXT = "org.apache.camel.CamelContext getExtension(java.lang.Class)"; + private static final String MATCHER_GET_NAME_RESOLVER = "org.apache.camel.ExtendedCamelContext getComponentNameResolver()"; + private static final String M_PRODUCER_TEMPLATE_ASYNC_CALLBACK = "org.apache.camel.ProducerTemplate asyncCallback(..)"; + private static final String M_CONTEXT_ADAPT = "org.apache.camel.CamelContext adapt(java.lang.Class)"; + private static final String M_CONTEXT_SET_DUMP_ROUTES = "org.apache.camel.CamelContext setDumpRoutes(java.lang.Boolean)"; + private static final String M_CONTEXT_IS_DUMP_ROUTES = "org.apache.camel.CamelContext isDumpRoutes()"; + private static final String M_EXCHANGE_ADAPT = "org.apache.camel.Exchange adapt(java.lang.Class)"; + private static final String M_EXCHANGE_GET_PROPERTY = "org.apache.camel.Exchange getProperty(org.apache.camel.ExchangePropertyKey)"; + private static final String M_EXCHANGE_REMOVE_PROPERTY = "org.apache.camel.Exchange removeProperty(org.apache.camel.ExchangePropertyKey)"; + private static final String M_EXCHANGE_SET_PROPERTY = "org.apache.camel.Exchange setProperty(..)"; + private static final String M_CATALOG_ARCHETYPE_AS_XML = "org.apache.camel.catalog.CamelCatalog archetypeCatalogAsXml()"; + + @Override + public String getDisplayName() { + return "Camel API changes"; + } + + @Override + public String getDescription() { + return "Apache Camel API migration from version 3.20 or higher to 4.0. Removal of deprecated APIs."; + } + + @Override + public TreeVisitor getVisitor() { + + return new AbstractCamelQuarkusJavaVisitor() { + + //Cache for all methodInvocations CamelContext adapt(java.lang.Class). + private Map adaptCache = new HashMap<>(); + + @Override + protected J.Import doVisitImport(J.Import _import, ExecutionContext context) { + J.Import im = super.doVisitImport(_import, context); + + //Removed Discard and DiscardOldest from org.apache.camel.util.concurrent.ThreadPoolRejectedPolicy. + if(im.isStatic() && im.getTypeName().equals("org.apache.camel.util.concurrent.ThreadPoolRejectedPolicy") + && im.getQualid() != null + && ("Discard".equals(im.getQualid().getSimpleName()) || "DiscardOldest".equals(im.getQualid().getSimpleName()))) { + Comment comment = RecipesUtil.createMultinlineComment(String.format("'ThreadPoolRejectedPolicy.%s' has been removed, consider using 'ThreadPoolRejectedPolicy.Abort'.", im.getQualid().getSimpleName())); + im = im.withComments(Collections.singletonList(comment)); + + } + //Removed org.apache.camel.builder.SimpleBuilder. + // Was mostly used internally in Camel with the Java DSL in some situations. + else if("org.apache.camel.builder.SimpleBuilder".equals(im.getTypeName())) { + Comment comment = RecipesUtil.createMultinlineComment(String.format("'%s' has been removed, (class was used internally).", SimpleBeanInfo.class.getCanonicalName())); + im = im.withComments(Collections.singletonList(comment)); + + } + //Moved org.apache.camel.support.IntrospectionSupport to camel-core-engine for internal use only. + // End users should use org.apache.camel.spi.BeanInspection instead. + // Moved from `org.apache.camel.support` to `org.apache.camel.impl.engine` + else if("org.apache.camel.support.IntrospectionSupport".equals(im.getTypeName())) { + maybeRemoveImport(im.getTypeName()); + String newImportName = im.getQualid() == null ? im.getTypeName() : im.getTypeName() /*+ "." + im.getQualid().getSimpleName()*/; + newImportName = newImportName.replaceAll(".support.", ".impl.engine."); + if(im.isStatic() && im.getQualid() != null) { + maybeAddImport(newImportName, im.getQualid().getSimpleName(), false); + } else { + maybeAddImport(newImportName, null, false); + } + } + + //Move the following class from org.apache.camel.api.management.mbean.BacklogTracerEventMessage in camel-management-api JAR to org.apache.camel.spi.BacklogTracerEventMessage in camel-api JAR. + // + // BacklogTracerEventMessage moved from `org.apache.camel.api.management.mbean.BacklogTracerEventMessage` + // to `org.apache.camel.spi.BacklogTracerEventMessage` + doAfterVisit( + new ChangePackage("org.apache.camel.api.management.mbean.BacklogTracerEventMessage", + "org.apache.camel.spi.BacklogTracerEventMessage", null)); + + return im; + } + + @Override + protected J.ClassDeclaration doVisitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) { + J.ClassDeclaration cd = super.doVisitClassDeclaration(classDecl, context); + + //Removed org.apache.camel.spi.OnCamelContextStart. Use org.apache.camel.spi.OnCamelContextStarting instead. + if(cd.getImplements() != null && cd.getImplements().stream() + .anyMatch(f -> TypeUtils.isOfClassType(f.getType(), "org.apache.camel.spi.OnCamelContextStart"))) { + + doAfterVisit(new ImplementInterface(cd, "org.apache.camel.spi.OnCamelContextStarting")); + doAfterVisit(new RemoveImplements("org.apache.camel.spi.OnCamelContextStart", null)); + + } //Removed org.apache.camel.spi.OnCamelContextStop. Use org.apache.camel.spi.OnCamelContextStopping instead. + else if(cd.getImplements() != null && cd.getImplements().stream() + .anyMatch(f -> TypeUtils.isOfClassType(f.getType(), "org.apache.camel.spi.OnCamelContextStop"))) { + + doAfterVisit(new ImplementInterface(cd, "org.apache.camel.spi.OnCamelContextStopping")); + doAfterVisit(new RemoveImplements("org.apache.camel.spi.OnCamelContextStop", null)); + + } + return cd; + } + + @Override + protected J.FieldAccess doVisitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext context) { + J.FieldAccess fa = super.doVisitFieldAccess(fieldAccess, context); + //The org.apache.camel.ExchangePattern has removed InOptionalOut. + if("InOptionalOut".equals(fieldAccess.getSimpleName()) && fa.getType() != null && fa.getType().isAssignableFrom(Pattern.compile("org.apache.camel.ExchangePattern"))) { + return fa.withName(new J.Identifier(UUID.randomUUID(), fa.getPrefix(), Markers.EMPTY, "/* " + fa.getSimpleName() + " has been removed */", fa.getType(), null)); + } + + else if(("Discard".equals(fa.getSimpleName()) || "DiscardOldest".equals(fa.getSimpleName())) + && fa.getType() != null && fa.getType().isAssignableFrom(Pattern.compile("org.apache.camel.util.concurrent.ThreadPoolRejectedPolicy")) + ) { + Comment comment = RecipesUtil.createMultinlineComment(String.format("'ThreadPoolRejectedPolicy.%s' has been removed, consider using 'ThreadPoolRejectedPolicy.Abort'.", fa.getSimpleName())); + fa = fa.withComments(Collections.singletonList(comment)); + + } + + + return fa; + } + + @Override + protected J.MethodDeclaration doVisitMethodDeclaration(J.MethodDeclaration method, ExecutionContext context) { + J.MethodDeclaration md = super.doVisitMethodDeclaration(method, context); + + //Method 'configure' was removed from `org.apache.camel.main.MainListener`, consider using 'beforeConfigure' or 'afterConfigure'. + if("configure".equals(md.getSimpleName()) + && md.getReturnTypeExpression().getType().equals(JavaType.Primitive.Void) + && getImplementsList().stream().anyMatch(jt -> "org.apache.camel.main.MainListener".equals(jt.toString())) + && !md.getParameters().isEmpty() + && md.getParameters().size() == 1 + && md.getParameters().get(0) instanceof J.VariableDeclarations + && ((J.VariableDeclarations)md.getParameters().get(0)).getType().isAssignableFrom(Pattern.compile("org.apache.camel.CamelContext"))) { + Comment comment = RecipesUtil.createMultinlineComment(String.format(" Method '%s' was removed from `%s`, consider using 'beforeConfigure' or 'afterConfigure'. ", md.getSimpleName(), "org.apache.camel.main.MainListener")); + md = md.withComments(Collections.singletonList(comment)); + } + + return md; + } + + @Override + protected J.Annotation doVisitAnnotation(J.Annotation annotation, ExecutionContext context) { + J.Annotation a = super.doVisitAnnotation(annotation, context); + + //Removed @FallbackConverter as you should use @Converter(fallback = true) instead. + if (a.getType().toString().equals("org.apache.camel.FallbackConverter")) { + maybeAddImport("org.apache.camel.Converter", null, false); + maybeRemoveImport("org.apache.camel.FallbackConverter"); + + return RecipesUtil.createAnnotation(annotation, "Converter", null, "fallback = true"); + } + //Removed uri attribute on @EndpointInject, @Produce, and @Consume as you should use value (default) instead. + //For example @Produce(uri = "kafka:cheese") should be changed to @Produce("kafka:cheese") + else if (a.getType().toString().equals("org.apache.camel.EndpointInject")) { + Optional originalValue = RecipesUtil.getValueOfArgs(a.getArguments(), "uri"); + if(originalValue.isPresent()) { + return RecipesUtil.createAnnotation(annotation, "EndpointInject", s -> s.startsWith("uri="), originalValue.get()); + } + } + //Removed uri attribute on @EndpointInject, @Produce, and @Consume as you should use value (default) instead. + //For example @Produce(uri = "kafka:cheese") should be changed to @Produce("kafka:cheese") + else if (a.getType().toString().equals("org.apache.camel.Produce")) { + Optional originalValue = RecipesUtil.getValueOfArgs(a.getArguments(), "uri"); + if(originalValue.isPresent()) { + return RecipesUtil.createAnnotation(annotation, "Produce", s -> s.startsWith("uri="), originalValue.get()); + } + } + //Removed uri attribute on @EndpointInject, @Produce, and @Consume as you should use value (default) instead. + //For example @Produce(uri = "kafka:cheese") should be changed to @Produce("kafka:cheese") + else if (a.getType().toString().equals("org.apache.camel.Consume")) { + Optional originalValue = RecipesUtil.getValueOfArgs(a.getArguments(), "uri"); + if(originalValue.isPresent()) { + return RecipesUtil.createAnnotation(annotation, "Consume", s -> s.startsWith("uri="), originalValue.get()); + } + } + // Removed label on @UriEndpoint as you should use category instead. + else if (a.getType().toString().equals("org.apache.camel.spi.UriEndpoint")) { + + Optional originalValue = RecipesUtil.getValueOfArgs(a.getArguments(), "label"); + if(originalValue.isPresent()) { + maybeAddImport("org.apache.camel.Category", null, false); + + String newValue; + try { + newValue = RecipesUtil.Category.valueOf(originalValue.get().toUpperCase().replaceAll("\"", "")).getValue(); + } catch(IllegalArgumentException e) { + newValue = originalValue.get() + "/*unknown_value*/"; + } + + return RecipesUtil.createAnnotation(annotation, "UriEndpoint", s -> s.startsWith("label="), "category = {Category." + newValue + "}"); + } + } + + return a; + } + + @Override + protected J.MethodInvocation doVisitMethodInvocation(J.MethodInvocation method, ExecutionContext context) { + J.MethodInvocation mi = super.doVisitMethodInvocation(method, context); + + //if adapt method invocation is used as a select for another method invocation, it is replaced + if(mi.getSelect() != null && adaptCache.containsKey(mi.getSelect().getId())) { + getCursor().putMessage("adapt_cast", mi.getSelect().getId()); + } else + // context.getExtension(ExtendedCamelContext.class).getComponentNameResolver() -> PluginHelper.getComponentNameResolver(context) + if (getMethodMatcher(MATCHER_CONTEXT_GET_ENDPOINT_MAP).matches(mi)) { + mi = mi.withName(new J.Identifier(UUID.randomUUID(), mi.getPrefix(), Markers.EMPTY, + "/* " + mi.getSimpleName() + " has been removed, consider getEndpointRegistry() instead */", mi.getType(), null)); + } + // ProducerTemplate.asyncCallback() has been replaced by 'asyncSend(') or 'asyncRequest()' + else if(getMethodMatcher(M_PRODUCER_TEMPLATE_ASYNC_CALLBACK).matches(mi)) { + Comment comment = RecipesUtil.createMultinlineComment(String.format(" Method '%s()' has been replaced by 'asyncSend()' or 'asyncRequest()'.", mi.getSimpleName())); + mi = mi.withComments(Collections.singletonList(comment)); + } + //context.adapt(ModelCamelContext.class) -> ((ModelCamelContext) context) + else if (getMethodMatcher(M_CONTEXT_ADAPT).matches(mi)) { + if (mi.getType().isAssignableFrom(Pattern.compile("org.apache.camel.model.ModelCamelContext"))) { + J.Identifier type = RecipesUtil.createIdentifier(mi.getPrefix(), "ModelCamelContext", "java.lang.Object"); + J.ControlParentheses cp = RecipesUtil.createParentheses(RecipesUtil.createTypeCast(type, mi.getSelect())); + //put the type cast into cache in case it is replaced lately + mi = mi.withComments(Collections.singletonList(RecipesUtil.createMultinlineComment("Method 'adapt' was removed."))); + adaptCache.put(method.getId(), cp); + } else if (mi.getType().isAssignableFrom(Pattern.compile("org.apache.camel.ExtendedCamelContext"))) { + mi = mi.withName(mi.getName().withSimpleName("getCamelContextExtension")).withArguments(Collections.emptyList()); + maybeRemoveImport("org.apache.camel.ExtendedCamelContext"); + } + } + //exchange.adapt(ExtendedExchange.class) -> exchange.getExchangeExtension() + else if (getMethodMatcher(M_EXCHANGE_ADAPT).matches(mi) + && mi.getType().isAssignableFrom(Pattern.compile("org.apache.camel.ExtendedExchange"))) { + mi = mi.withName(mi.getName().withSimpleName("getExchangeExtension")).withArguments(Collections.emptyList()); + maybeRemoveImport("org.apache.camel.ExtendedExchange"); + } + //newExchange.getProperty(ExchangePropertyKey.FAILURE_HANDLED) -> newExchange.getExchangeExtension().isFailureHandled() + else if(getMethodMatcher(M_EXCHANGE_GET_PROPERTY).matches(mi) + && mi.getArguments().get(0).toString().endsWith("FAILURE_HANDLED")) { + mi = mi.withName(mi.getName().withSimpleName("getExchangeExtension().isFailureHandled")).withArguments(Collections.emptyList()); + maybeRemoveImport("org.apache.camel.ExchangePropertyKey"); + } + //exchange.removeProperty(ExchangePropertyKey.FAILURE_HANDLED); -> exchange.getExchangeExtension().setFailureHandled(false); + else if(getMethodMatcher(M_EXCHANGE_REMOVE_PROPERTY).matches(mi) + && mi.getArguments().get(0).toString().endsWith("FAILURE_HANDLED")) { + mi = mi.withName(mi.getName().withSimpleName("getExchangeExtension().setFailureHandled")).withArguments(Collections.singletonList(RecipesUtil.createIdentifier(Space.EMPTY, "false", "java.lang.Boolean"))); + maybeRemoveImport("org.apache.camel.ExchangePropertyKey"); + } + //exchange.setProperty(ExchangePropertyKey.FAILURE_HANDLED, failureHandled); -> exchange.getExchangeExtension().setFailureHandled(failureHandled); + else if(getMethodMatcher(M_EXCHANGE_SET_PROPERTY).matches(mi) + && mi.getArguments().get(0).toString().endsWith("FAILURE_HANDLED")) { + mi = mi.withName(mi.getName() + .withSimpleName("getExchangeExtension().setFailureHandled")) + .withArguments(Collections.singletonList(mi.getArguments().get(1).withPrefix(Space.EMPTY))); + maybeRemoveImport("org.apache.camel.ExchangePropertyKey"); + } + //'org.apache.camel.catalogCamelCatalog.archetypeCatalogAsXml()` has been removed + else if(getMethodMatcher(M_CATALOG_ARCHETYPE_AS_XML).matches(mi)) { + mi = mi.withComments(Collections.singletonList(RecipesUtil.createMultinlineComment(" Method '" + mi.getSimpleName() + "' has been removed. "))); + } + //context().setDumpRoutes(true); -> context().setDumpRoutes("xml");(or "yaml") + else if(getMethodMatcher(M_CONTEXT_SET_DUMP_ROUTES).matches(mi)) { + mi = mi.withComments(Collections.singletonList(RecipesUtil.createMultinlineComment(" Method '" + mi.getSimpleName() + "' accepts String parameter ('xml' or 'yaml' or 'false'). "))); + } + //Boolean isDumpRoutes(); -> getDumpRoutes(); with returned type String + else if(getMethodMatcher(M_CONTEXT_IS_DUMP_ROUTES).matches(mi)) { + mi = mi.withName(mi.getName().withSimpleName("getDumpRoutes")).withComments(Collections.singletonList(RecipesUtil.createMultinlineComment(" Method 'getDumpRoutes' returns String value ('xml' or 'yaml' or 'false'). "))); + } + // context.getExtension(ExtendedCamelContext.class).getComponentNameResolver() -> PluginHelper.getComponentNameResolver(context) + else if (getMethodMatcher(MATCHER_GET_NAME_RESOLVER).matches(mi)) { + if (mi.getSelect() instanceof J.MethodInvocation && getMethodMatcher(MATCHER_CONTEXT_GET_EXT).matches(((J.MethodInvocation) mi.getSelect()).getMethodType())) { + J.MethodInvocation innerInvocation = (J.MethodInvocation) mi.getSelect(); + mi = mi.withTemplate(JavaTemplate.builder(() -> getCursor().getParentOrThrow(), "PluginHelper.getComponentNameResolver(#{any(org.apache.camel.CamelContext)})") + .build(), + mi.getCoordinates().replace(), innerInvocation.getSelect()); + doAfterVisit(new AddImport<>("org.apache.camel.support.PluginHelper", null, false)); + } + } + // (CamelRuntimeCatalog) context.getExtension(RuntimeCamelCatalog.class) -> context.getCamelContextExtension().getContextPlugin(RuntimeCamelCatalog.class); + else if (getMethodMatcher(MATCHER_CONTEXT_GET_EXT).matches(mi)) { + + mi = mi.withName(mi.getName().withSimpleName("getCamelContextExtension().getContextPlugin")) + .withMethodType(mi.getMethodType()); + //remove type cast before expression + if(getCursor().getParent().getValue() instanceof J.TypeCast && ((J.TypeCast)getCursor().getParent().getValue()).getType().equals(mi.getType())) { + getCursor().getParent().putMessage("remove_type_cast", mi); + } + + } + return mi; + } + + @Override + public @Nullable J postVisit(J tree, ExecutionContext context) { + J j = super.postVisit(tree, context); + + UUID adaptCast = getCursor().getMessage("adapt_cast"); + + if(adaptCast != null) { + J.MethodInvocation mi = (J.MethodInvocation)j; + J.ControlParentheses cp = (J.ControlParentheses) adaptCache.get(adaptCast); + + J.MethodInvocation m = mi.withSelect(cp); + return m; + } + + J removeTypeCast = getCursor().getMessage("remove_type_cast"); + + if(removeTypeCast != null) { + return removeTypeCast; + } + + return j; + } + + }; + } + +} + diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelBeanRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelBeanRecipe.java new file mode 100644 index 0000000000..de5afec499 --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelBeanRecipe.java @@ -0,0 +1,142 @@ +package org.apache.camel.quarkus.update.v3_0.java; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.camel.quarkus.update.AbstractCamelQuarkusJavaVisitor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.ChangeLiteral; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; + +public class CamelBeanRecipe extends Recipe { + + private final String primitive[] = new String[] { "byte", "short", "int", "float", "double", "long", "char", + "String" }; + + @Override + public String getDisplayName() { + return "Camel bean recipe"; + } + + @Override + public String getDescription() { + return "Camel bean recipe."; + } + + @Override + public TreeVisitor getVisitor() { + return new AbstractCamelQuarkusJavaVisitor() { + + @Override + protected J.MethodInvocation doVisitMethodInvocation(J.MethodInvocation method, ExecutionContext context) { + J.MethodInvocation mi = super.doVisitMethodInvocation(method, context); + Pattern findMethodPattern = Pattern.compile("method=.*"); + + if (mi.getSimpleName().equals("to")) { + List arguments = method.getArguments(); + + for (int i = 0; i < arguments.size(); i++) { + Expression argument = arguments.get(i); + if (argument instanceof J.Literal + && ((J.Literal) argument).getType().getClassName().equals("java.lang.String") + && findMethodPattern + .matcher((String) (((J.Literal) method.getArguments().get(i)).getValue())) + .find()) { + + String uriWithMethod = (String) (((J.Literal) method.getArguments().get(i)).getValue()); + + String uriWithoutMethod = uriWithMethod.split("=")[0]; + + String methodNameAndArgs = uriWithMethod.split("=")[1]; + + //method without any args, we can simply return the mi in that case. + if(!methodNameAndArgs.contains("(") && !methodNameAndArgs.contains(")")) { + return mi; + } + + String methodName = extractMethodName(methodNameAndArgs); + + String actualArgs = methodNameAndArgs.substring( + methodNameAndArgs.indexOf("(") + 1, + methodNameAndArgs.indexOf(")")); + + String updatedArg = uriWithoutMethod + "=" + methodName + "(" + updateMethodArgument(actualArgs) + + ")"; + + doAfterVisit(new ChangeLiteral<>(argument, p -> updatedArg)); + + return mi; + + } + + } + + } + + return mi; + } + + }; + + }; + + private String extractMethodName(String methodCallString) { + // Regular expression to match the method call pattern + Pattern pattern = Pattern.compile("^([a-zA-Z_$][a-zA-Z0-9_$]*)\\(.+\\)$"); + Matcher matcher = pattern.matcher(methodCallString); + + // Check if the string matches the method call pattern + if (matcher.matches()) { + // Extract the method name from the matched group + String methodName = matcher.group(1); + return methodName; + } else { + // Return null if the string doesn't match the method call pattern + return null; + } + } + + private String updateMethodArgument(String argument) { + + Pattern identifierPattern = Pattern.compile("^[a-zA-Z_$][a-zA-Z0-9_$]*$"); + Pattern fullyQualifiedPattern = Pattern + .compile("^([a-zA-Z_$][a-zA-Z0-9_$]*\\.)*[a-zA-Z_$][a-zA-Z0-9_$]*$"); + + String updatedArgs = Arrays.asList(argument.split(",")).stream().map(arg -> { + if (arg.endsWith(".class")) { + return arg; + } + + if (Arrays.asList(primitive).contains(arg.trim())) { + return arg + ".class"; + } + + Matcher fullyQualifiedMatcher = fullyQualifiedPattern.matcher(arg); + if (!fullyQualifiedMatcher.matches()) { + return arg; + } + + String[] parts = arg.split("\\."); + + for (String part : parts) { + Matcher identifierMatcher = identifierPattern.matcher(part); + if (!identifierMatcher.matches()) { + return arg; + } + } + + return arg + ".class"; + + }).collect(Collectors.joining(",")); + + return updatedArgs; + + } + +} diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelEIPRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelEIPRecipe.java new file mode 100644 index 0000000000..ae1093ac10 --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelEIPRecipe.java @@ -0,0 +1,42 @@ +package org.apache.camel.quarkus.update.v3_0.java; + +import org.apache.camel.quarkus.update.AbstractCamelQuarkusJavaVisitor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AddImport; +import org.openrewrite.java.tree.J; + +public class CamelEIPRecipe extends Recipe { + + @Override + public String getDisplayName() { + return "Replaces removed method camel EIP"; + } + + @Override + public String getDescription() { + return "The InOnly and InOut EIPs have been removed. Instead, use 'SetExchangePattern' or 'To' where you can specify the exchange pattern to use."; + } + + @Override + public TreeVisitor getVisitor() { + return new AbstractCamelQuarkusJavaVisitor() { + + @Override + protected J.MethodInvocation doVisitMethodInvocation(J.MethodInvocation method, ExecutionContext context) { + J.MethodInvocation mi = super.doVisitMethodInvocation(method, context); + + if (mi.getSimpleName().equals("inOut") || mi.getSimpleName().equals("inOnly")) { + String name = mi.getSimpleName().substring(0, 1).toUpperCase() + mi.getSimpleName().substring(1); + mi = mi.withName(mi.getName().withSimpleName("setExchangePattern(ExchangePattern."+name+").to")); + doAfterVisit(new AddImport<>("org.apache.camel.ExchangePattern", null, false)); + } + return mi; + } + + }; + + }; + +} diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelHttpRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelHttpRecipe.java new file mode 100644 index 0000000000..5040032a2a --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/java/CamelHttpRecipe.java @@ -0,0 +1,87 @@ +package org.apache.camel.quarkus.update.v3_0.java; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.camel.quarkus.update.AbstractCamelQuarkusJavaVisitor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.ChangePackage; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.J; + +@EqualsAndHashCode(callSuper = true) +@Value +public class CamelHttpRecipe extends Recipe { + + private static final String SET_CREDENTIALS = "org.apache.http.impl.client.BasicCredentialsProvider setCredentials(..)"; + private static final String SCOPE_ANY = "AuthScope.ANY"; + + @Override + public String getDisplayName() { + return "Camel Http Extension changes"; + } + + @Override + public String getDescription() { + return "Camel Http Extension changes."; + } + + @Override + public TreeVisitor getVisitor() { + + return new AbstractCamelQuarkusJavaVisitor() { + @Override + protected J.Import doVisitImport(J.Import _import, ExecutionContext context) { + doAfterVisit( new ChangePackage("org.apache.http.HttpHost", "org.apache.hc.core5.http.HttpHost", null)); + doAfterVisit( new ChangePackage("org.apache.http.client.protocol.HttpClientContext", "org.apache.hc.client5.http.protocol.HttpClientContext", null)); + doAfterVisit( new ChangePackage("org.apache.http.impl.client", "org.apache.hc.client5.http.impl.auth", null)); + doAfterVisit( new ChangePackage("org.apache.http.protocol.HttpContext", "org.apache.hc.core5.http.protocol.HttpContext", null)); + doAfterVisit( new ChangePackage("org.apache.http", "org.apache.hc.client5.http", null)); + + return super.doVisitImport(_import, context); + } + + @Override + protected J.FieldAccess doVisitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext context) { + J.FieldAccess f = super.doVisitFieldAccess(fieldAccess, context); + + //The component has been upgraded to use Apache HttpComponents v5 + //AuthScope.ANY -> new AuthScope(null, -1) + if("ANY".equals(f.getSimpleName()) && "org.apache.http.auth.AuthScope".equals(f.getType().toString())) { + JavaTemplate.Builder templateBuilder = JavaTemplate.builder(this::getCursor, "new AuthScope(null, -1)") + .imports("org.apache.hc.client5.http.auth.AuthScope"); + J.NewClass nc = f.withTemplate( + templateBuilder.build(), + f.getCoordinates().replace()) + .withPrefix(f.getPrefix() + ); + getCursor().putMessage("authScopeNewClass", nc); + } + return f; + } + + @Override + public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext context) { + return super.visitNewClass(newClass, context); + } + + + @Override + public @Nullable J postVisit(J tree, ExecutionContext context) { + J j = super.postVisit(tree, context); + + //use a new class instead of original element + J.NewClass newClass = getCursor().getMessage("authScopeNewClass"); + if(newClass != null) { + return newClass; + } + + return j; + } + + }; + } + +} \ No newline at end of file diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/maven/RemovedComponentsRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/maven/RemovedComponentsRecipe.java new file mode 100644 index 0000000000..5253801beb --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/maven/RemovedComponentsRecipe.java @@ -0,0 +1,126 @@ +package org.apache.camel.quarkus.update.v3_0.maven; + +import org.apache.camel.quarkus.update.RecipesUtil; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.MavenVisitor; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Several camel/CQ extensions were removed. + * All of such dependencies are commented-out with a comment about removal. In case that different extension is suggested as a reoplacement, + * comment suggest that. + * TODO usu official links + * See https://camel.apache.org/manual/camel-4-migration-guide.html#_removed_components and https://github.com/apache/camel-quarkus/blob/main/docs/modules/ROOT/pages/migration-guide/3.0.0.adoc#removed-extensions + */ +public class RemovedComponentsRecipe extends Recipe { + + private static String GROUP_ID = "org.apache.camel.quarkus"; + private static Set ARTIFACT_IDS = new HashSet<>(Arrays.asList( + "camel-quarkus-activemq", + "camel-quarkus-atmos", + "camel-quarkus-avro-rpc", + "camel-quarkus-caffeine-lrucache", + "camel-quarkus-datasonnet", + "camel-quarkus-dozer", + "camel-quarkus-elasticsearch-rest", + "camel-quarkus-gora", + "camel-quarkus-hbase", + "camel-quarkus-iota", + "camel-quarkus-jbpm", + "camel-quarkus-jclouds", + "camel-quarkus-johnzon", + "camel-quarkus-microprofile-metrics", + "camel-quarkus-milo", + "camel-quarkus-opentracing", + "camel-quarkus-optaplanner", + "camel-quarkus-rabbitmq", + "camel-quarkus-smallrye-reactive-messaging", + "camel-quarkus-solr", + "camel-quarkus-tika", + "camel-quarkus-vm", + "camel-quarkus-xmlsecurity", + "camel-quarkus-xstream")); + private static Map> ALTERNATIVE_COMPONENTS = Stream.of(new String[][] { + { "camel-quarkus-activemq", "camel-quarkus-jms", "camel-quarkus-sjms", "camel-quarkus-amqp" }, + { "camel-quarkus-caffeine-lrucache", "camel-quarkus-ignite", "camel-quarkus-infinispan" }, + { "camel-quarkus-johnzon", "camel-quarkus-jackson", "camel-quarkus-fastjson", "camel-quarkus-gson" }, + { "camel-quarkus-microprofile-metrics", "camel-quarkus-opentelemetry", "camel-quarkus-micrometer" }, + { "camel-quarkus-opentracing", "camel-quarkus-opentelemetry", "camel-quarkus-micrometer" }, + { "camel-quarkus-rabbitmq", "camel-quarkus-spring-rabbitmq???" }, + { "camel-quarkus-xstream", "camel-quarkus-jacksonxml" }, + }).collect(Collectors.toMap(data -> data[0], data -> Arrays.asList(Arrays.copyOfRange(data, 1, data.length)))); + + private static Set TO_BE_REINTRODUCED = new HashSet<>(Arrays.asList( + "camel-quarkus-datasonnet", + "camel-quarkus-smallrye-reactive-messaging", + "camel-quarkus-tika", + "camel-quarkus-xmlsecurity")); + + private final static XPathMatcher DEPENDENCY_MATCHER = new XPathMatcher("//dependencies/dependency"); + + @Override + public String getDisplayName() { + return "Removed Camel components."; + } + + @Override + public String getDescription() { + return "Removed Camel components."; + } + + + @Override + public TreeVisitor getVisitor() { + return new MavenVisitor<>() { + + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx); + + if(!DEPENDENCY_MATCHER.matches(getCursor())) { + return t; + } + + if (GROUP_ID.equals(t.getChildValue("groupId").orElse(null))) { + String artifactdD = t.getChildValue("artifactId").orElse(null); + if(artifactdD != null && ARTIFACT_IDS.contains(artifactdD)) { + //add a comment to the artifactId + LinkedList l = new LinkedList(t.getContent()); + l.addFirst(RecipesUtil.createXmlComment(commentToRemovedArtifactId(artifactdD, t.toString()))); + + return RecipesUtil.createXmlComment(commentToRemovedArtifactId(artifactdD, t.print(getCursor()))).withPrefix(t.getPrefix()); + } + } + return t; + } + + @Override + protected void doAfterVisit(Recipe recipe) { + super.doAfterVisit(recipe); + } + + private String commentToRemovedArtifactId(String artifactId, String theDefinition) { + if (ALTERNATIVE_COMPONENTS.containsKey(artifactId)) { + return String.format("Extension %s was removed, consider %s instead. %s", artifactId, ALTERNATIVE_COMPONENTS.get(artifactId).stream().collect(Collectors.joining(" or ")), theDefinition); + } else if (TO_BE_REINTRODUCED.contains(artifactId)) { + return String.format("Extension %s was removed, but should be reintroduced. %s", artifactId, theDefinition); + } else { + return String.format("Extension %s was removed. %s", artifactId, theDefinition); + } + } + + }; + } +} diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/properties/CamelQuarkusAPIsPropertiesRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/properties/CamelQuarkusAPIsPropertiesRecipe.java new file mode 100644 index 0000000000..842f87d9b2 --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/properties/CamelQuarkusAPIsPropertiesRecipe.java @@ -0,0 +1,54 @@ +package org.apache.camel.quarkus.update.v3_0.properties; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.camel.quarkus.update.RecipesUtil; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.properties.PropertiesIsoVisitor; +import org.openrewrite.properties.tree.Properties; + +/** + * Part of the API changes is - removed Discard and DiscardOldest from org.apache.camel.util.concurrent.ThreadPoolRejectedPolicy. + *

+ * Bith options could be used as a proprtty in application.properties. + *

+ */ +@EqualsAndHashCode(callSuper = true) +@Value +public class CamelQuarkusAPIsPropertiesRecipe extends Recipe { + + @Override + public String getDisplayName() { + return "Camel API changes in application.properties"; + } + + @Override + public String getDescription() { + return "Apache Camel API migration from version 3.20 or higher to 4.0. Removal of deprecated APIs, which could be part of the application.properties."; + } + + @Override + public PropertiesIsoVisitor getVisitor() { + + return new PropertiesIsoVisitor() { + + + @Override + public Properties.Entry visitEntry(Properties.Entry entry, ExecutionContext context) { + Properties.Entry e = super.visitEntry(entry, context); + + if("camel.threadpool.rejectedPolicy".equals(e.getKey()) && + ("DiscardOldest".equals(e.getValue().getText()) || "Discard".equals(e.getValue().getText()))) { + return e.withPrefix(String.format("\n#'ThreadPoolRejectedPolicy.%s' has been removed, consider using 'Abort'. ", e.getKey())); + } + + return e; + } + + + }; + } + +} + diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/xml/XmlDslRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/xml/XmlDslRecipe.java new file mode 100644 index 0000000000..bc947cbcdb --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/xml/XmlDslRecipe.java @@ -0,0 +1,83 @@ +package org.apache.camel.quarkus.update.v3_0.xml; + +import org.apache.camel.quarkus.update.RecipesUtil; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.marker.Markers; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.XmlIsoVisitor; +import org.openrewrite.xml.tree.Xml; + +/** + *

+ * Camel Migration guide + *

+ * The to set a description on a route or node, has been changed from an element to an attribute. + * + * Before: + *
+ * <route id="myRoute">
+ *   <description>Something that this route do</description>
+ *   <from uri="kafka:cheese"/>
+ *   ...
+ * </route>
+ * 
+ * After: + *
+ * <route id="myRoute" description="Something that this route do">
+ *   <from uri="kafka:cheese"/>
+ *   ...
+ * </route>
+ * 
+ */ +public class XmlDslRecipe extends Recipe { + + private static final XPathMatcher ROUTE_DESCRIPTION_XPATH_MATCHER = new XPathMatcher("/routes/route/description"); + private static final XPathMatcher ROUTE_XPATH_MATCHER = new XPathMatcher("/routes/route"); + + @Override + public String getDisplayName() { + return "Camel XMl DSL changes"; + } + + @Override + public String getDescription() { + return "Apache Camel XML DSL migration from version 3.20 or higher to 4.0."; + } + + @Override + public TreeVisitor getVisitor() { + return new XmlIsoVisitor<>() { + + @Override + public Xml.Tag visitTag(final Xml.Tag tag, final ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + + + if (ROUTE_XPATH_MATCHER.matches(getCursor())) { + String d = ctx.pollMessage("description"); + if(d != null) { + return t.withAttributes(ListUtils.concat(t.getAttributes(), autoFormat(new Xml.Attribute(Tree.randomId(), "", Markers.EMPTY, + new Xml.Ident(Tree.randomId(), "", Markers.EMPTY, "description"), + "", + autoFormat(new Xml.Attribute.Value(Tree.randomId(), "", Markers.EMPTY, + Xml.Attribute.Value.Quote.Double, + d), ctx)), ctx))); + } + } + if (ROUTE_DESCRIPTION_XPATH_MATCHER.matches(getCursor())) { + //save description into context for parent + t.getValue().ifPresent(s -> ctx.putMessage("description", s)); + //skip tag + return null; + + } + + return t; + } + }; + } +} diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlRouteConfigurationSequenceRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlRouteConfigurationSequenceRecipe.java new file mode 100644 index 0000000000..b914f36b1e --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlRouteConfigurationSequenceRecipe.java @@ -0,0 +1,109 @@ +package org.apache.camel.quarkus.update.v3_0.yaml; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.camel.quarkus.update.RecipesUtil; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.yaml.JsonPathMatcher; +import org.openrewrite.yaml.format.IndentsVisitor; +import org.openrewrite.yaml.style.IndentsStyle; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.openrewrite.Tree.randomId; + +/** + * Camel API changes requires several changes in YAML route definition. + * Route-configuration children sequence is replaced with mappingEntry (with special migration of "on-exception") + */ +@EqualsAndHashCode(callSuper = true) +@Value +public class CamelQuarkusYamlRouteConfigurationSequenceRecipe extends Recipe { + + private static JsonPathMatcher MATCHER_ROUTE_CONFIGURATION = new JsonPathMatcher("$.route-configuration"); + private static JsonPathMatcher MATCHER_ROUTE_CONFIGURATION_ON_EXCEPTION = new JsonPathMatcher("$.route-configuration.on-exception"); + + @Override + public String getDisplayName() { + return "Camel Yaml changes regardinhg route-configuration children"; + } + + @Override + public String getDescription() { + return "Camel YAML changes. route-configuration children sequence is replaced with mappingEntry (with special migration of \"on-exception\")"; + } + + + @Override + public TreeVisitor getVisitor() { + + return new CamelQuarkusYamlVisitor() { + + private Yaml.Sequence sequenceToReplace; + private boolean indentRegistered = false; + + @Override + void clearLocalCache() { + sequenceToReplace = null; + } + + @Override + public Yaml.Sequence visitSequence(Yaml.Sequence sequence, ExecutionContext context) { + Yaml.Sequence s = super.visitSequence(sequence, context); + + //if there is a sequence in a route-configuration, it has to be replaced with mapping + if (new JsonPathMatcher("$.route-configuration").matches(getCursor().getParent())) { + this.sequenceToReplace = s; + } + return s; + } + + @Override + public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext context) { + Yaml.Mapping.Entry e = super.visitMappingEntry(entry, context); + + //if current mapping contains an entry with sequence belonging to route-configuration, remove the sequence + if (e.getValue() == sequenceToReplace) { + List entries = new ArrayList<>(); + for (Yaml.Sequence.Entry sEntry : sequenceToReplace.getEntries()) { + + if (sEntry.getBlock() instanceof Yaml.Mapping) { + ((Yaml.Mapping) sEntry.getBlock()).getEntries().forEach(y -> { + //if entry is on-exception from the route-configuration sequence, it has to be handled differently + if ("on-exception".equals(y.getKey().getValue())) { + Yaml.Sequence newSequence = sequenceToReplace.copyPaste(); + //keep only on-exception item + List filteredEntries = newSequence.getEntries().stream() + .filter(se -> ((Yaml.Mapping) se.getBlock()).getEntries().stream() + .filter(me -> "on-exception".equals(me.getKey().getValue())).findFirst().isPresent()) + .collect(Collectors.toList()); + + entries.add(y.withValue(newSequence.withEntries(filteredEntries)).withPrefix("\n")); + } else { + entries.add(y.withPrefix("\n")); + } + }); + } + } + Yaml.Mapping.Entry resultr = e.withValue(new Yaml.Mapping(randomId(), sequenceToReplace.getMarkers(), sequenceToReplace.getOpeningBracketPrefix(), entries, null, null)); + + if(!indentRegistered) { + indentRegistered = true; + //TODO might probably change indent in original file, may this happen? + doAfterVisit(new IndentsVisitor(new IndentsStyle(2), null)); + } + + return resultr; + } + return e; + } + }; + } + +} + diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlStepsInFromRecipe.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlStepsInFromRecipe.java new file mode 100644 index 0000000000..951bf6419a --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlStepsInFromRecipe.java @@ -0,0 +1,123 @@ +package org.apache.camel.quarkus.update.v3_0.yaml; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.camel.quarkus.update.RecipesUtil; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.yaml.JsonPathMatcher; +import org.openrewrite.yaml.YamlIsoVisitor; +import org.openrewrite.yaml.format.IndentsVisitor; +import org.openrewrite.yaml.style.IndentsStyle; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fixes following yaml change. + * + * The backwards compatible mode Camel 3.14 or older, which allowed to have steps as child to route has been removed. + * + * The old syntax: + * + *
+ * - route:
+ *     from:
+ *       uri: "direct:info"
+ *     steps:
+ *     - log: "message"
+ * 
+ * should be changed to: + *
+ * - route:
+ *     from:
+ *       uri: "direct:info"
+ *       steps:
+ *       - log: "message"
+ * 
+ */ +@EqualsAndHashCode(callSuper = true) +@Value +public class CamelQuarkusYamlStepsInFromRecipe extends Recipe { + + private static String[] PATHS_TO_PRE_CHECK = new String[] {"route.from"}; + private static JsonPathMatcher MATCHER_WITHOUT_ROUTE = new JsonPathMatcher("$.steps"); + private static JsonPathMatcher MATCHER_WITH_ROUTE = new JsonPathMatcher("$.route.steps"); + + + @Override + public String getDisplayName() { + return "Camel Yaml steps not allowed as route child"; + } + + @Override + public String getDescription() { + return "The YAML DSL backwards compatible mode in Camel 3.14 or older, which allowed 'steps' to be defined as a child of 'route' has been removed."; + } + + + @Override + public TreeVisitor getVisitor() { + + return new YamlIsoVisitor<>() { + + //both variables has to be set to null, to mark the migration done + Yaml.Mapping from = null; + Yaml.Mapping.Entry steps = null; + + @Override + public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext context) { + Yaml.Mapping.Entry e = super.visitMappingEntry(entry, context); + + if(steps == null && (MATCHER_WITH_ROUTE.matches(getCursor()) || MATCHER_WITHOUT_ROUTE.matches(getCursor()))) { + steps = e; + if(from != null) { + moveSteps(); + } + return null; + } + return e; + + } + + @Override + public Yaml.Mapping visitMapping(Yaml.Mapping mapping, ExecutionContext context) { + Yaml.Mapping m = super.visitMapping(mapping, context); + + String prop = YamlRecipesUtil.getProperty(getCursor()); + if(("route.from".equals(prop) || "from".equals(prop)) && from == null) { + from = m; + if(steps != null) { + moveSteps(); + } + } + + return m; + } + + private void moveSteps() { + doAfterVisit(new YamlIsoVisitor() { + + @Override + public Yaml.Mapping visitMapping(Yaml.Mapping mapping, ExecutionContext c) { + Yaml.Mapping m = super.visitMapping(mapping, c); + + if(m == from) { + List entries = new ArrayList<>(m.getEntries()); + entries.add(steps.copyPaste().withPrefix("\n")); + m = m.withEntries(entries); + } + + return m; + }}); + + //TODO might probably change indent in original file, may this happen? + doAfterVisit(new IndentsVisitor(new IndentsStyle(2), null)); + } + }; + } + +} + diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlVisitor.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlVisitor.java new file mode 100644 index 0000000000..199e724c32 --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/CamelQuarkusYamlVisitor.java @@ -0,0 +1,34 @@ +package org.apache.camel.quarkus.update.v3_0.yaml; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.yaml.YamlIsoVisitor; +import org.openrewrite.yaml.tree.Yaml; + +/** + * TODO add constraint to runthis recipe only of project contains Camel. + */ +public abstract class CamelQuarkusYamlVisitor extends YamlIsoVisitor { + + /** + * Method is called before start of visiting a new document. + * Implementations might need to clear all local state from previous document. + */ + abstract void clearLocalCache(); + + @Override + public Yaml.Document visitDocument(Yaml.Document document, ExecutionContext o) { + clearLocalCache(); + Yaml.Document d = super.visitDocument(document, o); + return d; + } + + @Override + public Yaml.Documents visitDocuments(Yaml.Documents documents, ExecutionContext context) { + boolean visited = context.getMessage(CamelQuarkusYamlVisitor.class.getSimpleName() + "_visited", false); + if(!visited) { + context.putMessage(CamelQuarkusYamlVisitor.class.getSimpleName() + "_visited", true); + documents = super.visitDocuments(documents, context); + } + return documents; + } +} diff --git a/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/YamlRecipesUtil.java b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/YamlRecipesUtil.java new file mode 100644 index 0000000000..046c515fcc --- /dev/null +++ b/recipes/src/main/java/org/apache/camel/quarkus/update/v3_0/yaml/YamlRecipesUtil.java @@ -0,0 +1,45 @@ +package org.apache.camel.quarkus.update.v3_0.yaml; + +import org.openrewrite.Cursor; +import org.openrewrite.Tree; +import org.openrewrite.java.tree.Comment; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JContainer; +import org.openrewrite.java.tree.JRightPadded; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.TextComment; +import org.openrewrite.marker.Markers; +import org.openrewrite.xml.tree.Xml; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; + +import static org.openrewrite.Tree.randomId; + +public class YamlRecipesUtil { + + static String getProperty(Cursor cursor) { + StringBuilder asProperty = new StringBuilder(); + Iterator path = cursor.getPath(); + int i = 0; + while (path.hasNext()) { + Object next = path.next(); + if (next instanceof Yaml.Mapping.Entry) { + Yaml.Mapping.Entry entry = (Yaml.Mapping.Entry) next; + if (i++ > 0) { + asProperty.insert(0, '.'); + } + asProperty.insert(0, entry.getKey().getValue()); + } + } + return asProperty.toString(); + } +} diff --git a/recipes/src/main/resources/quarkus-updates/core/3.0.yaml b/recipes/src/main/resources/quarkus-updates/core/3.0.yaml index 33aa37d23f..05fd446618 100644 --- a/recipes/src/main/resources/quarkus-updates/core/3.0.yaml +++ b/recipes/src/main/resources/quarkus-updates/core/3.0.yaml @@ -26,6 +26,13 @@ ##### --- type: specs.openrewrite.org/v1beta/recipe +name: custom +displayName: customName +description: customDesc +recipeList: + - org.apache.camel.quarkus.update.v3_0.CamelQuarkusMigrationRecipe +--- +type: specs.openrewrite.org/v1beta/recipe name: io.quarkus.updates.core.quarkus30.UpgradeQuarkiverse recipeList: - org.openrewrite.maven.UpgradeDependencyVersion: diff --git a/recipes/src/test/java/org/apache/camel/quarkus/update/v3_0/BaseCamelQuarkusTest.java b/recipes/src/test/java/org/apache/camel/quarkus/update/v3_0/BaseCamelQuarkusTest.java new file mode 100644 index 0000000000..2d774fdde9 --- /dev/null +++ b/recipes/src/test/java/org/apache/camel/quarkus/update/v3_0/BaseCamelQuarkusTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.quarkus.update.v3_0; + +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpecs; + +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.openrewrite.maven.Assertions.pomXml; + +public class BaseCamelQuarkusTest implements RewriteTest { + + SourceSpecs[] withCamel(SourceSpecs... sourceSpecs) { + return Stream.concat(Stream.of(camelPom()), Arrays.stream(sourceSpecs)).toArray(SourceSpecs[]::new); + } + + private SourceSpecs camelPom() { + return pomXml( + "\n" + + " 4.0.0\n" + + "\n" + + " test\n" + + " org.apache.camel.quarkus.test\n" + + " 1.0.0\n" + + "\n" + + " \n" + + " 2.13.3.Final\n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + " io.quarkus.platform\n" + + " quarkus-camel-bom\n" + + " 2.13.7.Final\n" + + " pom\n" + + " import\n" + + " \n" + + " \n" + + " \n" + + "\n" + + " \n" + + " \n" + + " org.apache.camel.quarkus\n" + + " camel-quarkus-activemq\n" + + " \n" + + " \n" + + "\n" + + " ", + + "\n" + + " 4.0.0\n" + + "\n" + + " test\n" + + " org.apache.camel.quarkus.test\n" + + " 1.0.0\n" + + "\n" + + " \n" + + " 2.13.3.Final\n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + " io.quarkus.platform\n" + + " quarkus-camel-bom\n" + + " 2.13.7.Final\n" + + " pom\n" + + " import\n" + + " \n" + + " \n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n" + + " "); + } + +} diff --git a/recipes/src/test/java/org/apache/camel/quarkus/update/v3_0/RecipesWithoutCamelQuarkusTest.java b/recipes/src/test/java/org/apache/camel/quarkus/update/v3_0/RecipesWithoutCamelQuarkusTest.java new file mode 100644 index 0000000000..3f6b8375bd --- /dev/null +++ b/recipes/src/test/java/org/apache/camel/quarkus/update/v3_0/RecipesWithoutCamelQuarkusTest.java @@ -0,0 +1,146 @@ +package org.apache.camel.quarkus.update.v3_0; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.SourceSpecs; +import org.openrewrite.yaml.Assertions; + +import java.util.function.BiFunction; +import java.util.function.Function; + +// Tests covering that CamelQuarkusRecipe is not applied to the migrated project, if there is no camelQuarkus dependency. +public class RecipesWithoutCamelQuarkusTest extends BaseCamelQuarkusTest { + + @Test + void testYaml() { + testCamelVsWithoutCamel(1, + Assertions::yaml, + Assertions::yaml, + "- route-configuration:\n" + + " - id: \"__id\"", + "- route-configuration:\n" + + " id: \"__id\"" + ); + } + + @Test + void testProperties(){ + testCamelVsWithoutCamel(2, + org.openrewrite.properties.Assertions::properties, + org.openrewrite.properties.Assertions::properties, + "#test\n" + + "camel.threadpool.rejectedPolicy=Discard", + "#test\n" + + "#'ThreadPoolRejectedPolicy.camel.threadpool.rejectedPolicy' has been removed, consider using 'Abort'. camel.threadpool.rejectedPolicy=Discard" + ); + } + + @Test + void testJava() { + testCamelVsWithoutCamel(2, + org.openrewrite.java.Assertions::java, + org.openrewrite.java.Assertions::java, + "import org.apache.camel.builder.SimpleBuilder;", + "/*'java.beans.SimpleBeanInfo' has been removed, (class was used internally).*/import org.apache.camel.builder.SimpleBuilder;" + ); + } + + @Test + void testJava11WithoutCamelQuarkus() { + rewriteRun( + spec -> spec.recipe(new CamelQuarkusMigrationRecipe()), + org.openrewrite.maven.Assertions.pomXml( + " \n" + + " 4.0.0\n" + + " org.apache.camel.quarkus\n" + + " 2.13.4-SNAPSHOT\n" + + " camel-quarkus-migration-test-microprofile\n" + + "\n" + + " \n" + + " 11\n" + + " 11\n" + + " " + + " ") + ); + } + + @Test + void testJava17Update() { + rewriteRun( + spec -> spec.recipe(new CamelQuarkusMigrationRecipe()), + org.openrewrite.maven.Assertions.pomXml( + " \n" + + " 4.0.0\n" + + " org.apache.camel.quarkus\n" + + " 2.13.4-SNAPSHOT\n" + + " camel-quarkus-migration-test-microprofile\n" + + " \n" + + " 11\n" + + " 11\n" + + " " + + " \n" + + " \n" + + " \n" + + " io.quarkus.platform\n" + + " quarkus-camel-bom\n" + + " 2.13.7.Final\n" + + " pom\n" + + " import\n" + + " \n" + + " \n" + + " " + + " \n" + + " \n" + + " org.apache.camel.quarkus\n" + + " camel-quarkus-bean\n" + + " \n" + + " " + + " " + , + " \n" + + " 4.0.0\n" + + " org.apache.camel.quarkus\n" + + " 2.13.4-SNAPSHOT\n" + + " camel-quarkus-migration-test-microprofile\n" + + " \n" + + " 17\n" + + " 17\n" + + " " + + " \n" + + " \n" + + " \n" + + " io.quarkus.platform\n" + + " quarkus-camel-bom\n" + + " 2.13.7.Final\n" + + " pom\n" + + " import\n" + + " \n" + + " \n" + + " " + + " \n" + + " \n" + + " org.apache.camel.quarkus\n" + + " camel-quarkus-bean\n" + + " \n" + + " " + + " ") + ); + } + + + //-------------------------------------- internal test method ------------------------------ + + private void testCamelVsWithoutCamel(int expectedCyclesThatMakeChanges, Function first, BiFunction second, String... sources) { + //if camel is not present, content should stay the same + rewriteRun( + spec -> spec.recipe(new CamelQuarkusMigrationRecipe()), + first.apply(sources[0]) + ); + //if camel is present, content should be changed (if the after == before, rewrite test will fail) + rewriteRun( + spec -> spec.expectedCyclesThatMakeChanges(expectedCyclesThatMakeChanges).recipe(new CamelQuarkusMigrationRecipe()), + withCamel(second.apply(sources[0], sources[1])) + ); + } + + +}