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:12 UTC

[camel-quarkus] 05/34: 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 c1d78922e3bfe587d337bc1d89509845f38693be
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 87cd5adc97..7e9ad015a0 100644
--- a/extensions-jvm/pom.xml
+++ b/extensions-jvm/pom.xml
@@ -67,7 +67,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 3c6c06e714..a5f7c51dbc 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 7b6ddbd759..4195d56634 100644
--- a/integration-tests-jvm/pom.xml
+++ b/integration-tests-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/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 33e9b77d31..08364d63e9 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 9d1a1e2471..fa82d318cb 100644
--- a/tooling/scripts/test-categories.yaml
+++ b/tooling/scripts/test-categories.yaml
@@ -150,6 +150,7 @@ group-10:
   - consul
   - digitalocean
   - fop
+  - groovy
   - joor
   - kotlin
   - mail