You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ji...@apache.org on 2023/04/05 13:49:35 UTC

[camel-quarkus] 01/18: Ref #4384: Groovy DSL - Add support of Groovy extensions

This is an automated email from the ASF dual-hosted git repository.

jiriondrusek pushed a commit to branch camel-main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit a75a7a55392ff9cc91e9af1d5bd064b9eb0e9ca7
Author: Nicolas Filotto <nf...@talend.com>
AuthorDate: Wed Mar 8 10:28:33 2023 +0100

    Ref #4384: Groovy DSL - Add support of Groovy extensions
---
 .../pages/reference/extensions/groovy-dsl.adoc     |   6 -
 .../dsl/groovy/deployment/GroovyDslProcessor.java  |  65 +++++++-
 .../runtime/src/main/doc/limitations.adoc          |   1 -
 integration-tests/groovy-dsl/pom.xml               |  54 +++++-
 .../quarkus/dsl/groovy/GroovyDslResource.java      |   6 +
 .../main/resources/routes/routes-with-eip.groovy   |  36 ++++
 .../camel/quarkus/dsl/groovy/GroovyDslTest.java    | 181 +++++++++++++++------
 7 files changed, 285 insertions(+), 64 deletions(-)

diff --git a/docs/modules/ROOT/pages/reference/extensions/groovy-dsl.adoc b/docs/modules/ROOT/pages/reference/extensions/groovy-dsl.adoc
index f9c62daa0d..1926f73527 100644
--- a/docs/modules/ROOT/pages/reference/extensions/groovy-dsl.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/groovy-dsl.adoc
@@ -43,9 +43,3 @@ Or add the coordinates to your existing project:
 ifeval::[{doc-show-user-guide-link} == true]
 Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications.
 endif::[]
-
-[id="extensions-groovy-dsl-camel-quarkus-limitations"]
-== Camel Quarkus limitations
-
-The Groovy extensions are not supported which means that the extensions defined in the Camel project are ignored, more details in https://github.com/apache/camel-quarkus/issues/4384[Groovy DSL - Add support of Groovy extensions issue #4384].
-
diff --git a/extensions/groovy-dsl/deployment/src/main/java/org/apache/camel/quarkus/dsl/groovy/deployment/GroovyDslProcessor.java b/extensions/groovy-dsl/deployment/src/main/java/org/apache/camel/quarkus/dsl/groovy/deployment/GroovyDslProcessor.java
index d39b5eb8fc..6a8fd78dfd 100644
--- a/extensions/groovy-dsl/deployment/src/main/java/org/apache/camel/quarkus/dsl/groovy/deployment/GroovyDslProcessor.java
+++ b/extensions/groovy-dsl/deployment/src/main/java/org/apache/camel/quarkus/dsl/groovy/deployment/GroovyDslProcessor.java
@@ -19,14 +19,19 @@ package org.apache.camel.quarkus.dsl.groovy.deployment;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 import io.quarkus.deployment.annotations.BuildProducer;
 import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
 import io.quarkus.deployment.builditem.FeatureBuildItem;
 import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
 import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
@@ -34,6 +39,10 @@ import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
 import io.quarkus.deployment.pkg.steps.NativeBuild;
 import io.quarkus.maven.dependency.ResolvedDependency;
 import io.quarkus.paths.PathCollection;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.Message;
 import org.apache.camel.quarkus.core.deployment.main.CamelMainHelper;
 import org.apache.camel.quarkus.dsl.groovy.runtime.Configurer;
 import org.apache.camel.quarkus.support.dsl.deployment.DslGeneratedClassBuildItem;
@@ -44,6 +53,9 @@ import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.CompilerConfiguration;
 import org.codehaus.groovy.control.Phases;
 import org.codehaus.groovy.tools.GroovyClass;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.IndexView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,6 +65,11 @@ import static org.apache.camel.quarkus.support.dsl.deployment.DslSupportProcesso
 public class GroovyDslProcessor {
 
     private static final Logger LOG = LoggerFactory.getLogger(GroovyDslProcessor.class);
+    private static final List<Class<?>> CAMEL_REFLECTIVE_CLASSES = Arrays.asList(
+            Exchange.class,
+            Message.class,
+            ExchangePattern.class,
+            CamelContext.class);
     private static final String PACKAGE_NAME = "org.apache.camel.quarkus.dsl.groovy.generated";
     private static final String FILE_FORMAT = """
             package %s
@@ -117,12 +134,48 @@ public class GroovyDslProcessor {
         }
     }
 
-    // To put it back once the Groovy extensions will be supported (https://github.com/apache/camel-quarkus/issues/4384)
-    //    @BuildStep
-    //    void registerNativeImageResources(BuildProducer<ServiceProviderBuildItem> serviceProvider) {
-    //        serviceProvider
-    //                .produce(ServiceProviderBuildItem.allProvidersFromClassPath("org.codehaus.groovy.runtime.ExtensionModule"));
-    //    }
+    @BuildStep(onlyIf = NativeBuild.class)
+    void registerReflectiveClasses(
+            BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
+            CombinedIndexBuildItem combinedIndexBuildItem) {
+
+        IndexView view = combinedIndexBuildItem.getIndex();
+
+        for (Class<?> type : CAMEL_REFLECTIVE_CLASSES) {
+            DotName name = DotName.createSimple(type.getName());
+
+            if (type.isInterface()) {
+                for (ClassInfo info : view.getAllKnownImplementors(name)) {
+                    reflectiveClass.produce(ReflectiveClassBuildItem.builder(info.name().toString()).methods().build());
+                }
+            } else {
+                for (ClassInfo info : view.getAllKnownSubclasses(name)) {
+                    reflectiveClass.produce(ReflectiveClassBuildItem.builder(info.name().toString()).methods().build());
+                }
+            }
+
+            reflectiveClass.produce(ReflectiveClassBuildItem.builder(type).methods().fields(type.isEnum()).build());
+        }
+
+        Set<Class<?>> types = new HashSet<>();
+        // Register all the Camel return types of public methods of the camel reflective classes for reflection to
+        // be accessible in native mode from a Groovy resource
+        for (Class<?> c : CAMEL_REFLECTIVE_CLASSES) {
+            for (Method method : c.getMethods()) {
+                if (!method.getDeclaringClass().equals(Object.class)) {
+                    Class<?> returnType = method.getReturnType();
+                    if (returnType.getPackageName().startsWith("org.apache.camel.")
+                            && !CAMEL_REFLECTIVE_CLASSES.contains(returnType)) {
+                        types.add(returnType);
+                    }
+                }
+            }
+        }
+        // Allow access to methods by reflection to be accessible in native mode from a Groovy resource
+        reflectiveClass.produce(
+                ReflectiveClassBuildItem.builder(types.toArray(new Class<?>[0])).constructors(false).methods().build());
+
+    }
 
     /**
      * Convert a Groovy script into a Groovy class to be able to compile it.
diff --git a/extensions/groovy-dsl/runtime/src/main/doc/limitations.adoc b/extensions/groovy-dsl/runtime/src/main/doc/limitations.adoc
deleted file mode 100644
index a77743a491..0000000000
--- a/extensions/groovy-dsl/runtime/src/main/doc/limitations.adoc
+++ /dev/null
@@ -1 +0,0 @@
-The Groovy extensions are not supported which means that the extensions defined in the Camel project are ignored, more details in https://github.com/apache/camel-quarkus/issues/4384[Groovy DSL - Add support of Groovy extensions issue #4384].
diff --git a/integration-tests/groovy-dsl/pom.xml b/integration-tests/groovy-dsl/pom.xml
index 07f11a865a..2e1d62d904 100644
--- a/integration-tests/groovy-dsl/pom.xml
+++ b/integration-tests/groovy-dsl/pom.xml
@@ -30,6 +30,9 @@
     <name>Camel Quarkus :: Integration Tests :: Groovy DSL</name>
     <description>Integration tests for Camel Groovy DSL extension</description>
 
+    <properties>
+        <quarkus.runner>${project.build.directory}/quarkus-app/quarkus-run.jar</quarkus.runner>
+    </properties>
     <dependencies>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
@@ -67,13 +70,18 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>io.rest-assured</groupId>
-            <artifactId>rest-assured</artifactId>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.assertj</groupId>
-            <artifactId>assertj-core</artifactId>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-integration-tests-process-executor-support</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>
@@ -177,6 +185,7 @@
             </activation>
             <properties>
                 <quarkus.package.type>native</quarkus.package.type>
+                <quarkus.runner>${project.build.directory}/${project.artifactId}-${project.version}-runner</quarkus.runner>
             </properties>
             <build>
                 <plugins>
@@ -191,6 +200,43 @@
                                 </goals>
                             </execution>
                         </executions>
+                        <configuration>
+                            <systemProperties>
+                                <quarkus.runner>${quarkus.runner}</quarkus.runner>
+                            </systemProperties>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>full</id>
+            <activation>
+                <property>
+                    <name>!quickly</name>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <!-- Move surefire:test to integration-test phase to be able to run
+                             java -jar target/*runner.jar from a test -->
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>default-test</id>
+                                <goals>
+                                    <goal>test</goal>
+                                </goals>
+                                <phase>integration-test</phase>
+                                <configuration>
+                                    <systemProperties>
+                                        <quarkus.runner>${quarkus.runner}</quarkus.runner>
+                                    </systemProperties>
+                                </configuration>
+                            </execution>
+                        </executions>
                     </plugin>
                 </plugins>
             </build>
diff --git a/integration-tests/groovy-dsl/src/main/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslResource.java b/integration-tests/groovy-dsl/src/main/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslResource.java
index 5d0585c253..8eb3c028b2 100644
--- a/integration-tests/groovy-dsl/src/main/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslResource.java
+++ b/integration-tests/groovy-dsl/src/main/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslResource.java
@@ -44,6 +44,12 @@ public class GroovyDslResource {
     @Inject
     ProducerTemplate producerTemplate;
 
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String ready() {
+        return "OK";
+    }
+
     @Path("/main/groovyRoutesBuilderLoader")
     @GET
     @Produces(MediaType.TEXT_PLAIN)
diff --git a/integration-tests/groovy-dsl/src/main/resources/routes/routes-with-eip.groovy b/integration-tests/groovy-dsl/src/main/resources/routes/routes-with-eip.groovy
new file mode 100644
index 0000000000..8d21881910
--- /dev/null
+++ b/integration-tests/groovy-dsl/src/main/resources/routes/routes-with-eip.groovy
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+from('direct:routes-with-eip-setBody')
+    .id('routes-with-eip-setBody')
+    .setBody { "true" }
+
+from('direct:routes-with-eip-body')
+    .id('routes-with-eip-body')
+    .transform().body { b -> "true"}
+
+from('direct:routes-with-eip-message')
+        .id('routes-with-eip-message')
+        .transform().message { m -> m.body = "true"}
+
+from('direct:routes-with-eip-exchange')
+        .id('routes-with-eip-exchange')
+        .transform().exchange { e -> e.in.body = "true"}
+
+from('direct:routes-with-eip-process')
+        .id('routes-with-eip-process')
+        .process {e -> e.in.body = "true" }
diff --git a/integration-tests/groovy-dsl/src/test/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslTest.java b/integration-tests/groovy-dsl/src/test/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslTest.java
index fdc6a4faf4..82ce107208 100644
--- a/integration-tests/groovy-dsl/src/test/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslTest.java
+++ b/integration-tests/groovy-dsl/src/test/java/org/apache/camel/quarkus/dsl/groovy/GroovyDslTest.java
@@ -16,64 +16,151 @@
  */
 package org.apache.camel.quarkus.dsl.groovy;
 
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.RestAssured;
+import java.util.concurrent.TimeUnit;
+
 import org.apache.camel.dsl.groovy.GroovyRoutesBuilderLoader;
-import org.hamcrest.CoreMatchers;
+import org.apache.camel.quarkus.test.support.process.QuarkusProcessExecutor;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
+import org.zeroturnaround.exec.StartedProcess;
+
+import static org.assertj.core.api.Assertions.assertThat;
 
-@QuarkusTest
 class GroovyDslTest {
 
+    private static int port;
+    private static StartedProcess process;
+
+    @BeforeAll
+    static void start() throws Exception {
+        // Need to use an external process to test the extension because of a CL issue that happens only on test mode
+        // due to the fact that groovy is defined as a parent first artifact
+        QuarkusProcessExecutor quarkusProcessExecutor = new QuarkusProcessExecutor();
+        process = quarkusProcessExecutor.start();
+        port = quarkusProcessExecutor.getHttpPort();
+        awaitStartup();
+    }
+
+    @AfterAll
+    static void stop() {
+        if (process != null && process.getProcess().isAlive()) {
+            process.getProcess().destroy();
+        }
+    }
+
+    private static String toAbsolutePath(String relativePath) {
+        return String.format("http://localhost:%d/%s", port, relativePath);
+    }
+
+    private static void awaitStartup() {
+        Awaitility.await().atMost(10, TimeUnit.SECONDS).pollDelay(1, TimeUnit.SECONDS).until(() -> {
+            HttpUriRequest request = new HttpGet(toAbsolutePath("/groovy-dsl"));
+            try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
+                HttpResponse httpResponse = client.execute(request);
+                return httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
+            } catch (Exception e) {
+                return false;
+            }
+        });
+    }
+
     @Test
-    void groovyHello() {
-        RestAssured.given()
-                .body("John Smith")
-                .post("/groovy-dsl/hello")
-                .then()
-                .statusCode(200)
-                .body(CoreMatchers.is("Hello John Smith from Groovy!"));
+    void groovyHello() throws Exception {
+        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
+            // Given
+            HttpPost httpPost = new HttpPost(toAbsolutePath("/groovy-dsl/hello"));
+            httpPost.setEntity(new StringEntity("John Smith", ContentType.TEXT_PLAIN));
+
+            // When
+            HttpResponse httpResponse = client.execute(httpPost);
+
+            // Then
+            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+            assertThat(EntityUtils.toString(httpResponse.getEntity())).isEqualTo("Hello John Smith from Groovy!");
+        }
     }
 
     @Test
-    void testMainInstanceWithJavaRoutes() {
-        RestAssured.given()
-                .get("/groovy-dsl/main/groovyRoutesBuilderLoader")
-                .then()
-                .statusCode(200)
-                .body(CoreMatchers.is(GroovyRoutesBuilderLoader.class.getName()));
-
-        RestAssured.given()
-                .get("/groovy-dsl/main/routeBuilders")
-                .then()
-                .statusCode(200)
-                .body(CoreMatchers.is(""));
-
-        RestAssured.given()
-                .get("/groovy-dsl/main/routes")
-                .then()
-                .statusCode(200)
-                .body(CoreMatchers.is(
-                        "my-groovy-route,routes-with-components-configuration,routes-with-dataformats-configuration,routes-with-endpoint-dsl,routes-with-error-handler,routes-with-languages-configuration,routes-with-rest,routes-with-rest-dsl-get,routes-with-rest-dsl-post,routes-with-rest-get,routes-with-rest-post"));
-        RestAssured.given()
-                .get("/groovy-dsl/main/successful/routes")
-                .then()
-                .statusCode(200)
-                .body(CoreMatchers.is("5"));
+    void testMainInstanceWithJavaRoutes() throws Exception {
+        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
+            // Given
+            HttpUriRequest request = new HttpGet(toAbsolutePath("/groovy-dsl/main/groovyRoutesBuilderLoader"));
+
+            // When
+            HttpResponse httpResponse = client.execute(request);
+
+            // Then
+            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+            assertThat(EntityUtils.toString(httpResponse.getEntity())).isEqualTo(GroovyRoutesBuilderLoader.class.getName());
+
+            // Given
+            request = new HttpGet(toAbsolutePath("/groovy-dsl/main/routeBuilders"));
+
+            // When
+            httpResponse = client.execute(request);
+
+            // Then
+            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+            assertThat(EntityUtils.toString(httpResponse.getEntity())).isEmpty();
+
+            // Given
+            request = new HttpGet(toAbsolutePath("/groovy-dsl/main/routes"));
+
+            // When
+            httpResponse = client.execute(request);
+
+            // Then
+            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+            assertThat(EntityUtils.toString(httpResponse.getEntity())).isEqualTo(
+                    "my-groovy-route,routes-with-components-configuration,routes-with-dataformats-configuration,routes-with-eip-body,routes-with-eip-exchange,routes-with-eip-message,routes-with-eip-process,routes-with-eip-setBody,routes-with-endpoint-dsl,routes-with-error-handler,routes-with-languages-configuration,routes-with-rest,routes-with-rest-dsl-get,routes-with-rest-dsl-post,routes-with-rest-get,routes-with-rest-post");
+
+            // Given
+            request = new HttpGet(toAbsolutePath("/groovy-dsl/main/successful/routes"));
+
+            // When
+            httpResponse = client.execute(request);
+
+            // Then
+            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+            assertThat(EntityUtils.toString(httpResponse.getEntity())).isEqualTo("10");
+        }
     }
 
     @Test
-    void testRestEndpoints() {
-        RestAssured.given()
-                .get("/root/my/path/get")
-                .then()
-                .statusCode(200)
-                .body(CoreMatchers.is("Hello World"));
-        RestAssured.given()
-                .body("Will")
-                .post("/root/post")
-                .then()
-                .statusCode(200)
-                .body(CoreMatchers.is("Hello Will"));
+    void testRestEndpoints() throws Exception {
+        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
+            // Given
+            final HttpGet httpGet = new HttpGet(toAbsolutePath("/root/my/path/get"));
+
+            // When
+            HttpResponse httpResponse = client.execute(httpGet);
+
+            // Then
+            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+            assertThat(EntityUtils.toString(httpResponse.getEntity())).isEqualTo("Hello World");
+
+            // Given
+            HttpPost httpPost = new HttpPost(toAbsolutePath("/root/post"));
+            httpPost.setEntity(new StringEntity("Will", ContentType.TEXT_PLAIN));
+
+            // When
+            httpResponse = client.execute(httpPost);
+
+            // Then
+            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+            assertThat(EntityUtils.toString(httpResponse.getEntity())).isEqualTo("Hello Will");
+        }
     }
 }