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/27 07:40:08 UTC
[camel-quarkus] 01/34: 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 127df62892d88c735afc0f11f8989393bf8a7ce0
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");
+ }
}
}