This is an automated email from the ASF dual-hosted git repository. nfilotto pushed a commit to branch 4384/add-support-of-groovy-extensions in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit e35a97f47574b4b8b45b5a2cf1ca398aa8410f5b Author: Nicolas Filotto <nfilo...@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 | 64 +++++++++- .../runtime/src/main/doc/limitations.adoc | 1 - integration-tests/groovy-dsl/pom.xml | 5 - .../main/resources/routes/routes-with-eip.groovy | 36 ++++++ .../camel/quarkus/dsl/groovy/GroovyDslTest.java | 142 ++++++++++++++------- 6 files changed, 191 insertions(+), 63 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 5f23bb77c0..c7f114f6a8 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,47 @@ 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(new ReflectiveClassBuildItem(true, false, info.name().toString())); + } + } else { + for (ClassInfo info : view.getAllKnownSubclasses(name)) { + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, info.name().toString())); + } + } + + reflectiveClass.produce(new ReflectiveClassBuildItem(true, type.isEnum(), type)); + } + + 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(new ReflectiveClassBuildItem(false, true, false, types.toArray(new Class<?>[0]))); + + } /** * 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..2b35814bb7 100644 --- a/integration-tests/groovy-dsl/pom.xml +++ b/integration-tests/groovy-dsl/pom.xml @@ -66,11 +66,6 @@ <artifactId>quarkus-junit5</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>io.rest-assured</groupId> - <artifactId>rest-assured</artifactId> - <scope>test</scope> - </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> 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..b0a0b6ae6a 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,116 @@ */ package org.apache.camel.quarkus.dsl.groovy; +import java.net.URL; + +import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; -import io.restassured.RestAssured; import org.apache.camel.dsl.groovy.GroovyRoutesBuilderLoader; -import org.hamcrest.CoreMatchers; +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.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + @QuarkusTest class GroovyDslTest { + @TestHTTPResource + URL url; + @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(this.url.toExternalForm() + "/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(this.url.toExternalForm() + "/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(this.url.toExternalForm() + "/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(this.url.toExternalForm() + "/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(this.url.toExternalForm() + "/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(this.url.toExternalForm() + "/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(this.url.toExternalForm() + "/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"); + } } }