You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/10/05 08:05:49 UTC
[camel] branch main updated: CAMEL-19952: camel-core-model - Bean should be able to be created from an inlined script (for advanced use-cases) (#11647)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 0542da0a4f5 CAMEL-19952: camel-core-model - Bean should be able to be created from an inlined script (for advanced use-cases) (#11647)
0542da0a4f5 is described below
commit 0542da0a4f535eaf841aab3d4c18e6aa2263f3d1
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Oct 5 10:05:43 2023 +0200
CAMEL-19952: camel-core-model - Bean should be able to be created from an inlined script (for advanced use-cases) (#11647)
---
.../apache/camel/catalog/schemas/camel-spring.xsd | 2 +
.../camel/model/app/RegistryBeanDefinition.java | 22 ++++++
.../java/org/apache/camel/xml/in/ModelParser.java | 2 +
.../java/org/apache/camel/xml/out/ModelWriter.java | 2 +
.../org/apache/camel/xml/LwModelToXMLDumper.java | 17 ++++-
.../camel/xml/jaxb/JaxbModelToXMLDumper.java | 17 ++++-
.../org/apache/camel/yaml/out/ModelWriter.java | 2 +
.../org/apache/camel/yaml/LwModelToYAMLDumper.java | 6 ++
dsl/camel-xml-io-dsl/pom.xml | 5 ++
.../src/main/docs/java-xml-io-dsl.adoc | 19 +++++
.../camel/dsl/xml/io/XmlRoutesBuilderLoader.java | 81 +++++++++++++++-----
.../apache/camel/dsl/xml/io/XmlLoadAppTest.java | 25 +++++++
.../org/apache/camel/dsl/xml/io/camel-app9.xml | 39 ++++++++++
.../dsl/yaml/deserializers/ModelDeserializers.java | 12 +++
.../dsl/yaml/deserializers/BeansDeserializer.java | 87 +++++++++++++++++-----
.../generated/resources/schema/camelYamlDsl.json | 6 ++
.../camel-yaml-dsl/src/main/docs/yaml-dsl.adoc | 20 +++++
.../org/apache/camel/dsl/yaml/BeansTest.groovy | 18 +++++
18 files changed, 336 insertions(+), 46 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index d94d85f3d8d..b4a6bce6207 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -13600,6 +13600,7 @@ org.apache.camel.builder.RouteBuilder.
<xs:sequence>
<xs:element minOccurs="0" name="constructors" type="tns:beanConstructorsDefinition"/>
<xs:element minOccurs="0" name="properties" type="tns:beanPropertiesDefinition"/>
+ <xs:element minOccurs="0" name="script" type="xs:string"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="type" type="xs:string" use="required"/>
@@ -13607,6 +13608,7 @@ org.apache.camel.builder.RouteBuilder.
<xs:attribute name="destroyMethod" type="xs:string"/>
<xs:attribute name="factoryMethod" type="xs:string"/>
<xs:attribute name="factoryBean" type="xs:string"/>
+ <xs:attribute name="scriptLanguage" type="xs:string"/>
</xs:complexType>
<xs:complexType name="beanConstructorsDefinition">
<xs:sequence>
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java
index ad764a8aa71..943d89b0ae3 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java
@@ -54,12 +54,18 @@ public class RegistryBeanDefinition implements ResourceAware {
private String factoryMethod;
@XmlAttribute
private String factoryBean;
+ @XmlAttribute
+ @Metadata(label = "advanced")
+ private String scriptLanguage;
@XmlElement(name = "constructors")
@XmlJavaTypeAdapter(BeanConstructorsAdapter.class)
private Map<Integer, Object> constructors;
@XmlElement(name = "properties")
@XmlJavaTypeAdapter(BeanPropertiesAdapter.class)
private Map<String, Object> properties;
+ @XmlElement(name = "script")
+ @Metadata(label = "advanced")
+ private String script;
public String getName() {
return name;
@@ -125,6 +131,22 @@ public class RegistryBeanDefinition implements ResourceAware {
this.properties = properties;
}
+ public String getScriptLanguage() {
+ return scriptLanguage;
+ }
+
+ public void setScriptLanguage(String scriptLanguage) {
+ this.scriptLanguage = scriptLanguage;
+ }
+
+ public void setScript(String script) {
+ this.script = script;
+ }
+
+ public String getScript() {
+ return script;
+ }
+
@Override
public Resource getResource() {
return resource;
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index 75abe87c86b..a7483c74b4f 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -1639,6 +1639,7 @@ public class ModelParser extends BaseParser {
case "factoryMethod": def.setFactoryMethod(val); break;
case "initMethod": def.setInitMethod(val); break;
case "name": def.setName(val); break;
+ case "scriptLanguage": def.setScriptLanguage(val); break;
case "type": def.setType(val); break;
default: return false;
}
@@ -1647,6 +1648,7 @@ public class ModelParser extends BaseParser {
switch (key) {
case "constructors": def.setConstructors(new BeanConstructorsAdapter().unmarshal(doParseBeanConstructorsDefinition())); break;
case "properties": def.setProperties(new BeanPropertiesAdapter().unmarshal(doParseBeanPropertiesDefinition())); break;
+ case "script": def.setScript(doParseText()); break;
default: return false;
}
return true;
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
index 3085681d84a..a88d2559a1c 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
@@ -2568,11 +2568,13 @@ public class ModelWriter extends BaseWriter {
startElement(name);
doWriteAttribute("factoryMethod", def.getFactoryMethod());
doWriteAttribute("initMethod", def.getInitMethod());
+ doWriteAttribute("scriptLanguage", def.getScriptLanguage());
doWriteAttribute("name", def.getName());
doWriteAttribute("destroyMethod", def.getDestroyMethod());
doWriteAttribute("type", def.getType());
doWriteAttribute("factoryBean", def.getFactoryBean());
doWriteElement("constructors", new BeanConstructorsAdapter().marshal(def.getConstructors()), this::doWriteBeanConstructorsDefinition);
+ doWriteElement("script", def.getScript(), this::doWriteString);
doWriteElement("properties", new BeanPropertiesAdapter().marshal(def.getProperties()), this::doWriteBeanPropertiesDefinition);
endElement(name);
}
diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
index 9ad46e95510..0ca0a23cc76 100644
--- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
+++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
@@ -326,16 +326,25 @@ public class LwModelToXMLDumper implements ModelToXMLDumper {
}
buffer.write(String.format(" <bean name=\"%s\" type=\"%s\"", b.getName(), type));
if (b.getFactoryBean() != null) {
- buffer.write(String.format(" factory-bean=\"%s\"", b.getFactoryBean()));
+ buffer.write(String.format(" factoryBean=\"%s\"", b.getFactoryBean()));
}
if (b.getFactoryMethod() != null) {
- buffer.write(String.format(" factory-method=\"%s\"", b.getFactoryMethod()));
+ buffer.write(String.format(" factoryMethod=\"%s\"", b.getFactoryMethod()));
}
if (b.getInitMethod() != null) {
- buffer.write(String.format(" init-method=\"%s\"", b.getInitMethod()));
+ buffer.write(String.format(" initMethod=\"%s\"", b.getInitMethod()));
}
if (b.getDestroyMethod() != null) {
- buffer.write(String.format(" destroy-method=\"%s\"", b.getDestroyMethod()));
+ buffer.write(String.format(" destroyMethod=\"%s\"", b.getDestroyMethod()));
+ }
+ if (b.getScriptLanguage() != null) {
+ buffer.write(String.format(" scriptLanguage=\"%s\"", b.getScriptLanguage()));
+ }
+ if (b.getScript() != null) {
+ buffer.write(String.format(" <script>%n"));
+ buffer.write(b.getScript());
+ buffer.write("\n");
+ buffer.write(String.format(" </script>%n"));
}
buffer.write(">\n");
if (b.getConstructors() != null && !b.getConstructors().isEmpty()) {
diff --git a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
index 6e6a1abe149..1c6c67f124b 100644
--- a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
+++ b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
@@ -339,16 +339,25 @@ public class JaxbModelToXMLDumper implements ModelToXMLDumper {
}
buffer.write(String.format(" <bean name=\"%s\" type=\"%s\"", b.getName(), type));
if (b.getFactoryBean() != null) {
- buffer.write(String.format(" factory-bean=\"%s\"", b.getFactoryBean()));
+ buffer.write(String.format(" factoryBean=\"%s\"", b.getFactoryBean()));
}
if (b.getFactoryMethod() != null) {
- buffer.write(String.format(" factory-method=\"%s\"", b.getFactoryMethod()));
+ buffer.write(String.format(" factoryMethod=\"%s\"", b.getFactoryMethod()));
}
if (b.getInitMethod() != null) {
- buffer.write(String.format(" init-method=\"%s\"", b.getInitMethod()));
+ buffer.write(String.format(" initMethod=\"%s\"", b.getInitMethod()));
}
if (b.getDestroyMethod() != null) {
- buffer.write(String.format(" destroy-method=\"%s\"", b.getDestroyMethod()));
+ buffer.write(String.format(" destroyMethod=\"%s\"", b.getDestroyMethod()));
+ }
+ if (b.getScriptLanguage() != null) {
+ buffer.write(String.format(" scriptLanguage=\"%s\"", b.getScriptLanguage()));
+ }
+ if (b.getScript() != null) {
+ buffer.write(String.format(" <script>%n"));
+ buffer.write(b.getScript());
+ buffer.write("\n");
+ buffer.write(String.format(" </script>%n"));
}
buffer.write(">\n");
if (b.getConstructors() != null && !b.getConstructors().isEmpty()) {
diff --git a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
index 82e928bc44c..43aadc53dfb 100644
--- a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
+++ b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
@@ -2568,11 +2568,13 @@ public class ModelWriter extends BaseWriter {
startElement(name);
doWriteAttribute("factoryMethod", def.getFactoryMethod());
doWriteAttribute("initMethod", def.getInitMethod());
+ doWriteAttribute("scriptLanguage", def.getScriptLanguage());
doWriteAttribute("name", def.getName());
doWriteAttribute("destroyMethod", def.getDestroyMethod());
doWriteAttribute("type", def.getType());
doWriteAttribute("factoryBean", def.getFactoryBean());
doWriteElement("constructors", new BeanConstructorsAdapter().marshal(def.getConstructors()), this::doWriteBeanConstructorsDefinition);
+ doWriteElement("script", def.getScript(), this::doWriteString);
doWriteElement("properties", new BeanPropertiesAdapter().marshal(def.getProperties()), this::doWriteBeanPropertiesDefinition);
endElement(name);
}
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
index 46decad3e35..5268f4d87e9 100644
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
+++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
@@ -326,6 +326,12 @@ public class LwModelToYAMLDumper implements ModelToYAMLDumper {
if (b.getDestroyMethod() != null) {
buffer.write(String.format(" destroyMethod: \"%s\"%n", b.getDestroyMethod()));
}
+ if (b.getScriptLanguage() != null) {
+ buffer.write(String.format(" scriptLanguage: \"%s\"%n", b.getScriptLanguage()));
+ }
+ if (b.getScript() != null) {
+ buffer.write(String.format(" script: \"%s\"%n", b.getScript()));
+ }
if (b.getConstructors() != null && !b.getConstructors().isEmpty()) {
buffer.write(String.format(" constructors:%n"));
int counter = 0;
diff --git a/dsl/camel-xml-io-dsl/pom.xml b/dsl/camel-xml-io-dsl/pom.xml
index 814444a0c60..7bf564d9685 100644
--- a/dsl/camel-xml-io-dsl/pom.xml
+++ b/dsl/camel-xml-io-dsl/pom.xml
@@ -106,6 +106,11 @@
<artifactId>camel-seda</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-groovy</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.assertj</groupId>
diff --git a/dsl/camel-xml-io-dsl/src/main/docs/java-xml-io-dsl.adoc b/dsl/camel-xml-io-dsl/src/main/docs/java-xml-io-dsl.adoc
index b985c67c4df..138021c6990 100644
--- a/dsl/camel-xml-io-dsl/src/main/docs/java-xml-io-dsl.adoc
+++ b/dsl/camel-xml-io-dsl/src/main/docs/java-xml-io-dsl.adoc
@@ -222,6 +222,25 @@ public class MyHelper {
NOTE: The factory method must be `public static`.
+=== Creating beans using script language
+
+For advanced use-cases then Camel allows to inline a script language, such as groovy, java, javascript, etc, to create the bean.
+This gives flexibility to use a bit of programming to create and configure the bean.
+
+[source,xml]
+----
+ <bean name="myBean" type="com.acme.MyBean" scriptLanguage="groovy">
+ <script>
+ // some groovy script here to create the bean
+ bean = ...
+ ...
+ return bean
+ </script>
+ </bean>
+----
+
+NOTE: When using `script` then constructors and factory bean/method is not in use
+
=== Using init and destroy methods on beans
Sometimes beans need to do some initialization and cleanup work before a bean is ready to be used.
diff --git a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
index 9fa17215c3a..2b3d858b849 100644
--- a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
+++ b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.xml.io;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -32,6 +33,9 @@ import org.w3c.dom.Document;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.NoSuchBeanException;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.RouteConfigurationBuilder;
@@ -49,11 +53,15 @@ import org.apache.camel.model.app.BeansDefinition;
import org.apache.camel.model.app.RegistryBeanDefinition;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.RestsDefinition;
+import org.apache.camel.spi.ExchangeFactory;
+import org.apache.camel.spi.Language;
import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.ScriptingLanguage;
import org.apache.camel.spi.annotations.RoutesLoader;
import org.apache.camel.support.CachedResource;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.support.ScriptHelper;
import org.apache.camel.support.scan.PackageScanHelper;
import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.StringHelper;
@@ -339,34 +347,69 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
}
public Object newInstance(RegistryBeanDefinition def, CamelContext context) throws Exception {
+ Object target;
String type = def.getType();
if (!type.startsWith("#")) {
type = "#class:" + type;
}
- // factory bean/method
- if (def.getFactoryBean() != null && def.getFactoryMethod() != null) {
- type = type + "#" + def.getFactoryBean() + ":" + def.getFactoryMethod();
- } else if (def.getFactoryMethod() != null) {
- type = type + "#" + def.getFactoryMethod();
- }
- // property binding support has constructor arguments as part of the type
- StringJoiner ctr = new StringJoiner(", ");
- if (def.getConstructors() != null && !def.getConstructors().isEmpty()) {
- // need to sort constructor args based on index position
- Map<Integer, Object> sorted = new TreeMap<>(def.getConstructors());
- for (Object val : sorted.values()) {
- String text = val.toString();
- if (!StringHelper.isQuoted(text)) {
- text = "\"" + text + "\"";
+ if (def.getScriptLanguage() != null && def.getScript() != null) {
+ // create bean via the script
+ final Language lan = context.resolveLanguage(def.getScriptLanguage());
+ final ScriptingLanguage slan = lan instanceof ScriptingLanguage ? (ScriptingLanguage) lan : null;
+ String fqn = def.getType();
+ if (fqn.startsWith("#class:")) {
+ fqn = fqn.substring(7);
+ }
+ final Class<?> clazz = context.getClassResolver().resolveMandatoryClass(fqn);
+ if (slan != null) {
+ // scripting language should be evaluated with context as binding
+ Map<String, Object> bindings = new HashMap<>();
+ bindings.put("context", context);
+ target = slan.evaluate(def.getScript(), bindings, clazz);
+ } else {
+ // exchange based languages needs a dummy exchange to be evaluated
+ ExchangeFactory ef = context.getCamelContextExtension().getExchangeFactory();
+ Exchange dummy = ef.create(false);
+ try {
+ String text = ScriptHelper.resolveOptionalExternalScript(context, dummy, def.getScript());
+ Expression exp = lan.createExpression(text);
+ target = exp.evaluate(dummy, clazz);
+ } finally {
+ ef.release(dummy);
}
- ctr.add(text);
}
- type = type + "(" + ctr + ")";
- }
- final Object target = PropertyBindingSupport.resolveBean(context, type);
+ // a bean must be created
+ if (target == null) {
+ throw new NoSuchBeanException(def.getName(), "Creating bean using script returned null");
+ }
+
+ } else {
+ // factory bean/method
+ if (def.getFactoryBean() != null && def.getFactoryMethod() != null) {
+ type = type + "#" + def.getFactoryBean() + ":" + def.getFactoryMethod();
+ } else if (def.getFactoryMethod() != null) {
+ type = type + "#" + def.getFactoryMethod();
+ }
+ // property binding support has constructor arguments as part of the type
+ StringJoiner ctr = new StringJoiner(", ");
+ if (def.getConstructors() != null && !def.getConstructors().isEmpty()) {
+ // need to sort constructor args based on index position
+ Map<Integer, Object> sorted = new TreeMap<>(def.getConstructors());
+ for (Object val : sorted.values()) {
+ String text = val.toString();
+ if (!StringHelper.isQuoted(text)) {
+ text = "\"" + text + "\"";
+ }
+ ctr.add(text);
+ }
+ type = type + "(" + ctr + ")";
+ }
+
+ target = PropertyBindingSupport.resolveBean(context, type);
+ }
if (def.getProperties() != null && !def.getProperties().isEmpty()) {
PropertyBindingSupport.setPropertiesOnTarget(context, target, def.getProperties());
diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
index d5984129b8d..4039d20dd97 100644
--- a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
+++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
@@ -227,4 +227,29 @@ public class XmlLoadAppTest {
}
}
+ @Test
+ public void testLoadCamelAppWithBeanScript() throws Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ context.start();
+
+ Resource resource = PluginHelper.getResourceLoader(context).resolveResource(
+ "/org/apache/camel/dsl/xml/io/camel-app9.xml");
+
+ RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+ routesLoader.preParseRoute(resource, false);
+ routesLoader.loadRoutes(resource);
+
+ assertNotNull(context.getRoute("r9"), "Loaded r9 route should be there");
+ assertEquals(1, context.getRoutes().size());
+
+ // test that loaded route works
+ MockEndpoint y8 = context.getEndpoint("mock:y9", MockEndpoint.class);
+ y8.expectedBodiesReceived("Hi World from groovy Uranus");
+ context.createProducerTemplate().sendBody("direct:x9", "I'm Uranus");
+ y8.assertIsSatisfied();
+
+ context.stop();
+ }
+ }
+
}
diff --git a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app9.xml b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app9.xml
new file mode 100644
index 00000000000..54ce75f60ad
--- /dev/null
+++ b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app9.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<camel xmlns="http://camel.apache.org/schema/spring" xmlns:s="http://www.springframework.org/schema/beans">
+
+ <bean name="xml-bean-from-registry" type="org.apache.camel.dsl.xml.io.beans.Greeter"
+ scriptLanguage="groovy">
+ <script>
+ b = new org.apache.camel.dsl.xml.io.beans.Greeter()
+ m = new org.apache.camel.dsl.xml.io.beans.GreeterMessage()
+ m.msg = 'Hi World from groovy'
+ b.message = m
+ return b
+ </script>
+ </bean>
+
+ <route id="r9">
+ <from uri="direct:x9"/>
+ <process ref="xml-bean-from-registry"/>
+ <to uri="mock:y9"/>
+ </route>
+
+</camel>
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index c0b4a019284..98270b33ddc 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -12361,6 +12361,8 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
@YamlProperty(name = "init-method", type = "string"),
@YamlProperty(name = "name", type = "string", required = true),
@YamlProperty(name = "properties", type = "object"),
+ @YamlProperty(name = "script", type = "string"),
+ @YamlProperty(name = "script-language", type = "string"),
@YamlProperty(name = "type", type = "string", required = true)
}
)
@@ -12413,6 +12415,16 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
target.setProperties(val);
break;
}
+ case "script": {
+ String val = asText(node);
+ target.setScript(val);
+ break;
+ }
+ case "script-language": {
+ String val = asText(node);
+ target.setScriptLanguage(val);
+ break;
+ }
case "type": {
String val = asText(node);
target.setType(val);
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
index 4224881e130..ca2eddc7879 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
@@ -17,6 +17,7 @@
package org.apache.camel.dsl.yaml.deserializers;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -26,16 +27,23 @@ import java.util.StringJoiner;
import java.util.TreeMap;
import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.NoSuchBeanException;
import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
import org.apache.camel.model.Model;
import org.apache.camel.model.app.RegistryBeanDefinition;
import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.ExchangeFactory;
+import org.apache.camel.spi.Language;
+import org.apache.camel.spi.ScriptingLanguage;
import org.apache.camel.spi.annotations.YamlIn;
import org.apache.camel.spi.annotations.YamlProperty;
import org.apache.camel.spi.annotations.YamlType;
import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.support.ScriptHelper;
import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StringHelper;
@@ -79,6 +87,10 @@ public class BeansDeserializer extends YamlDeserializerSupport implements Constr
if (!bean.getType().startsWith("#class:")) {
bean.setType("#class:" + bean.getType());
}
+ if (bean.getScriptLanguage() != null || bean.getScript() != null) {
+ ObjectHelper.notNull(bean.getScriptLanguage(), "The bean script language must be set");
+ ObjectHelper.notNull(bean.getScript(), "The bean script must be set");
+ }
// due to yaml-dsl is pre parsing beans which gets created eager
// and then later beans can be parsed again such as from Camel K Integration CRD files
@@ -99,32 +111,69 @@ public class BeansDeserializer extends YamlDeserializerSupport implements Constr
}
public Object newInstance(RegistryBeanDefinition def, CamelContext context) throws Exception {
+ Object target;
String type = def.getType();
- // factory bean/method
- if (def.getFactoryBean() != null && def.getFactoryMethod() != null) {
- type = type + "#" + def.getFactoryBean() + ":" + def.getFactoryMethod();
- } else if (def.getFactoryMethod() != null) {
- type = type + "#" + def.getFactoryMethod();
- }
- // property binding support has constructor arguments as part of the type
- StringJoiner ctr = new StringJoiner(", ");
- if (def.getConstructors() != null && !def.getConstructors().isEmpty()) {
- // need to sort constructor args based on index position
- Map<Integer, Object> sorted = new TreeMap<>(def.getConstructors());
- for (Object val : sorted.values()) {
- String text = val.toString();
- if (!StringHelper.isQuoted(text)) {
- text = "\"" + text + "\"";
+ // script bean
+ if (def.getScriptLanguage() != null && def.getScript() != null) {
+ // create bean via the script
+ final Language lan = context.resolveLanguage(def.getScriptLanguage());
+ final ScriptingLanguage slan = lan instanceof ScriptingLanguage ? (ScriptingLanguage) lan : null;
+ String fqn = def.getType();
+ if (fqn.startsWith("#class:")) {
+ fqn = fqn.substring(7);
+ }
+ final Class<?> clazz = context.getClassResolver().resolveMandatoryClass(fqn);
+ if (slan != null) {
+ // scripting language should be evaluated with context as binding
+ Map<String, Object> bindings = new HashMap<>();
+ bindings.put("context", context);
+ target = slan.evaluate(def.getScript(), bindings, clazz);
+ } else {
+ // exchange based languages needs a dummy exchange to be evaluated
+ ExchangeFactory ef = context.getCamelContextExtension().getExchangeFactory();
+ Exchange dummy = ef.create(false);
+ try {
+ String text = ScriptHelper.resolveOptionalExternalScript(context, dummy, def.getScript());
+ Expression exp = lan.createExpression(text);
+ target = exp.evaluate(dummy, clazz);
+ } finally {
+ ef.release(dummy);
}
- ctr.add(text);
}
- type = type + "(" + ctr + ")";
- }
- final Object target = PropertyBindingSupport.resolveBean(context, type);
+ // a bean must be created
+ if (target == null) {
+ throw new NoSuchBeanException(def.getName(), "Creating bean using script returned null");
+ }
+
+ } else {
+ // factory bean/method
+ if (def.getFactoryBean() != null && def.getFactoryMethod() != null) {
+ type = type + "#" + def.getFactoryBean() + ":" + def.getFactoryMethod();
+ } else if (def.getFactoryMethod() != null) {
+ type = type + "#" + def.getFactoryMethod();
+ }
+ // property binding support has constructor arguments as part of the type
+ StringJoiner ctr = new StringJoiner(", ");
+ if (def.getConstructors() != null && !def.getConstructors().isEmpty()) {
+ // need to sort constructor args based on index position
+ Map<Integer, Object> sorted = new TreeMap<>(def.getConstructors());
+ for (Object val : sorted.values()) {
+ String text = val.toString();
+ if (!StringHelper.isQuoted(text)) {
+ text = "\"" + text + "\"";
+ }
+ ctr.add(text);
+ }
+ type = type + "(" + ctr + ")";
+ }
+
+ target = PropertyBindingSupport.resolveBean(context, type);
+ }
+ // set optional properties on created bean
if (def.getProperties() != null && !def.getProperties().isEmpty()) {
PropertyBindingSupport.setPropertiesOnTarget(context, target, def.getProperties());
}
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 15d6837cf97..a31f5382127 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -8044,6 +8044,12 @@
"properties" : {
"type" : "object"
},
+ "script" : {
+ "type" : "string"
+ },
+ "scriptLanguage" : {
+ "type" : "string"
+ },
"type" : {
"type" : "string"
}
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc
index f52b67e8016..7886335c75d 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc
@@ -267,6 +267,26 @@ public class MyHelper {
NOTE: The factory method must be `public static`.
+=== Creating beans using script language
+
+For advanced use-cases then Camel allows to inline a script language, such as groovy, java, javascript, etc, to create the bean.
+This gives flexibility to use a bit of programming to create and configure the bean.
+
+[source,yaml]
+----
+- beans:
+ - name: myBean
+ type: com.acme.MyBean
+ scriptLanguage: groovy
+ script: >
+ // some groovy script here to create the bean
+ bean = ...
+ ...
+ return bean
+----
+
+NOTE: When using `script` then constructors and factory bean/method is not in use
+
=== Using init and destroy methods on beans
Sometimes beans need to do some initialization and cleanup work before a bean is ready to be used.
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
index e7e2185ddef..194b6bf9595 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
@@ -22,6 +22,7 @@ import org.apache.camel.dsl.yaml.support.model.MyCtrBean
import org.apache.camel.dsl.yaml.support.model.MyDestroyBean
import org.apache.camel.dsl.yaml.support.model.MyFacBean
import org.apache.camel.dsl.yaml.support.model.MyFacHelper
+import org.apache.camel.dsl.yaml.support.model.MyUppercaseProcessor
class BeansTest extends YamlTestSupport {
@@ -211,4 +212,21 @@ class BeansTest extends YamlTestSupport {
MyDestroyBean.destroyCalled.get() == true
}
+ def "beans with script"() {
+ when:
+ loadRoutes """
+ - beans:
+ - name: myBean
+ type: ${MyBean.class.name}
+ scriptLanguage: groovy
+ script: "var b = new ${MyBean.class.name}(); b.field1 = 'script1'; b.field2 = 'script2'; return b"
+ """
+
+ then:
+ with(context.registry.lookupByName('myBean'), MyBean) {
+ it.field1 == 'script1'
+ it.field2 == 'script2'
+ }
+ }
+
}