This is an automated email from the ASF dual-hosted git repository. github-bot pushed a commit to branch camel-main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 10e8c403b91f653309f6f8545cf5ec0651dc571c Author: Nicolas Filotto <nfilo...@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 7f609c8c28..e9701c4225 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -114,6 +114,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 479d31ab85..fd5092906b 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -101,6 +101,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