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 <claus.ib...@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' + } + } + }