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 2e7578c9426 Builder class (#12270)
2e7578c9426 is described below
commit 2e7578c9426bb327e0ca8c43b4a93063d70b946e
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Nov 30 11:29:18 2023 +0100
Builder class (#12270)
* CAMEL-20130: camel-core - PropertyBuilderSupport add support for fluent
builder classes.
* CAMEL-20130: camel-yaml-dsl - Allow to use bean builder classes when
defining beans
* CAMEL-20130: camel-xml-io-dsl - Allow to use bean builder classes when
defining beans
* CAMEL-20130: camel-yaml-dsl - Allow to use bean builder classes when
defining beans
* CAMEL-20130: camel-yaml-dsl - Allow to use bean builder classes when
defining beans
---
.../org/apache/camel/catalog/models-app/bean.json | 10 +-
.../resources/org/apache/camel/model/app/bean.json | 10 +-
.../camel/model/app/RegistryBeanDefinition.java | 29 ++++++
.../PropertyBindingSupportBuildMethodTest.java | 104 +++++++++++++++++++++
.../camel/support/PropertyBindingSupport.java | 31 ++++++
.../java/org/apache/camel/xml/in/ModelParser.java | 2 +
.../java/org/apache/camel/xml/out/ModelWriter.java | 2 +
.../org/apache/camel/xml/LwModelToXMLDumper.java | 6 ++
.../camel/xml/jaxb/JaxbModelToXMLDumper.java | 6 ++
.../org/apache/camel/yaml/out/ModelWriter.java | 2 +
.../org/apache/camel/yaml/LwModelToYAMLDumper.java | 6 ++
.../modules/ROOT/pages/property-binding.adoc | 51 ++++++++++
.../src/main/docs/java-xml-io-dsl.adoc | 21 +++++
.../camel/dsl/xml/io/XmlRoutesBuilderLoader.java | 14 ++-
.../apache/camel/dsl/xml/io/XmlLoadAppTest.java | 31 +++++-
.../org/apache/camel/dsl/xml/io/beans/MyBean.java | 35 +++++++
.../camel/dsl/xml/io/beans/MyBeanBuilder.java | 44 +++++++++
.../org/apache/camel/dsl/xml/io/camel-app10.xml | 37 ++++++++
.../dsl/yaml/deserializers/ModelDeserializers.java | 12 +++
.../dsl/yaml/deserializers/BeansDeserializer.java | 14 ++-
.../generated/resources/schema/camelYamlDsl.json | 6 ++
.../camel-yaml-dsl/src/main/docs/yaml-dsl.adoc | 22 +++++
.../org/apache/camel/dsl/yaml/BeansTest.groovy | 21 +++++
.../dsl/yaml/support/model/MyBeanBuilder.groovy | 46 +++++++++
24 files changed, 549 insertions(+), 13 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/bean.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/bean.json
index ffb007e78e4..f67b2ec342e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/bean.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/bean.json
@@ -18,9 +18,11 @@
"destroyMethod": { "index": 3, "kind": "attribute", "displayName":
"Destroy Method", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The name of the custom destroy method to invoke on bean
shutdown, such as when Camel is shutting down. The method must have no
arguments, but may throw any exception." },
"factoryMethod": { "index": 4, "kind": "attribute", "displayName":
"Factory Method", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Name of method to invoke when creating the bean via a factory
bean." },
"factoryBean": { "index": 5, "kind": "attribute", "displayName": "Factory
Bean", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Name
of factory bean (bean id) to use for creating the bean." },
- "scriptLanguage": { "index": 6, "kind": "attribute", "displayName":
"Script Language", "label": "advanced", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The script language to use when using inlined
script for creating the bean, such as groovy, java, javascript etc." },
- "constructors": { "index": 7, "kind": "element", "displayName":
"Constructors", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.Integer, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional constructor
arguments for creating the bean. Arguments correspond to specific index of the
constructor argument list, starting from zero." },
- "properties": { "index": 8, "kind": "element", "displayName":
"Properties", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional properties to set
on the created bean." },
- "script": { "index": 9, "kind": "element", "displayName": "Script",
"label": "advanced", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The script to execute that creates the bean when using
scripting languages. If the script use the prefix resource: such as
resource:classpath:com\/foo\/myscript.groovy,
resource:file:\/var\/myscript.groovy, then its loaded from the external
resource." }
+ "builderClass": { "index": 6, "kind": "attribute", "displayName": "Builder
Class", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Fully
qualified class name of builder class to use for creating and configuring the
bean. The builder will use the properties values to configure the bean." },
+ "builderMethod": { "index": 7, "kind": "attribute", "displayName":
"Builder Method", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "build", "description": "Name of method when using builder
class. This method is invoked after configuring to create the actual bean. This
method is often named build (used by default)." },
+ "scriptLanguage": { "index": 8, "kind": "attribute", "displayName":
"Script Language", "label": "advanced", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The script language to use when using inlined
script for creating the bean, such as groovy, java, javascript etc." },
+ "constructors": { "index": 9, "kind": "element", "displayName":
"Constructors", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.Integer, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional constructor
arguments for creating the bean. Arguments correspond to specific index of the
constructor argument list, starting from zero." },
+ "properties": { "index": 10, "kind": "element", "displayName":
"Properties", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional properties to set
on the created bean." },
+ "script": { "index": 11, "kind": "element", "displayName": "Script",
"label": "advanced", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The script to execute that creates the bean when using
scripting languages. If the script use the prefix resource: such as
resource:classpath:com\/foo\/myscript.groovy,
resource:file:\/var\/myscript.groovy, then its loaded from the external
resource." }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/org/apache/camel/model/app/bean.json
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/app/bean.json
index ffb007e78e4..f67b2ec342e 100644
---
a/core/camel-core-model/src/generated/resources/org/apache/camel/model/app/bean.json
+++
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/app/bean.json
@@ -18,9 +18,11 @@
"destroyMethod": { "index": 3, "kind": "attribute", "displayName":
"Destroy Method", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The name of the custom destroy method to invoke on bean
shutdown, such as when Camel is shutting down. The method must have no
arguments, but may throw any exception." },
"factoryMethod": { "index": 4, "kind": "attribute", "displayName":
"Factory Method", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Name of method to invoke when creating the bean via a factory
bean." },
"factoryBean": { "index": 5, "kind": "attribute", "displayName": "Factory
Bean", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Name
of factory bean (bean id) to use for creating the bean." },
- "scriptLanguage": { "index": 6, "kind": "attribute", "displayName":
"Script Language", "label": "advanced", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The script language to use when using inlined
script for creating the bean, such as groovy, java, javascript etc." },
- "constructors": { "index": 7, "kind": "element", "displayName":
"Constructors", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.Integer, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional constructor
arguments for creating the bean. Arguments correspond to specific index of the
constructor argument list, starting from zero." },
- "properties": { "index": 8, "kind": "element", "displayName":
"Properties", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional properties to set
on the created bean." },
- "script": { "index": 9, "kind": "element", "displayName": "Script",
"label": "advanced", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The script to execute that creates the bean when using
scripting languages. If the script use the prefix resource: such as
resource:classpath:com\/foo\/myscript.groovy,
resource:file:\/var\/myscript.groovy, then its loaded from the external
resource." }
+ "builderClass": { "index": 6, "kind": "attribute", "displayName": "Builder
Class", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Fully
qualified class name of builder class to use for creating and configuring the
bean. The builder will use the properties values to configure the bean." },
+ "builderMethod": { "index": 7, "kind": "attribute", "displayName":
"Builder Method", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "build", "description": "Name of method when using builder
class. This method is invoked after configuring to create the actual bean. This
method is often named build (used by default)." },
+ "scriptLanguage": { "index": 8, "kind": "attribute", "displayName":
"Script Language", "label": "advanced", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The script language to use when using inlined
script for creating the bean, such as groovy, java, javascript etc." },
+ "constructors": { "index": 9, "kind": "element", "displayName":
"Constructors", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.Integer, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional constructor
arguments for creating the bean. Arguments correspond to specific index of the
constructor argument list, starting from zero." },
+ "properties": { "index": 10, "kind": "element", "displayName":
"Properties", "required": false, "type": "object", "javaType":
"java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional properties to set
on the created bean." },
+ "script": { "index": 11, "kind": "element", "displayName": "Script",
"label": "advanced", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The script to execute that creates the bean when using
scripting languages. If the script use the prefix resource: such as
resource:classpath:com\/foo\/myscript.groovy,
resource:file:\/var\/myscript.groovy, then its loaded from the external
resource." }
}
}
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 6c1b8da40f4..936978bcfc7 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,6 +54,11 @@ public class RegistryBeanDefinition implements ResourceAware
{
@XmlAttribute
private String factoryBean;
@XmlAttribute
+ private String builderClass;
+ @XmlAttribute
+ @Metadata(defaultValue = "build")
+ private String builderMethod;
+ @XmlAttribute
@Metadata(label = "advanced")
private String scriptLanguage;
@XmlElement(name = "constructors")
@@ -134,6 +139,30 @@ public class RegistryBeanDefinition implements
ResourceAware {
this.factoryBean = factoryBean;
}
+ public String getBuilderClass() {
+ return builderClass;
+ }
+
+ /**
+ * Fully qualified class name of builder class to use for creating and
configuring the bean. The builder will use
+ * the properties values to configure the bean.
+ */
+ public void setBuilderClass(String builderClass) {
+ this.builderClass = builderClass;
+ }
+
+ public String getBuilderMethod() {
+ return builderMethod;
+ }
+
+ /**
+ * Name of method when using builder class. This method is invoked after
configuring to create the actual bean. This
+ * method is often named build (used by default).
+ */
+ public void setBuilderMethod(String builderMethod) {
+ this.builderMethod = builderMethod;
+ }
+
public Map<Integer, Object> getConstructors() {
return constructors;
}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/PropertyBindingSupportBuildMethodTest.java
b/core/camel-main/src/test/java/org/apache/camel/main/PropertyBindingSupportBuildMethodTest.java
new file mode 100644
index 00000000000..507b686f704
--- /dev/null
+++
b/core/camel-main/src/test/java/org/apache/camel/main/PropertyBindingSupportBuildMethodTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.main;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.PropertyBindingSupport;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit test for PropertyBindingSupport
+ */
+public class PropertyBindingSupportBuildMethodTest {
+
+ @Test
+ public void testBuildClass() throws Exception {
+ CamelContext context = new DefaultCamelContext();
+
+ context.start();
+
+ MyDriver driver = PropertyBindingSupport.build()
+ .withCamelContext(context)
+ .withTarget(new MyDriverBuilder())
+ .withFluentBuilder(true)
+ .withProperty("url", "localhost:1234")
+ .withProperty("username", "scott")
+ .withProperty("password", "tiger")
+ .build(MyDriver.class);
+
+ Assertions.assertNotNull(driver);
+ Assertions.assertEquals("localhost:1234", driver.getUrl());
+ Assertions.assertEquals("scott", driver.getUsername());
+ Assertions.assertEquals("tiger", driver.getPassword());
+
+ context.stop();
+ }
+
+ public static class MyDriver {
+
+ private final String url;
+ private final String username;
+ private final String password;
+
+ public MyDriver(String url, String username, String password) {
+ this.url = url;
+ this.username = username;
+ this.password = password;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+ }
+
+ public static class MyDriverBuilder {
+
+ private String url;
+ private String username;
+ private String password;
+
+ public MyDriverBuilder url(String url) {
+ this.url = url;
+ return this;
+ }
+
+ public MyDriverBuilder username(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public MyDriverBuilder password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public MyDriver build() {
+ return new MyDriver(url, username, password);
+ }
+ }
+
+}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
index 7d766645ee1..df8297c3df0 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
@@ -38,6 +38,7 @@ import java.util.stream.Collectors;
import org.apache.camel.CamelContext;
import org.apache.camel.Component;
import org.apache.camel.PropertyBindingException;
+import org.apache.camel.RuntimeCamelException;
import org.apache.camel.spi.BeanIntrospection;
import org.apache.camel.spi.PropertiesComponent;
import org.apache.camel.spi.PropertyConfigurer;
@@ -1874,6 +1875,36 @@ public final class PropertyBindingSupport {
return this;
}
+ /**
+ * Binds the properties to the target object, and builds the output as
the given type, by invoking the build
+ * method (uses build as name)
+ *
+ * @param type the type of the output class
+ */
+ public <T> T build(Class<T> type) {
+ return build(type, "build");
+ }
+
+ /**
+ * Binds the properties to the target object, and builds the output as
the given type, by invoking the build
+ * method (via reflection).
+ *
+ * @param type the type of the output class
+ * @param buildMethod the name of the builder method to invoke
+ */
+ public <T> T build(Class<T> type, String buildMethod) {
+ // first bind
+ bind();
+
+ // then invoke the build method on target via reflection
+ try {
+ Object out = ObjectHelper.invokeMethodSafe(buildMethod,
target);
+ return camelContext.getTypeConverter().convertTo(type, out);
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeException(e);
+ }
+ }
+
/**
* Binds the properties to the target object, and removes the property
that was bound from properties.
*
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 d5ea55476bd..db2af55817d 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
@@ -1645,6 +1645,8 @@ public class ModelParser extends BaseParser {
protected RegistryBeanDefinition doParseRegistryBeanDefinition() throws
IOException, XmlPullParserException {
return doParse(new RegistryBeanDefinition(), (def, key, val) -> {
switch (key) {
+ case "builderClass": def.setBuilderClass(val); break;
+ case "builderMethod": def.setBuilderMethod(val); break;
case "destroyMethod": def.setDestroyMethod(val); break;
case "factoryBean": def.setFactoryBean(val); break;
case "factoryMethod": def.setFactoryMethod(val); break;
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 0b1ae97db63..c8de5e968d0 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
@@ -2585,7 +2585,9 @@ public class ModelWriter extends BaseWriter {
doWriteAttribute("factoryMethod", def.getFactoryMethod());
doWriteAttribute("initMethod", def.getInitMethod());
doWriteAttribute("scriptLanguage", def.getScriptLanguage());
+ doWriteAttribute("builderClass", def.getBuilderClass());
doWriteAttribute("name", def.getName());
+ doWriteAttribute("builderMethod", def.getBuilderMethod());
doWriteAttribute("destroyMethod", def.getDestroyMethod());
doWriteAttribute("type", def.getType());
doWriteAttribute("factoryBean", def.getFactoryBean());
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 0ca0a23cc76..cb14b3e4110 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
@@ -331,6 +331,12 @@ public class LwModelToXMLDumper implements
ModelToXMLDumper {
if (b.getFactoryMethod() != null) {
buffer.write(String.format(" factoryMethod=\"%s\"",
b.getFactoryMethod()));
}
+ if (b.getBuilderClass() != null) {
+ buffer.write(String.format(" builderClass=\"%s\"",
b.getBuilderClass()));
+ }
+ if (b.getBuilderMethod() != null) {
+ buffer.write(String.format(" builderMethod=\"%s\"",
b.getBuilderMethod()));
+ }
if (b.getInitMethod() != null) {
buffer.write(String.format(" initMethod=\"%s\"",
b.getInitMethod()));
}
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 9a0c4440553..5b3cea30587 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
@@ -316,6 +316,12 @@ public class JaxbModelToXMLDumper implements
ModelToXMLDumper {
if (b.getFactoryMethod() != null) {
buffer.write(String.format(" factoryMethod=\"%s\"",
b.getFactoryMethod()));
}
+ if (b.getBuilderClass() != null) {
+ buffer.write(String.format(" builderClass=\"%s\"",
b.getBuilderClass()));
+ }
+ if (b.getBuilderMethod() != null) {
+ buffer.write(String.format(" builderMethod=\"%s\"",
b.getBuilderMethod()));
+ }
if (b.getInitMethod() != null) {
buffer.write(String.format(" initMethod=\"%s\"",
b.getInitMethod()));
}
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 f9de1b056d8..844d01839ee 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
@@ -2585,7 +2585,9 @@ public class ModelWriter extends BaseWriter {
doWriteAttribute("factoryMethod", def.getFactoryMethod());
doWriteAttribute("initMethod", def.getInitMethod());
doWriteAttribute("scriptLanguage", def.getScriptLanguage());
+ doWriteAttribute("builderClass", def.getBuilderClass());
doWriteAttribute("name", def.getName());
+ doWriteAttribute("builderMethod", def.getBuilderMethod());
doWriteAttribute("destroyMethod", def.getDestroyMethod());
doWriteAttribute("type", def.getType());
doWriteAttribute("factoryBean", def.getFactoryBean());
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 5268f4d87e9..e8581cd572b 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
@@ -320,6 +320,12 @@ public class LwModelToYAMLDumper implements
ModelToYAMLDumper {
if (b.getFactoryMethod() != null) {
buffer.write(String.format(" factoryMethod: \"%s\"%n",
b.getFactoryMethod()));
}
+ if (b.getBuilderClass() != null) {
+ buffer.write(String.format(" builderClass: \"%s\"%n",
b.getBuilderClass()));
+ }
+ if (b.getBuilderMethod() != null) {
+ buffer.write(String.format(" builderMethod: \"%s\"%n",
b.getBuilderMethod()));
+ }
if (b.getInitMethod() != null) {
buffer.write(String.format(" initMethod: \"%s\"%n",
b.getInitMethod()));
}
diff --git a/docs/user-manual/modules/ROOT/pages/property-binding.adoc
b/docs/user-manual/modules/ROOT/pages/property-binding.adoc
index 8687eee222c..31d578cebd6 100644
--- a/docs/user-manual/modules/ROOT/pages/property-binding.adoc
+++ b/docs/user-manual/modules/ROOT/pages/property-binding.adoc
@@ -193,6 +193,57 @@
PropertyBindingSupport.build().withCamelContext(context).withTarget(foo)
.bind();
----
+=== Using fluent builder class
+
+When you are in need to configure a bean via _fluent builder class_, such as
the following example:
+
+[source,java]
+----
+public class MyDriverBuilder {
+
+ private String url;
+ private String username;
+ private String password;
+
+ public MyDriverBuilder url(String url) {
+ this.url = url;
+ return this;
+ }
+
+ public MyDriverBuilder username(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public MyDriverBuilder password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public MyDriver build() {
+ return new MyDriver(url, username, password);
+ }
+}
+----
+
+And you want to create an instance of `MyDriver` via the `MyDriverBuilder`
class, then this can be done as follows:
+
+[source,java]
+----
+MyDriver driver = PropertyBindingSupport.build()
+ .withCamelContext(context)
+ .withTarget(new MyDriverBuilder())
+ .withFluentBuilder(true)
+ .withProperty("url", "localhost:1234")
+ .withProperty("username", "scott")
+ .withProperty("password", "tiger")
+ .build(MyDriver.class);
+----
+
+Notice how we use the `build(MyDriver.class)` to build the bean via the target
class `.withTarget(new MyDriverBuilder())`.
+The build method will by default invoke `build` as the builder method, but you
can specify the name, such as `.build(MyDriver.class, "myBuilderMethod");`
+
+
== More details
Property binding is notably used when running Camel in standalone mode with
Camel Main, or using Camel Spring Boot, Camel K,
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 138021c6990..289c0c92ae5 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
@@ -188,6 +188,27 @@ public class MyBean {
NOTE: The factory method must be `public static` and from the same class as
the created class itself.
+=== Creating beans from builder classes
+
+A bean can also be created from another builder class as shown below:
+
+[source,xml]
+----
+ <bean name="myBean" type="com.acme.MyBean"
+ builderClass="com.acme.MyBeanBuilder" builderMethod="createMyBean">
+ <properties>
+ <property key="id" value="123"/>
+ <property key="name" value="Acme"/>
+ </constructors>
+ </bean>
+----
+
+NOTE: The builder class must be `public` and have a no-arg default constructor.
+
+The builder class is then used to create the actual bean by using fluent
builder style configuration.
+So the properties will be set on the builder class, and the bean is created by
invoking the `builderMethod`
+at the end. The invocation of this method is done via Java reflection.
+
=== Creating beans from factory bean
A bean can also be created from a factory bean as shown below:
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 2b3d858b849..e19fb750a58 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
@@ -385,7 +385,19 @@ public class XmlRoutesBuilderLoader extends
RouteBuilderLoaderSupport {
if (target == null) {
throw new NoSuchBeanException(def.getName(), "Creating bean
using script returned null");
}
-
+ } else if (def.getBuilderClass() != null) {
+ // builder class and method
+ Class<?> clazz =
context.getClassResolver().resolveMandatoryClass(def.getBuilderClass());
+ Object builder = context.getInjector().newInstance(clazz);
+ String bm = def.getBuilderMethod() != null ?
def.getBuilderMethod() : "build";
+
+ // create bean via builder and assign as target output
+ target = PropertyBindingSupport.build()
+ .withCamelContext(context)
+ .withTarget(builder)
+ .withRemoveParameters(true)
+ .withProperties(def.getProperties())
+ .build(Object.class, bm);
} else {
// factory bean/method
if (def.getFactoryBean() != null && def.getFactoryMethod() !=
null) {
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 4039d20dd97..83700f09df1 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
@@ -243,10 +243,35 @@ public class XmlLoadAppTest {
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");
+ MockEndpoint y9 = context.getEndpoint("mock:y9",
MockEndpoint.class);
+ y9.expectedBodiesReceived("Hi World from groovy Uranus");
context.createProducerTemplate().sendBody("direct:x9", "I'm
Uranus");
- y8.assertIsSatisfied();
+ y9.assertIsSatisfied();
+
+ context.stop();
+ }
+ }
+
+ @Test
+ public void testLoadCamelAppWithBeanBuilderClass() throws Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ context.start();
+
+ Resource resource =
PluginHelper.getResourceLoader(context).resolveResource(
+ "/org/apache/camel/dsl/xml/io/camel-app10.xml");
+
+ RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+ routesLoader.preParseRoute(resource, false);
+ routesLoader.loadRoutes(resource);
+
+ assertNotNull(context.getRoute("r10"), "Loaded r10 route should be
there");
+ assertEquals(1, context.getRoutes().size());
+
+ // test that loaded route works
+ MockEndpoint y10 = context.getEndpoint("mock:y10",
MockEndpoint.class);
+ y10.expectedBodiesReceived("Hi World. I am Camel and 44 years
old!");
+ context.createProducerTemplate().sendBody("direct:x10", "Hi");
+ y10.assertIsSatisfied();
context.stop();
}
diff --git
a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyBean.java
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyBean.java
new file mode 100644
index 00000000000..66212bda235
--- /dev/null
+++
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyBean.java
@@ -0,0 +1,35 @@
+/*
+ * 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.dsl.xml.io.beans;
+
+public class MyBean {
+
+ private String field1;
+ private String field2;
+ private int age;
+
+ public MyBean(String field1, String field2, int age) {
+ this.field1 = field1;
+ this.field2 = field2;
+ this.age = age;
+ }
+
+ public String hi(String body) {
+ return body + " " + field1 + ". I am " + field2 + " and " + age + "
years old!";
+ }
+
+}
diff --git
a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyBeanBuilder.java
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyBeanBuilder.java
new file mode 100644
index 00000000000..e5e06393878
--- /dev/null
+++
b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyBeanBuilder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dsl.xml.io.beans;
+
+public class MyBeanBuilder {
+
+ private String field1;
+ private String field2;
+ private int age;
+
+ public MyBeanBuilder field1(String field1) {
+ this.field1 = field1;
+ return this;
+ }
+
+ public MyBeanBuilder field2(String field2) {
+ this.field2 = field2;
+ return this;
+ }
+
+ public MyBeanBuilder age(int age) {
+ this.age = age;
+ return this;
+ }
+
+ public MyBean createTheBean() {
+ return new MyBean(field1, field2, age);
+ }
+
+}
diff --git
a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app10.xml
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app10.xml
new file mode 100644
index 00000000000..9a5843ce93e
--- /dev/null
+++
b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app10.xml
@@ -0,0 +1,37 @@
+<?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.MyBean"
+ builderClass="org.apache.camel.dsl.xml.io.beans.MyBeanBuilder"
builderMethod="createTheBean">
+ <properties>
+ <property key="field1" value="World"/>
+ <property key="field2" value="Camel"/>
+ <property key="age" value="44"/>
+ </properties>
+ </bean>
+
+ <route id="r10">
+ <from uri="direct:x10"/>
+ <bean ref="xml-bean-from-registry"/>
+ <to uri="mock:y10"/>
+ </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 8fec7a9903c..9e01da00d36 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
@@ -12308,6 +12308,8 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
types = org.apache.camel.model.app.RegistryBeanDefinition.class,
order =
org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1,
properties = {
+ @YamlProperty(name = "builder-class", type = "string"),
+ @YamlProperty(name = "builder-method", type = "string"),
@YamlProperty(name = "constructors", type = "object"),
@YamlProperty(name = "destroy-method", type = "string"),
@YamlProperty(name = "factory-bean", type = "string"),
@@ -12334,6 +12336,16 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
protected boolean setProperty(RegistryBeanDefinition target, String
propertyKey,
String propertyName, Node node) {
switch(propertyKey) {
+ case "builder-class": {
+ String val = asText(node);
+ target.setBuilderClass(val);
+ break;
+ }
+ case "builder-method": {
+ String val = asText(node);
+ target.setBuilderMethod(val);
+ break;
+ }
case "constructors": {
java.util.Map val = asMap(node);
target.setConstructors(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 ca2eddc7879..64fdaef4f16 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
@@ -147,7 +147,19 @@ public class BeansDeserializer extends
YamlDeserializerSupport implements Constr
if (target == null) {
throw new NoSuchBeanException(def.getName(), "Creating bean
using script returned null");
}
-
+ } else if (def.getBuilderClass() != null) {
+ // builder class and method
+ Class<?> clazz =
context.getClassResolver().resolveMandatoryClass(def.getBuilderClass());
+ Object builder = context.getInjector().newInstance(clazz);
+ String bm = def.getBuilderMethod() != null ?
def.getBuilderMethod() : "build";
+
+ // create bean via builder and assign as target output
+ target = PropertyBindingSupport.build()
+ .withCamelContext(context)
+ .withTarget(builder)
+ .withRemoveParameters(true)
+ .withProperties(def.getProperties())
+ .build(Object.class, bm);
} else {
// factory bean/method
if (def.getFactoryBean() != null && def.getFactoryMethod() !=
null) {
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 da067a148aa..1447e89ffa1 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
@@ -7942,6 +7942,12 @@
"type" : "object",
"additionalProperties" : false,
"properties" : {
+ "builderClass" : {
+ "type" : "string"
+ },
+ "builderMethod" : {
+ "type" : "string"
+ },
"constructors" : {
"type" : "object"
},
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 7886335c75d..25d60874613 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,28 @@ public class MyHelper {
NOTE: The factory method must be `public static`.
+=== Creating beans from builder classes
+
+A bean can also be created from another builder class as shown below:
+
+[source,yaml]
+----
+- beans:
+ - name: myBean
+ type: com.acme.MyBean
+ builderClass: com.acme.MyBeanBuilder
+ builderMethod: createMyBean
+ properties:
+ id: 123
+ name: 'Acme'
+----
+
+NOTE: The builder class must be `public` and have a no-arg default constructor.
+
+The builder class is then used to create the actual bean by using fluent
builder style configuration.
+So the properties will be set on the builder class, and the bean is created by
invoking the `builderMethod`
+at the end. The invocation of this method is done via Java reflection.
+
=== 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.
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 194b6bf9595..e7b05bf71e2 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
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml
import org.apache.camel.dsl.yaml.support.YamlTestSupport
import org.apache.camel.dsl.yaml.support.model.MyBean
+import org.apache.camel.dsl.yaml.support.model.MyBeanBuilder
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
@@ -229,4 +230,24 @@ class BeansTest extends YamlTestSupport {
}
}
+ def "beans with builder class"() {
+ when:
+ loadRoutes """
+ - beans:
+ - name: myBean
+ type: ${MyBean.class.name}
+ builderClass: ${MyBeanBuilder.class.name}
+ builderMethod: createTheBean
+ properties:
+ field1: builder1
+ field2: builder2
+ """
+
+ then:
+ with(context.registry.lookupByName('myBean'), MyBean) {
+ it.field1 == 'builder1'
+ it.field2 == 'builder2'
+ }
+ }
+
}
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/model/MyBeanBuilder.groovy
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/model/MyBeanBuilder.groovy
new file mode 100644
index 00000000000..8bd930cb953
--- /dev/null
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/model/MyBeanBuilder.groovy
@@ -0,0 +1,46 @@
+/*
+ * 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.dsl.yaml.support.model
+
+class MyBeanBuilder {
+ private String field1
+ private String field2
+ private MyFooBar nested
+
+ MyBeanBuilder field1(String field1) {
+ this.field1 = field1
+ return this
+ }
+
+ MyBeanBuilder field2(String field2) {
+ this.field2 = field2
+ return this
+ }
+
+ MyBeanBuilder nested(MyFooBar fooBar) {
+ this.nested = fooBar;
+ return this
+ }
+
+ MyBean createTheBean() {
+ MyBean answer = new MyBean()
+ answer.field1 = field1
+ answer.field2 = field2
+ answer.nested = nested
+ return answer
+ }
+}