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/13 06:52:44 UTC
[camel-quarkus] 05/25: Ref #1746: Groovy language native support
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 821c541b0f6c6d85f17d76cbfb6288679e628279
Author: Nicolas Filotto <nf...@talend.com>
AuthorDate: Wed Mar 22 16:58:08 2023 +0100
Ref #1746: Groovy language native support
---
docs/modules/ROOT/examples/languages/groovy.yml | 6 +-
.../ROOT/pages/reference/extensions/groovy.adoc | 22 ++-
.../groovy/deployment/GroovyProcessor.java | 47 ------
extensions-jvm/pom.xml | 1 -
.../groovy/deployment/pom.xml | 8 +
.../GroovyExpressionSourceBuildItem.java | 57 +++++++
.../groovy/deployment/GroovyProcessor.java | 176 +++++++++++++++++++++
{extensions-jvm => extensions}/groovy/pom.xml | 2 +-
.../groovy/runtime/pom.xml | 9 ++
.../groovy/runtime/src/main/doc/limitations.adoc | 3 +
.../groovy/runtime/GroovyExpressionRecorder.java | 43 +++++
.../groovy/runtime/GroovyStaticScript.java | 63 ++++++++
.../main/resources/META-INF/quarkus-extension.yaml | 0
extensions/pom.xml | 1 +
integration-tests-jvm/pom.xml | 1 -
.../groovy/pom.xml | 68 +++++++-
.../quarkus/component/groovy/it/GroovyBean.java | 16 +-
.../component/groovy/it/GroovyResource.java | 24 +++
.../quarkus/component/groovy/it/GroovyRoutes.java | 18 +++
.../groovy/src/main/resources/bean.txt | 4 +
.../quarkus/component/groovy/it/GroovyIT.java | 21 +--
.../quarkus/component/groovy/it/GroovyTest.java | 43 ++++-
integration-tests/pom.xml | 1 +
tooling/scripts/test-categories.yaml | 1 +
24 files changed, 550 insertions(+), 85 deletions(-)
diff --git a/docs/modules/ROOT/examples/languages/groovy.yml b/docs/modules/ROOT/examples/languages/groovy.yml
index 6a2a19afa1..5f8155df7b 100644
--- a/docs/modules/ROOT/examples/languages/groovy.yml
+++ b/docs/modules/ROOT/examples/languages/groovy.yml
@@ -2,11 +2,11 @@
# This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page
cqArtifactId: camel-quarkus-groovy
cqArtifactIdBase: groovy
-cqNativeSupported: false
-cqStatus: Preview
+cqNativeSupported: true
+cqStatus: Stable
cqDeprecated: false
cqJvmSince: 1.0.0
-cqNativeSince: n/a
+cqNativeSince: 3.0.0
cqCamelPartName: groovy
cqCamelPartTitle: Groovy
cqCamelPartDescription: Evaluates a Groovy script.
diff --git a/docs/modules/ROOT/pages/reference/extensions/groovy.adoc b/docs/modules/ROOT/pages/reference/extensions/groovy.adoc
index 45a56e1a12..e0c5ad48ed 100644
--- a/docs/modules/ROOT/pages/reference/extensions/groovy.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/groovy.adoc
@@ -5,17 +5,17 @@
:page-aliases: extensions/groovy.adoc
:linkattrs:
:cq-artifact-id: camel-quarkus-groovy
-:cq-native-supported: false
-:cq-status: Preview
-:cq-status-deprecation: Preview
+:cq-native-supported: true
+:cq-status: Stable
+:cq-status-deprecation: Stable
:cq-description: Evaluate a Groovy script
:cq-deprecated: false
:cq-jvm-since: 1.0.0
-:cq-native-since: n/a
+:cq-native-since: 3.0.0
ifeval::[{doc-show-badges} == true]
[.badges]
-[.badge-key]##JVM since##[.badge-supported]##1.0.0## [.badge-key]##Native##[.badge-unsupported]##unsupported##
+[.badge-key]##JVM since##[.badge-supported]##1.0.0## [.badge-key]##Native since##[.badge-supported]##3.0.0##
endif::[]
Evaluate a Groovy script
@@ -30,6 +30,10 @@ Please refer to the above link for usage and configuration details.
[id="extensions-groovy-maven-coordinates"]
== Maven coordinates
+https://{link-quarkus-code-generator}/?extension-search=camel-quarkus-groovy[Create a new project with this extension on {link-quarkus-code-generator}, window="_blank"]
+
+Or add the coordinates to your existing project:
+
[source,xml]
----
<dependency>
@@ -40,3 +44,11 @@ Please refer to the above link for usage and configuration details.
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-camel-quarkus-limitations"]
+== Camel Quarkus limitations
+
+Due to some limitations in GraalVM that prevent to execute even basic scripts in native mode, the compilation of the
+Groovy expressions is made with the static compilation enabled which means that the types used in your expression must
+be known at compile time. Please refer to the https://docs.groovy-lang.org/latest/html/documentation/core-semantics.html#static-type-checking[Groovy documentation for more details].
+
diff --git a/extensions-jvm/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyProcessor.java b/extensions-jvm/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyProcessor.java
deleted file mode 100644
index 3b8716ad29..0000000000
--- a/extensions-jvm/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyProcessor.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.component.groovy.deployment;
-
-import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.annotations.ExecutionTime;
-import io.quarkus.deployment.annotations.Record;
-import io.quarkus.deployment.builditem.FeatureBuildItem;
-import io.quarkus.deployment.pkg.steps.NativeBuild;
-import org.apache.camel.quarkus.core.JvmOnlyRecorder;
-import org.jboss.logging.Logger;
-
-class GroovyProcessor {
- private static final Logger LOG = Logger.getLogger(GroovyProcessor.class);
-
- private static final String FEATURE = "camel-groovy";
-
- @BuildStep
- FeatureBuildItem feature() {
- return new FeatureBuildItem(FEATURE);
- }
-
- /**
- * Remove this once this extension starts supporting the native mode.
- */
- @BuildStep(onlyIf = NativeBuild.class)
- @Record(value = ExecutionTime.RUNTIME_INIT)
- void warnJvmInNative(JvmOnlyRecorder recorder) {
- JvmOnlyRecorder.warnJvmInNative(LOG, FEATURE); // warn at build time
- recorder.warnJvmInNative(FEATURE); // warn at runtime
- }
-
-}
diff --git a/extensions-jvm/pom.xml b/extensions-jvm/pom.xml
index d0ace3b3b3..6aa2e6a4ca 100644
--- a/extensions-jvm/pom.xml
+++ b/extensions-jvm/pom.xml
@@ -66,7 +66,6 @@
<module>flink</module>
<module>google-functions</module>
<module>google-secret-manager</module>
- <module>groovy</module>
<module>guava-eventbus</module>
<module>hashicorp-vault</module>
<module>hdfs</module>
diff --git a/extensions-jvm/groovy/deployment/pom.xml b/extensions/groovy/deployment/pom.xml
similarity index 87%
rename from extensions-jvm/groovy/deployment/pom.xml
rename to extensions/groovy/deployment/pom.xml
index e321de40a9..0beefa5e60 100644
--- a/extensions-jvm/groovy/deployment/pom.xml
+++ b/extensions/groovy/deployment/pom.xml
@@ -38,6 +38,14 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-groovy</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-groovy-dsl-deployment</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-support-language-deployment</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/extensions/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyExpressionSourceBuildItem.java b/extensions/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyExpressionSourceBuildItem.java
new file mode 100644
index 0000000000..104e478083
--- /dev/null
+++ b/extensions/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyExpressionSourceBuildItem.java
@@ -0,0 +1,57 @@
+/*
+ * 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.component.groovy.deployment;
+
+import io.quarkus.builder.item.MultiBuildItem;
+
+/**
+ * A {@link MultiBuildItem} bearing info about a Groovy language expression that needs to get compiled.
+ */
+public final class GroovyExpressionSourceBuildItem extends MultiBuildItem {
+
+ private final String sourceCode;
+ private final String originalCode;
+ private final String className;
+
+ public GroovyExpressionSourceBuildItem(String className, String originalCode, String sourceCode) {
+ this.className = className;
+ this.originalCode = originalCode;
+ this.sourceCode = sourceCode;
+ }
+
+ /**
+ * @return the expression source code to compile
+ */
+ public String getSourceCode() {
+ return sourceCode;
+ }
+
+ /**
+ * @return the original content of the extracted expression.
+ */
+ public String getOriginalCode() {
+ return originalCode;
+ }
+
+ /**
+ * @return a fully qualified class name that the compiler may use as a base for the name of the class into which it
+ * compiles the source code returned by {@link #getSourceCode()}
+ */
+ public String getClassName() {
+ return className;
+ }
+}
diff --git a/extensions/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyProcessor.java b/extensions/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyProcessor.java
new file mode 100644
index 0000000000..03fcd47769
--- /dev/null
+++ b/extensions/groovy/deployment/src/main/java/org/apache/camel/quarkus/component/groovy/deployment/GroovyProcessor.java
@@ -0,0 +1,176 @@
+/*
+ * 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.component.groovy.deployment;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
+import io.quarkus.deployment.pkg.steps.NativeBuild;
+import io.quarkus.deployment.recording.RecorderContext;
+import io.quarkus.maven.dependency.ResolvedDependency;
+import io.quarkus.paths.PathCollection;
+import io.quarkus.runtime.RuntimeValue;
+import org.apache.camel.language.groovy.GroovyLanguage;
+import org.apache.camel.quarkus.component.groovy.runtime.GroovyExpressionRecorder;
+import org.apache.camel.quarkus.component.groovy.runtime.GroovyStaticScript;
+import org.apache.camel.quarkus.core.deployment.spi.CamelBeanBuildItem;
+import org.apache.camel.quarkus.support.language.deployment.ExpressionBuildItem;
+import org.apache.camel.quarkus.support.language.deployment.ExpressionExtractionResultBuildItem;
+import org.apache.camel.quarkus.support.language.deployment.ScriptBuildItem;
+import org.apache.camel.quarkus.support.language.runtime.ExpressionUID;
+import org.apache.camel.quarkus.support.language.runtime.ScriptUID;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class GroovyProcessor {
+ private static final Logger LOG = LoggerFactory.getLogger(GroovyProcessor.class);
+
+ private static final String PACKAGE_NAME = "org.apache.camel.quarkus.component.groovy.generated";
+ private static final String SCRIPT_FORMAT = """
+ package %s
+ @groovy.transform.CompileStatic
+ class %s extends %s {
+ Object run() {
+ %s
+ }
+ }
+ """;
+ private static final String FEATURE = "camel-groovy";
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem(FEATURE);
+ }
+
+ @BuildStep(onlyIf = NativeBuild.class)
+ void collectExpressions(ExpressionExtractionResultBuildItem result,
+ List<ExpressionBuildItem> expressions,
+ List<ScriptBuildItem> scripts,
+ BuildProducer<GroovyExpressionSourceBuildItem> producer) {
+ if (result.isSuccess()) {
+ List<ExpressionBuildItem> groovyExpressions = expressions.stream()
+ .filter(exp -> "groovy".equals(exp.getLanguage()))
+ .toList();
+ List<ScriptBuildItem> groovyScripts = scripts.stream()
+ .filter(exp -> "groovy".equals(exp.getLanguage()))
+ .toList();
+ if (groovyExpressions.isEmpty() && groovyScripts.isEmpty()) {
+ return;
+ }
+ for (ExpressionBuildItem expression : groovyExpressions) {
+ String original = expression.getExpression();
+ ExpressionUID id = new ExpressionUID(original);
+ String name = String.format("%s.%s", PACKAGE_NAME, id);
+ String content = toScriptClass(id.asJavaIdentifier(), expression.getLoadedExpression());
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Extracting expression:\n\n{}\n", content);
+ }
+ producer.produce(new GroovyExpressionSourceBuildItem(name, original, content));
+ }
+ for (ScriptBuildItem script : groovyScripts) {
+ String original = script.getContent();
+ ScriptUID id = new ScriptUID(original, script.getBindings());
+ String name = String.format("%s.%s", PACKAGE_NAME, id);
+ String content = toScriptClass(id.asJavaIdentifier(), script.getLoadedContent());
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Extracting script:\n\n{}\n", content);
+ }
+ producer.produce(new GroovyExpressionSourceBuildItem(name, original, content));
+ }
+ }
+ }
+
+ @BuildStep(onlyIf = NativeBuild.class)
+ void compileScriptsAOT(CurateOutcomeBuildItem curateOutcomeBuildItem,
+ BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
+ List<GroovyExpressionSourceBuildItem> sources,
+ BuildProducer<GeneratedClassBuildItem> generatedClass) {
+ if (sources.isEmpty()) {
+ return;
+ }
+ CompilationUnit unit = new CompilationUnit();
+ Set<String> classNames = new HashSet<>();
+ for (GroovyExpressionSourceBuildItem source : sources) {
+ String name = source.getClassName();
+ String content = source.getSourceCode();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Compiling script:\n\n{}\n", content);
+ }
+ unit.addSource(name, content);
+ classNames.add(name);
+ }
+ CompilerConfiguration cc = new CompilerConfiguration();
+ cc.setClasspathList(
+ curateOutcomeBuildItem.getApplicationModel().getDependencies().stream()
+ .map(ResolvedDependency::getResolvedPaths)
+ .flatMap(PathCollection::stream)
+ .map(Objects::toString)
+ .toList());
+ unit.configure(cc);
+ unit.compile(Phases.CLASS_GENERATION);
+ for (GroovyClass clazz : unit.getClasses()) {
+ String className = clazz.getName();
+ generatedClass.produce(new GeneratedClassBuildItem(true, className, clazz.getBytes()));
+ if (classNames.contains(className)) {
+ reflectiveClass.produce(ReflectiveClassBuildItem.builder(className).methods().fields().build());
+ }
+ }
+ }
+
+ @Record(ExecutionTime.STATIC_INIT)
+ @BuildStep(onlyIf = NativeBuild.class)
+ CamelBeanBuildItem configureLanguage(
+ RecorderContext recorderContext,
+ GroovyExpressionRecorder recorder,
+ ExpressionExtractionResultBuildItem result,
+ List<GroovyExpressionSourceBuildItem> sources) {
+ if (result.isSuccess() && !sources.isEmpty()) {
+ RuntimeValue<GroovyLanguage.Builder> builder = recorder.languageBuilder();
+ for (GroovyExpressionSourceBuildItem source : sources) {
+ recorder.addScript(builder, source.getOriginalCode(), recorderContext.classProxy(source.getClassName()));
+ }
+ final RuntimeValue<GroovyLanguage> language = recorder.languageNewInstance(builder);
+ return new CamelBeanBuildItem("groovy", GroovyLanguage.class.getName(), language);
+ }
+ return null;
+ }
+
+ /**
+ * Convert a Groovy expression into a Script class to be able to compile it.
+ *
+ * @param name the name of the Groovy expression
+ * @param contentScript the content of the Groovy script
+ * @return the content of the corresponding Script class.
+ */
+ private static String toScriptClass(String name, String contentScript) {
+ return String.format(SCRIPT_FORMAT, PACKAGE_NAME, name, GroovyStaticScript.class.getName(), contentScript);
+ }
+}
diff --git a/extensions-jvm/groovy/pom.xml b/extensions/groovy/pom.xml
similarity index 96%
rename from extensions-jvm/groovy/pom.xml
rename to extensions/groovy/pom.xml
index a5eee7f5d6..dac9f90a14 100644
--- a/extensions-jvm/groovy/pom.xml
+++ b/extensions/groovy/pom.xml
@@ -21,7 +21,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.camel.quarkus</groupId>
- <artifactId>camel-quarkus-extensions-jvm</artifactId>
+ <artifactId>camel-quarkus-extensions</artifactId>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
diff --git a/extensions-jvm/groovy/runtime/pom.xml b/extensions/groovy/runtime/pom.xml
similarity index 90%
rename from extensions-jvm/groovy/runtime/pom.xml
rename to extensions/groovy/runtime/pom.xml
index 8990247530..b1f0e4483e 100644
--- a/extensions-jvm/groovy/runtime/pom.xml
+++ b/extensions/groovy/runtime/pom.xml
@@ -32,6 +32,7 @@
<properties>
<camel.quarkus.jvmSince>1.0.0</camel.quarkus.jvmSince>
+ <camel.quarkus.nativeSince>3.0.0</camel.quarkus.nativeSince>
</properties>
<dependencies>
@@ -39,10 +40,18 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-core</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-support-language</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-groovy</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-groovy-dsl</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/extensions/groovy/runtime/src/main/doc/limitations.adoc b/extensions/groovy/runtime/src/main/doc/limitations.adoc
new file mode 100644
index 0000000000..5aa4300809
--- /dev/null
+++ b/extensions/groovy/runtime/src/main/doc/limitations.adoc
@@ -0,0 +1,3 @@
+Due to some limitations in GraalVM that prevent to execute even basic scripts in native mode, the compilation of the
+Groovy expressions is made with the static compilation enabled which means that the types used in your expression must
+be known at compile time. Please refer to the https://docs.groovy-lang.org/latest/html/documentation/core-semantics.html#static-type-checking[Groovy documentation for more details].
diff --git a/extensions/groovy/runtime/src/main/java/org/apache/camel/quarkus/component/groovy/runtime/GroovyExpressionRecorder.java b/extensions/groovy/runtime/src/main/java/org/apache/camel/quarkus/component/groovy/runtime/GroovyExpressionRecorder.java
new file mode 100644
index 0000000000..e1d9a922a9
--- /dev/null
+++ b/extensions/groovy/runtime/src/main/java/org/apache/camel/quarkus/component/groovy/runtime/GroovyExpressionRecorder.java
@@ -0,0 +1,43 @@
+/*
+ * 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.component.groovy.runtime;
+
+import groovy.lang.Script;
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.annotations.Recorder;
+import org.apache.camel.language.groovy.GroovyLanguage;
+
+@Recorder
+public class GroovyExpressionRecorder {
+
+ public RuntimeValue<GroovyLanguage.Builder> languageBuilder() {
+ return new RuntimeValue<>(new GroovyLanguage.Builder());
+ }
+
+ @SuppressWarnings("unchecked")
+ public void addScript(RuntimeValue<GroovyLanguage.Builder> builder, String content, Class<?> clazz) {
+ try {
+ builder.getValue().addScript(content, (Class<Script>) clazz);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public RuntimeValue<GroovyLanguage> languageNewInstance(RuntimeValue<GroovyLanguage.Builder> builder) {
+ return new RuntimeValue<>(builder.getValue().build());
+ }
+}
diff --git a/extensions/groovy/runtime/src/main/java/org/apache/camel/quarkus/component/groovy/runtime/GroovyStaticScript.java b/extensions/groovy/runtime/src/main/java/org/apache/camel/quarkus/component/groovy/runtime/GroovyStaticScript.java
new file mode 100644
index 0000000000..f2d86e6577
--- /dev/null
+++ b/extensions/groovy/runtime/src/main/java/org/apache/camel/quarkus/component/groovy/runtime/GroovyStaticScript.java
@@ -0,0 +1,63 @@
+/*
+ * 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.component.groovy.runtime;
+
+import java.util.Map;
+
+import groovy.lang.Binding;
+import groovy.lang.Script;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.RouteTemplateContext;
+
+/**
+ * A type of {@link Script} that is specific to Camel with fixed fields to be able to compile the script using
+ * static compilation.
+ */
+public abstract class GroovyStaticScript extends Script {
+
+ protected Map<String, Object> headers;
+ protected Object body;
+ protected Message in;
+ protected Message request;
+ protected Exchange exchange;
+ protected Message out;
+ protected Message response;
+ protected CamelContext camelContext;
+ protected RouteTemplateContext rtc;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setBinding(Binding binding) {
+ super.setBinding(binding);
+ this.headers = (Map<String, Object>) getPropertyValue("headers");
+ this.body = getPropertyValue("body");
+ this.in = (Message) getPropertyValue("in");
+ this.out = (Message) getPropertyValue("out");
+ this.request = (Message) getPropertyValue("request");
+ this.response = (Message) getPropertyValue("response");
+ this.exchange = (Exchange) getPropertyValue("exchange");
+ this.camelContext = (CamelContext) getPropertyValue("camelContext");
+ this.rtc = (RouteTemplateContext) getPropertyValue("rtc");
+ }
+
+ private Object getPropertyValue(String name) {
+ Binding binding = getBinding();
+ return binding.hasVariable(name) ? binding.getProperty(name) : null;
+ }
+}
diff --git a/extensions-jvm/groovy/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/groovy/runtime/src/main/resources/META-INF/quarkus-extension.yaml
similarity index 100%
rename from extensions-jvm/groovy/runtime/src/main/resources/META-INF/quarkus-extension.yaml
rename to extensions/groovy/runtime/src/main/resources/META-INF/quarkus-extension.yaml
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 2cd09fa028..fcfe524e66 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -115,6 +115,7 @@
<module>google-storage</module>
<module>graphql</module>
<module>grok</module>
+ <module>groovy</module>
<module>groovy-dsl</module>
<module>grpc</module>
<module>gson</module>
diff --git a/integration-tests-jvm/pom.xml b/integration-tests-jvm/pom.xml
index ab72c888c7..9aa8850786 100644
--- a/integration-tests-jvm/pom.xml
+++ b/integration-tests-jvm/pom.xml
@@ -65,7 +65,6 @@
<module>flink</module>
<module>google-functions</module>
<module>google-secret-manager</module>
- <module>groovy</module>
<module>guava-eventbus</module>
<module>hashicorp-vault</module>
<module>hdfs</module>
diff --git a/integration-tests-jvm/groovy/pom.xml b/integration-tests/groovy/pom.xml
similarity index 60%
rename from integration-tests-jvm/groovy/pom.xml
rename to integration-tests/groovy/pom.xml
index a17bd3bf5f..0acaec5782 100644
--- a/integration-tests-jvm/groovy/pom.xml
+++ b/integration-tests/groovy/pom.xml
@@ -39,6 +39,14 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-kamelet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-bean</artifactId>
+ </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
@@ -54,6 +62,12 @@
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
</dependencies>
@@ -67,6 +81,19 @@
</activation>
<dependencies>
<!-- The following dependencies guarantee that this module is built after them. You can update them by running `mvn process-resources -Pformat -N` from the source tree root directory -->
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-bean-deployment</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>*</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct-deployment</artifactId>
@@ -93,8 +120,47 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-kamelet-deployment</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>*</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
</dependencies>
</profile>
+ <profile>
+ <id>native</id>
+ <activation>
+ <property>
+ <name>native</name>
+ </property>
+ </activation>
+ <properties>
+ <quarkus.package.type>native</quarkus.package.type>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
-
</project>
diff --git a/integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java b/integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyBean.java
similarity index 72%
copy from integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java
copy to integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyBean.java
index 59c03d1ec0..e0108b0fbd 100644
--- a/integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java
+++ b/integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyBean.java
@@ -16,16 +16,18 @@
*/
package org.apache.camel.quarkus.component.groovy.it;
-import org.apache.camel.builder.RouteBuilder;
+import io.quarkus.runtime.annotations.RegisterForReflection;
-public class GroovyRoutes extends RouteBuilder {
+@RegisterForReflection
+public class GroovyBean {
- @Override
- public void configure() {
+ private String bar;
- from("direct:scriptGroovy")
- .script()
- .groovy("exchange.getMessage().setBody('Hello ' + exchange.getMessage().getBody(String.class) + ' from Groovy!')");
+ public void setBar(String bar) {
+ this.bar = bar;
+ }
+ public String where(String name) {
+ return "Hi " + name + " we are going to " + bar;
}
}
diff --git a/integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyResource.java b/integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyResource.java
similarity index 73%
rename from integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyResource.java
rename to integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyResource.java
index a953e27812..2732e8ccc1 100644
--- a/integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyResource.java
+++ b/integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyResource.java
@@ -40,6 +40,30 @@ public class GroovyResource {
@Inject
ProducerTemplate producerTemplate;
+ @POST
+ @Path("/hello")
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.TEXT_PLAIN)
+ public String hello(String message) {
+ return producerTemplate.requestBody("direct:groovyHello", message, String.class);
+ }
+
+ @POST
+ @Path("/hi")
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.TEXT_PLAIN)
+ public String hi(String message) {
+ return producerTemplate.requestBody("direct:groovyHi", message, String.class);
+ }
+
+ @POST
+ @Path("/predicate")
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.TEXT_PLAIN)
+ public String predicate(String message) {
+ return producerTemplate.requestBody("direct:predicate", Integer.valueOf(message), String.class);
+ }
+
@Path("/route/{route}")
@POST
@Consumes(MediaType.TEXT_PLAIN)
diff --git a/integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java b/integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java
similarity index 63%
rename from integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java
rename to integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java
index 59c03d1ec0..4fd526812b 100644
--- a/integration-tests-jvm/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java
+++ b/integration-tests/groovy/src/main/java/org/apache/camel/quarkus/component/groovy/it/GroovyRoutes.java
@@ -23,6 +23,24 @@ public class GroovyRoutes extends RouteBuilder {
@Override
public void configure() {
+ routeTemplate("whereTo")
+ .templateParameter("bar")
+ .templateBean("myBar", "groovy", "resource:classpath:bean.txt")
+ .from("kamelet:source")
+ .to("bean:{{myBar}}");
+
+ from("direct:groovyHi")
+ .kamelet("whereTo?bar=Shamrock");
+
+ from("direct:groovyHello")
+ .transform().groovy("\"Hello \" + body + \" from Groovy!\"");
+ from("direct:predicate")
+ .choice()
+ .when().groovy("((int) body) / 2 > 10")
+ .setBody().constant("High").endChoice()
+ .otherwise()
+ .setBody().constant("Low").endChoice();
+
from("direct:scriptGroovy")
.script()
.groovy("exchange.getMessage().setBody('Hello ' + exchange.getMessage().getBody(String.class) + ' from Groovy!')");
diff --git a/integration-tests/groovy/src/main/resources/bean.txt b/integration-tests/groovy/src/main/resources/bean.txt
new file mode 100644
index 0000000000..eb19a8a2d9
--- /dev/null
+++ b/integration-tests/groovy/src/main/resources/bean.txt
@@ -0,0 +1,4 @@
+def bean = new org.apache.camel.quarkus.component.groovy.it.GroovyBean()
+// rtc is RouteTemplateContext
+bean.setBar(rtc.getProperty('bar', String.class))
+return bean
diff --git a/integration-tests-jvm/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java b/integration-tests/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyIT.java
similarity index 61%
copy from integration-tests-jvm/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java
copy to integration-tests/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyIT.java
index 258c35f43c..68c7733865 100644
--- a/integration-tests-jvm/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java
+++ b/integration-tests/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyIT.java
@@ -16,24 +16,9 @@
*/
package org.apache.camel.quarkus.component.groovy.it;
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.RestAssured;
-import io.restassured.http.ContentType;
-import org.hamcrest.Matchers;
-import org.junit.jupiter.api.Test;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
-@QuarkusTest
-class GroovyTest {
-
- @Test
- public void script() {
- RestAssured.given()
- .contentType(ContentType.TEXT)
- .body("world")
- .post("/groovy/route/scriptGroovy")
- .then()
- .statusCode(200)
- .body(Matchers.is("Hello world from Groovy!"));
- }
+@QuarkusIntegrationTest
+class GroovyIT extends GroovyTest {
}
diff --git a/integration-tests-jvm/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java b/integration-tests/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java
similarity index 55%
rename from integration-tests-jvm/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java
rename to integration-tests/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java
index 258c35f43c..1a4dc1a3a1 100644
--- a/integration-tests-jvm/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java
+++ b/integration-tests/groovy/src/test/java/org/apache/camel/quarkus/component/groovy/it/GroovyTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.quarkus.component.groovy.it;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
+import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
@@ -26,7 +27,47 @@ import org.junit.jupiter.api.Test;
class GroovyTest {
@Test
- public void script() {
+ void groovyHello() {
+ RestAssured.given()
+ .body("Will Smith")
+ .post("/groovy/hello")
+ .then()
+ .statusCode(200)
+ .body(CoreMatchers.is("Hello Will Smith from Groovy!"));
+ }
+
+ @Test
+ void groovyHi() {
+ RestAssured.given()
+ .body("Jack")
+ .post("/groovy/hi")
+ .then()
+ .statusCode(200)
+ .body(CoreMatchers.is("Hi Jack we are going to Shamrock"));
+ }
+
+ @Test
+ void groovyHigh() {
+ RestAssured.given()
+ .body("45")
+ .post("/groovy/predicate")
+ .then()
+ .statusCode(200)
+ .body(CoreMatchers.is("High"));
+ }
+
+ @Test
+ void groovyLow() {
+ RestAssured.given()
+ .body("13")
+ .post("/groovy/predicate")
+ .then()
+ .statusCode(200)
+ .body(CoreMatchers.is("Low"));
+ }
+
+ @Test
+ void script() {
RestAssured.given()
.contentType(ContentType.TEXT)
.body("world")
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 48a9d0431e..ae81c5fc50 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -103,6 +103,7 @@
<module>google-storage</module>
<module>graphql</module>
<module>grok</module>
+ <module>groovy</module>
<module>groovy-dsl</module>
<module>grpc</module>
<!--<module>hazelcast</module> https://github.com/apache/camel-quarkus/issues/4498 -->
diff --git a/tooling/scripts/test-categories.yaml b/tooling/scripts/test-categories.yaml
index ec46b0fad7..fa42528ea9 100644
--- a/tooling/scripts/test-categories.yaml
+++ b/tooling/scripts/test-categories.yaml
@@ -145,6 +145,7 @@ group-10:
- consul
- digitalocean
- fop
+ - groovy
- joor
- kotlin
- mail