diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templateBean.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templateBean.json index d2b0b7ce9bf72..0efe57794d4c0 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templateBean.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templateBean.json @@ -12,7 +12,7 @@ }, "properties": { "name": { "kind": "attribute", "displayName": "Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Bean name" }, - "language": { "kind": "attribute", "displayName": "Language", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "bean", "groovy", "joor", "language", "mvel", "ognl" ], "deprecated": false, "autowired": false, "secret": false, "description": "The language to use for creating the bean (such as groovy, joor)" }, - "script": { "kind": "value", "displayName": "Script", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The script to execute that creates the bean. 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." } + "type": { "kind": "attribute", "displayName": "Type", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "What type to use for creating the bean. Can be one of: #class,#type,bean,groovy,joor,language,mvel,ognl. #class or #type then the bean is created via the fully qualified classname, such as #class:com.foo.MyBean The others are scripting languages that gives more power to create the bean with an inlined code in the script section, such as using groovy." }, + "script": { "kind": "value", "displayName": "Script", "required": true, "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/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 1147f5db98857..57c504da6441f 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 @@ -10337,31 +10337,21 @@ Reference to the route templates in the xml dsl. - - - - - diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassFourTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassFourTest.java new file mode 100644 index 0000000000000..3b254b37e1c4d --- /dev/null +++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassFourTest.java @@ -0,0 +1,81 @@ +/* + * 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.component.kamelet; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.http.annotation.Obsolete; +import org.junit.jupiter.api.Test; + +public class KameletLocalBeanClassFourTest extends CamelTestSupport { + + @Test + public void testOne() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are going to Moes"); + + template.sendBody("direct:bar", "John"); + + assertMockEndpointsSatisfied(); + } + + // ********************************************** + // + // test set-up + // + // ********************************************** + + @Obsolete + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("whereTo") + .templateParameter("bar") + .templateBean("myBar") + .type("org.apache.camel.component.kamelet.KameletLocalBeanClassFourTest$MyBar") + .property("bar", "{{bar}}") + .end() + .from("kamelet:source") + // must use {{myBar}} to refer to the local bean + .to("bean:{{myBar}}"); + + from("direct:bar") + .kamelet("whereTo?bar=Moes") + .to("mock:result"); + } + }; + } + + public static class MyBar { + + private String bar; + + public String getBar() { + return bar; + } + + public void setBar(String bar) { + this.bar = bar; + } + + public String where(String name) { + return "Hi " + name + " we are going to " + bar; + } + } + +} diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassTest.java new file mode 100644 index 0000000000000..b69240113e3a4 --- /dev/null +++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassTest.java @@ -0,0 +1,69 @@ +/* + * 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.component.kamelet; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.http.annotation.Obsolete; +import org.junit.jupiter.api.Test; + +public class KameletLocalBeanClassTest extends CamelTestSupport { + + @Test + public void testOne() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are going to Murphys"); + + template.sendBody("direct:bar", "John"); + + assertMockEndpointsSatisfied(); + } + + // ********************************************** + // + // test set-up + // + // ********************************************** + + @Obsolete + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("whereTo") + .templateBean("myBar", MyBar.class) + .from("kamelet:source") + // must use {{myBar}} to refer to the local bean + .to("bean:{{myBar}}"); + + from("direct:bar") + .kamelet("whereTo") + .to("mock:result"); + } + }; + } + + public static class MyBar { + + private final String bar = "Murphys"; + + public String where(String name) { + return "Hi " + name + " we are going to " + bar; + } + } + +} diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassThreeTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassThreeTest.java new file mode 100644 index 0000000000000..5d4b3f8e51d85 --- /dev/null +++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassThreeTest.java @@ -0,0 +1,81 @@ +/* + * 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.component.kamelet; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.http.annotation.Obsolete; +import org.junit.jupiter.api.Test; + +public class KameletLocalBeanClassThreeTest extends CamelTestSupport { + + @Test + public void testOne() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are going to Moes"); + + template.sendBody("direct:bar", "John"); + + assertMockEndpointsSatisfied(); + } + + // ********************************************** + // + // test set-up + // + // ********************************************** + + @Obsolete + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("whereTo") + .templateParameter("bar") + .templateBean("myBar") + .type(MyBar.class) + .property("bar", "{{bar}}") + .end() + .from("kamelet:source") + // must use {{myBar}} to refer to the local bean + .to("bean:{{myBar}}"); + + from("direct:bar") + .kamelet("whereTo?bar=Moes") + .to("mock:result"); + } + }; + } + + public static class MyBar { + + private String bar; + + public String getBar() { + return bar; + } + + public void setBar(String bar) { + this.bar = bar; + } + + public String where(String name) { + return "Hi " + name + " we are going to " + bar; + } + } + +} diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassTwoTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassTwoTest.java new file mode 100644 index 0000000000000..e57760b2ae187 --- /dev/null +++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanClassTwoTest.java @@ -0,0 +1,69 @@ +/* + * 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.component.kamelet; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.http.annotation.Obsolete; +import org.junit.jupiter.api.Test; + +public class KameletLocalBeanClassTwoTest extends CamelTestSupport { + + @Test + public void testOne() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are going to Murphys"); + + template.sendBody("direct:bar", "John"); + + assertMockEndpointsSatisfied(); + } + + // ********************************************** + // + // test set-up + // + // ********************************************** + + @Obsolete + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("whereTo") + .templateBean("myBar", "#class:org.apache.camel.component.kamelet.KameletLocalBeanClassTwoTest$MyBar") + .from("kamelet:source") + // must use {{myBar}} to refer to the local bean + .to("bean:{{myBar}}"); + + from("direct:bar") + .kamelet("whereTo") + .to("mock:result"); + } + }; + } + + public static class MyBar { + + private final String bar = "Murphys"; + + public String where(String name) { + return "Hi " + name + " we are going to " + bar; + } + } + +} diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTypeTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTypeTest.java new file mode 100644 index 0000000000000..70d00d2f74189 --- /dev/null +++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTypeTest.java @@ -0,0 +1,77 @@ +/* + * 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.component.kamelet; + +import org.apache.camel.BindToRegistry; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.http.annotation.Obsolete; +import org.junit.jupiter.api.Test; + +public class KameletLocalBeanTypeTest extends CamelTestSupport { + + @BindToRegistry("myBar") + private MyBar bar = new MyBar(); + + @Test + public void testOne() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are going to Murphys"); + + template.sendBody("direct:bar", "John"); + + assertMockEndpointsSatisfied(); + } + + // ********************************************** + // + // test set-up + // + // ********************************************** + + @Obsolete + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("whereTo") + .templateBean("myBar", "#type:org.apache.camel.component.kamelet.KameletLocalBeanTypeTest$Bar") + .from("kamelet:source") + // must use {{myBar}} to refer to the local bean + .to("bean:{{myBar}}"); + + from("direct:bar") + .kamelet("whereTo") + .to("mock:result"); + } + }; + } + + public interface Bar { + String where(String name); + } + + public static class MyBar implements Bar { + + private final String bar = "Murphys"; + + public String where(String name) { + return "Hi " + name + " we are going to " + bar; + } + } + +} diff --git a/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/MyLocalBean.java b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/MyLocalBean.java new file mode 100644 index 0000000000000..47fed854d9f6f --- /dev/null +++ b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/MyLocalBean.java @@ -0,0 +1,34 @@ +/* + * 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.spring.routebuilder; + +public class MyLocalBean { + + private String prefix; + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String hello(String body) { + return prefix + " " + body; + } +} diff --git a/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringRouteTemplateLocalBeanTest.java b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringRouteTemplateLocalBeanTest.java new file mode 100644 index 0000000000000..7f0eba54d4428 --- /dev/null +++ b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringRouteTemplateLocalBeanTest.java @@ -0,0 +1,51 @@ +/* + * 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.spring.routebuilder; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spring.SpringTestSupport; +import org.junit.jupiter.api.Test; +import org.springframework.context.support.AbstractXmlApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class SpringRouteTemplateLocalBeanTest extends SpringTestSupport { + + @Override + protected AbstractXmlApplicationContext createApplicationContext() { + return new ClassPathXmlApplicationContext("org/apache/camel/spring/routebuilder/SpringRouteTemplateLocalBeanTest.xml"); + } + + @Test + public void testLocalBean() throws Exception { + Map parameters = new HashMap<>(); + parameters.put("foo", "one"); + parameters.put("bar", "cheese"); + parameters.put("greeting", "Davs"); + context.addRouteFromTemplate("first", "myTemplate", parameters); + + MockEndpoint mock = getMockEndpoint("mock:cheese"); + mock.expectedBodiesReceived("Davs World"); + + template.sendBody("direct:one", "World"); + + assertMockEndpointsSatisfied(); + } + +} diff --git a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringRouteTemplateLocalBeanTest.xml b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringRouteTemplateLocalBeanTest.xml new file mode 100644 index 0000000000000..73b77084698e8 --- /dev/null +++ b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringRouteTemplateLocalBeanTest.xml @@ -0,0 +1,46 @@ + + + + + + + blah blah + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/Registry.java b/core/camel-api/src/main/java/org/apache/camel/spi/Registry.java index a15a4652865e9..683d8aae845bb 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/Registry.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/Registry.java @@ -36,7 +36,9 @@ public interface Registry extends BeanRepository { * @throws RuntimeCamelException is thrown if binding is not possible */ default void bind(String id, Object bean) throws RuntimeCamelException { - bind(id, bean.getClass(), bean); + if (bean != null) { + bind(id, bean.getClass(), bean); + } } /** diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java index 53b02cc9f1ae4..43114166daca1 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java @@ -22,15 +22,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import org.apache.camel.CamelContext; +import org.apache.camel.Component; import org.apache.camel.Exchange; import org.apache.camel.Expression; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.FailedToCreateRouteFromTemplateException; +import org.apache.camel.NoSuchBeanException; +import org.apache.camel.PropertyBindingException; import org.apache.camel.RouteTemplateContext; import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.model.DefaultRouteTemplateContext; @@ -55,9 +59,13 @@ import org.apache.camel.spi.ExchangeFactory; import org.apache.camel.spi.Language; import org.apache.camel.spi.ModelReifierFactory; +import org.apache.camel.spi.PropertyConfigurer; import org.apache.camel.spi.ScriptingLanguage; +import org.apache.camel.support.PropertyBindingSupport; import org.apache.camel.support.ScriptHelper; +import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.util.AntPathMatcher; +import org.apache.camel.util.ObjectHelper; public class DefaultModel implements Model { @@ -336,16 +344,22 @@ public String addRouteFromTemplate(String routeId, String routeTemplateId, Route private void addTemplateBeans(RouteTemplateContext routeTemplateContext, RouteTemplateDefinition target) throws Exception { for (RouteTemplateBeanDefinition b : target.getTemplateBeans()) { + final Map props = new HashMap<>(); + if (b.getProperties() != null) { + b.getProperties().forEach(p -> props.put(p.getKey(), p.getValue())); + } if (b.getBeanSupplier() != null) { - // bean class is optional for supplier - if (b.getBeanClass() != null) { - routeTemplateContext.bind(b.getName(), b.getBeanClass(), b.getBeanSupplier()); - } else { - routeTemplateContext.bind(b.getName(), b.getBeanSupplier()); + if (props.isEmpty()) { + // bean class is optional for supplier + if (b.getBeanClass() != null) { + routeTemplateContext.bind(b.getName(), b.getBeanClass(), b.getBeanSupplier()); + } else { + routeTemplateContext.bind(b.getName(), b.getBeanSupplier()); + } } } else if (b.getScript() != null) { - final String script = b.getScript(); - final Language lan = camelContext.resolveLanguage(b.getLanguage()); + final String script = b.getScript().getScript(); + final Language lan = camelContext.resolveLanguage(b.getType()); final Class clazz = b.getBeanClass() != null ? b.getBeanClass() : Object.class; final ScriptingLanguage slan = lan instanceof ScriptingLanguage ? (ScriptingLanguage) lan : null; if (slan != null) { @@ -354,7 +368,11 @@ private void addTemplateBeans(RouteTemplateContext routeTemplateContext, RouteTe Map bindings = new HashMap<>(); // use rtx as the short-hand name, as context would imply its CamelContext bindings.put("rtc", routeTemplateContext); - return slan.evaluate(script, bindings, clazz); + Object local = slan.evaluate(script, bindings, clazz); + if (!props.isEmpty()) { + setPropertiesOnTarget(camelContext, local, props); + } + return local; }); } else { // exchange based languages needs a dummy exchange to be evaluated @@ -365,7 +383,11 @@ private void addTemplateBeans(RouteTemplateContext routeTemplateContext, RouteTe String text = ScriptHelper.resolveOptionalExternalScript(camelContext, dummy, script); if (text != null) { Expression exp = lan.createExpression(text); - return exp.evaluate(dummy, clazz); + Object local = exp.evaluate(dummy, clazz); + if (!props.isEmpty()) { + setPropertiesOnTarget(camelContext, local, props); + } + return local; } else { return null; } @@ -374,10 +396,30 @@ private void addTemplateBeans(RouteTemplateContext routeTemplateContext, RouteTe } }); } - } else if (b.getBeanClass() != null) { + } else if (b.getBeanClass() != null || b.getType() != null && b.getType().startsWith("#class:")) { + Class clazz = b.getBeanClass() != null + ? b.getBeanClass() : camelContext.getClassResolver().resolveMandatoryClass(b.getType().substring(7)); // we only have the bean class so we use that to create a new bean via the injector - routeTemplateContext.bind(b.getName(), b.getBeanClass(), - () -> camelContext.getInjector().newInstance(b.getBeanClass())); + routeTemplateContext.bind(b.getName(), clazz, + () -> { + Object local = camelContext.getInjector().newInstance(clazz); + if (!props.isEmpty()) { + setPropertiesOnTarget(camelContext, local, props); + } + return local; + }); + } else if (b.getType() != null && b.getType().startsWith("#type:")) { + Class clazz = camelContext.getClassResolver().resolveMandatoryClass(b.getType().substring(6)); + Set found = getCamelContext().getRegistry().findByType(clazz); + if (found == null || found.isEmpty()) { + throw new NoSuchBeanException(null, clazz.getName()); + } else if (found.size() > 1) { + throw new NoSuchBeanException( + "Found " + found.size() + " beans of type: " + clazz + ". Only one bean expected."); + } else { + // do not set properties when using #type as it uses an existing shared bean + routeTemplateContext.bind(b.getName(), clazz, found.iterator().next()); + } } } } @@ -621,4 +663,59 @@ private static T lookup(CamelContext context, String ref, Class type) { } } + private static void setPropertiesOnTarget(CamelContext context, Object target, Map properties) { + ObjectHelper.notNull(context, "context"); + ObjectHelper.notNull(target, "target"); + ObjectHelper.notNull(properties, "properties"); + + if (target instanceof CamelContext) { + throw new UnsupportedOperationException("Configuring the Camel Context is not supported"); + } + + PropertyConfigurer configurer = null; + if (target instanceof Component) { + // the component needs to be initialized to have the configurer ready + ServiceHelper.initService(target); + configurer = ((Component) target).getComponentPropertyConfigurer(); + } + + if (configurer == null) { + // see if there is a configurer for it + configurer = context.adapt(ExtendedCamelContext.class) + .getConfigurerResolver() + .resolvePropertyConfigurer(target.getClass().getSimpleName(), context); + } + + try { + PropertyBindingSupport.build() + .withMandatory(true) + .withRemoveParameters(false) + .withConfigurer(configurer) + .withIgnoreCase(true) + .withFlattenProperties(true) + .bind(context, target, properties); + } catch (PropertyBindingException e) { + String key = e.getOptionKey(); + if (key == null) { + String prefix = e.getOptionPrefix(); + if (prefix != null && !prefix.endsWith(".")) { + prefix = "." + prefix; + } + + key = prefix != null + ? prefix + "." + e.getPropertyName() + : e.getPropertyName(); + } + + // enrich the error with more precise details with option prefix and key + throw new PropertyBindingException( + e.getTarget(), + e.getPropertyName(), + e.getValue(), + null, + key, + e.getCause()); + } + } + } diff --git a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties index 3e5741135aed0..b94ed3bb0d10c 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties +++ b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties @@ -172,6 +172,7 @@ tarfile templateBean templateBeanFactory templateParameter +templateScript threadPoolProfile threads thrift diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index b/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index index 88bee1bbf8b05..c13438052112a 100644 --- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index +++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index @@ -69,6 +69,7 @@ RouteTemplateBeanDefinition RouteTemplateContextRefDefinition RouteTemplateDefinition RouteTemplateParameterDefinition +RouteTemplateScriptDefinition RouteTemplatesDefinition RoutesDefinition RoutingSlipDefinition diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateBean.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateBean.json index d2b0b7ce9bf72..9f2c566570688 100644 --- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateBean.json +++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateBean.json @@ -12,7 +12,8 @@ }, "properties": { "name": { "kind": "attribute", "displayName": "Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Bean name" }, - "language": { "kind": "attribute", "displayName": "Language", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "bean", "groovy", "joor", "language", "mvel", "ognl" ], "deprecated": false, "autowired": false, "secret": false, "description": "The language to use for creating the bean (such as groovy, joor)" }, - "script": { "kind": "value", "displayName": "Script", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The script to execute that creates the bean. 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." } + "type": { "kind": "attribute", "displayName": "Type", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "What type to use for creating the bean. Can be one of: #class,#type,bean,groovy,joor,language,mvel,ognl. #class or #type then the bean is created via the fully qualified classname, such as #class:com.foo.MyBean The others are scripting languages that gives more power to create the bean with an inlined code in the script section, such as using groovy." }, + "property": { "kind": "element", "displayName": "Property", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Optional properties to set on the created local bean" }, + "script": { "kind": "element", "displayName": "Script", "required": false, "type": "object", "javaType": "org.apache.camel.model.RouteTemplateScriptDefinition", "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/templateScript.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateScript.json new file mode 100644 index 0000000000000..2180976239763 --- /dev/null +++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateScript.json @@ -0,0 +1,16 @@ +{ + "model": { + "kind": "model", + "name": "templateScript", + "title": "Template Script", + "description": "A route template script (local bean) when using scripting languages such as groovy", + "deprecated": false, + "label": "configuration", + "javaType": "org.apache.camel.model.RouteTemplateScriptDefinition", + "input": false, + "output": false + }, + "properties": { + "script": { "kind": "value", "displayName": "Script", "required": true, "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/RouteTemplateBeanDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateBeanDefinition.java index 84bd5a798f9e4..79876d3f9e17c 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateBeanDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateBeanDefinition.java @@ -16,12 +16,16 @@ */ package org.apache.camel.model; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.XmlValue; import org.apache.camel.RouteTemplateContext; import org.apache.camel.spi.Metadata; @@ -37,17 +41,16 @@ public class RouteTemplateBeanDefinition { private RouteTemplateDefinition parent; @XmlAttribute(required = true) private String name; - @XmlTransient - private Class beanClass; - // it only makes sense to use the languages that are general purpose scripting languages @XmlAttribute(required = true) - @Metadata(enums = "bean,groovy,joor,language,mvel,ognl") - private String language; - @XmlValue - @Metadata(required = true) - private String script; + private String type; + @XmlElement(name = "property") + private List properties; + @XmlElement + private RouteTemplateScriptDefinition script; // special for java-dsl to allow using lambda style @XmlTransient + private Class beanClass; + @XmlTransient private RouteTemplateContext.BeanSupplier beanSupplier; public RouteTemplateBeanDefinition() { @@ -80,29 +83,31 @@ public void setBeanType(Class beanType) { this.beanClass = beanType; } - public String getLanguage() { - return language; + public String getType() { + return type; } /** - * The language to use for creating the bean (such as groovy, joor) + * What type to use for creating the bean. Can be one of: #class,#type,bean,groovy,joor,language,mvel,ognl. + * + * #class or #type then the bean is created via the fully qualified classname, such as #class:com.foo.MyBean + * + * The others are scripting languages that gives more power to create the bean with an inlined code in the script + * section, such as using groovy. */ - public void setLanguage(String language) { - this.language = language; + public void setType(String type) { + this.type = type; } - public String getScript() { - return script; + public List getProperties() { + return properties; } /** - * The script to execute that creates the bean. - * - * 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. + * Optional properties to set on the created local bean */ - public void setScript(String script) { - this.script = script; + public void setProperties(List properties) { + this.properties = properties; } public RouteTemplateContext.BeanSupplier getBeanSupplier() { @@ -116,9 +121,55 @@ public void setBeanSupplier(RouteTemplateContext.BeanSupplier beanSuppli this.beanSupplier = beanSupplier; } + public RouteTemplateScriptDefinition getScript() { + return script; + } + + /** + * 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. + */ + public void setScript(RouteTemplateScriptDefinition script) { + this.script = script; + } + + /** + * 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. + */ + public void setScript(String script) { + this.script = new RouteTemplateScriptDefinition(); + this.script.setScript(script); + } + // fluent builders // ---------------------------------------------------- + /** + * Creates the bean from the given class type + * + * @param type the type of the class to create as bean + */ + public RouteTemplateDefinition beanClass(Class type) { + setType("#class:" + type.getName()); + return parent; + } + + /** + * Lookup in the registry for bean instances of the given type, and if there is a single instance of the given type, + * then that bean will be used as the local bean (danger this bean is shared) + * + * @param type the type of the class to lookup in the registry + */ + public RouteTemplateDefinition beanType(Class type) { + setType("#type:" + type.getName()); + return parent; + } + /** * Calls a method on a bean for creating the local template bean * @@ -135,7 +186,7 @@ public RouteTemplateDefinition bean(Class type) { * @param method the name of the method to call */ public RouteTemplateDefinition bean(Class type, String method) { - setLanguage("bean"); + setType("bean"); if (method != null) { setScript(type.getName() + "?method=" + method); } else { @@ -153,7 +204,7 @@ public RouteTemplateDefinition bean(Class type, String method) { * @param script the script */ public RouteTemplateDefinition groovy(String script) { - setLanguage("groovy"); + setType("groovy"); setScript(script); return parent; } @@ -167,7 +218,7 @@ public RouteTemplateDefinition groovy(String script) { * @param script the script */ public RouteTemplateDefinition joor(String script) { - setLanguage("joor"); + setType("joor"); setScript(script); return parent; } @@ -178,15 +229,41 @@ public RouteTemplateDefinition joor(String script) { * 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. * - * @param language the custom language (a language not provided out of the box from Camel) + * @param language the language * @param script the script */ public RouteTemplateDefinition language(String language, String script) { - setLanguage(language); + setType(language); setScript(script); return parent; } + /** + * What type to use for creating the bean. Can be one of: #class,#type,bean,groovy,joor,language,mvel,ognl. + * + * #class or #type then the bean is created via the fully qualified classname, such as #class:com.foo.MyBean + * + * The others are scripting languages that gives more power to create the bean with an inlined code in the script + * section, such as using groovy. + */ + public RouteTemplateBeanDefinition type(String type) { + if (!type.startsWith("#type:") && !type.startsWith("#class:")) { + type = "#class:" + type; + } + setType(type); + return this; + } + + /** + * Creates the bean from the given class type + * + * @param type the type of the class to create as bean + */ + public RouteTemplateBeanDefinition type(Class type) { + beanClass(type); + return this; + } + /** * Calls a MvEL script for creating the local template bean * @@ -196,7 +273,7 @@ public RouteTemplateDefinition language(String language, String script) { * @param script the script */ public RouteTemplateDefinition mvel(String script) { - setLanguage("mvel"); + setType("mvel"); setScript(script); return parent; } @@ -210,9 +287,38 @@ public RouteTemplateDefinition mvel(String script) { * @param script the script */ public RouteTemplateDefinition ognl(String script) { - setLanguage("ognl"); + setType("ognl"); setScript(script); return parent; } + /** + * Sets a property to set on the created local bean + * + * @param key the property name + * @param value the property value + */ + public RouteTemplateBeanDefinition property(String key, String value) { + if (properties == null) { + properties = new ArrayList<>(); + } + properties.add(new PropertyDefinition(key, value)); + return this; + } + + /** + * Sets properties to set on the created local bean + */ + public RouteTemplateBeanDefinition properties(Map properties) { + if (this.properties == null) { + this.properties = new ArrayList<>(); + } + properties.forEach((k, v) -> this.properties.add(new PropertyDefinition(k, v))); + return this; + } + + public RouteTemplateDefinition end() { + return parent; + } + } diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java index 987b635c71ddb..26ae044957292 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java @@ -223,7 +223,7 @@ public RouteTemplateDefinition templateBean(String name, Class type) { * Adds a local bean the route template uses * * @param name the name of the bean - * @param bean the bean or a supplier for the bean + * @param bean the bean, or reference to bean (#class or #type), or a supplier for the bean */ @SuppressWarnings("unchecked") public RouteTemplateDefinition templateBean(String name, Object bean) { @@ -236,6 +236,9 @@ public RouteTemplateDefinition templateBean(String name, Object bean) { def.setBeanSupplier((RouteTemplateContext.BeanSupplier) bean); } else if (bean instanceof Supplier) { def.setBeanSupplier(ctx -> ((Supplier) bean).get()); + } else if (bean instanceof String) { + // its a string type + def.setType((String) bean); } else { def.setBeanSupplier(ctx -> bean); } @@ -292,7 +295,7 @@ public RouteTemplateDefinition templateBean(String name, String language, String } RouteTemplateBeanDefinition def = new RouteTemplateBeanDefinition(); def.setName(name); - def.setLanguage(language); + def.setType(language); def.setScript(script); templateBeans.add(def); return this; diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateScriptDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateScriptDefinition.java new file mode 100644 index 0000000000000..b4d583ea9a079 --- /dev/null +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateScriptDefinition.java @@ -0,0 +1,53 @@ +/* + * 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.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlValue; + +import org.apache.camel.spi.Metadata; + +/** + * A route template script (local bean) when using scripting languages such as groovy + */ +@Metadata(label = "configuration") +@XmlRootElement(name = "templateScript") +@XmlAccessorType(XmlAccessType.FIELD) +public class RouteTemplateScriptDefinition { + @XmlValue + private String script; + + public RouteTemplateScriptDefinition() { + } + + public String getScript() { + return script; + } + + /** + * 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. + */ + public void setScript(String script) { + this.script = script; + } + +} diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java index 4f50fdf3b04e4..ef50ef38f6cf0 100644 --- a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java @@ -548,7 +548,38 @@ public void configure() throws Exception { context.stop(); } - private class BuilderProcessor implements Processor { + @Test + public void testLocalBeanClassExpressionFluent() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar") + .templateBean("myBar").beanClass(BuilderProcessor.class) + .from("direct:{{foo}}") + .to("bean:{{bar}}"); + } + }); + + context.start(); + + TemplatedRouteBuilder.builder(context, "myTemplate") + .parameter("foo", "one") + .parameter("bar", "myBar") + .routeId("myRoute") + .add(); + + assertEquals(1, context.getRoutes().size()); + + Object out = template.requestBody("direct:one", "World"); + assertEquals("Builder World", out); + + // should not be a global bean + assertNull(context.getRegistry().lookupByName("myBar")); + + context.stop(); + } + + public static class BuilderProcessor implements Processor { @Override public void process(Exchange exchange) throws Exception { @@ -556,7 +587,39 @@ public void process(Exchange exchange) throws Exception { } } - private class BuilderTwoProcessor implements Processor { + @Test + public void testLocalBeanClassPropertiesFluent() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar").templateParameter("hi") + .templateBean("myBar").property("prefix", "{{hi}}").beanClass(BuilderThreeProcessor.class) + .from("direct:{{foo}}") + .to("bean:{{bar}}"); + } + }); + + context.start(); + + TemplatedRouteBuilder.builder(context, "myTemplate") + .parameter("foo", "one") + .parameter("bar", "myBar") + .parameter("hi", "Davs") + .routeId("myRoute") + .add(); + + assertEquals(1, context.getRoutes().size()); + + Object out = template.requestBody("direct:one", "World"); + assertEquals("DavsBuilder3 World", out); + + // should not be a global bean + assertNull(context.getRegistry().lookupByName("myBar")); + + context.stop(); + } + + public static class BuilderTwoProcessor implements Processor { private String prefix = ""; @@ -573,6 +636,24 @@ public void process(Exchange exchange) throws Exception { } } + public static class BuilderThreeProcessor implements Processor { + + private String prefix = ""; + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + @Override + public void process(Exchange exchange) throws Exception { + exchange.getMessage().setBody(prefix + "Builder3 " + exchange.getMessage().getBody()); + } + } + public Processor createBuilderProcessor() { return new BuilderProcessor(); } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/DefaultRegistry.java b/core/camel-support/src/main/java/org/apache/camel/support/DefaultRegistry.java index 7864fb7d20aaa..a714b8bd88b69 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/DefaultRegistry.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/DefaultRegistry.java @@ -165,22 +165,28 @@ public List getRepositories() { @Override public void bind(String id, Class type, Object bean) throws RuntimeCamelException { - // automatic inject camel context in bean if its aware - if (camelContext != null && bean instanceof CamelContextAware) { - ((CamelContextAware) bean).setCamelContext(camelContext); + if (bean != null) { + // automatic inject camel context in bean if its aware + if (camelContext != null && bean instanceof CamelContextAware) { + ((CamelContextAware) bean).setCamelContext(camelContext); + } + fallbackRegistry.bind(id, type, bean); } - fallbackRegistry.bind(id, type, bean); } @Override public void bind(String id, Class type, Supplier bean) throws RuntimeCamelException { - // wrap in cached supplier (memorize) - supplierRegistry.bind(id, type, Suppliers.memorize(bean)); + if (bean != null) { + // wrap in cached supplier (memorize) + supplierRegistry.bind(id, type, Suppliers.memorize(bean)); + } } @Override public void bindAsPrototype(String id, Class type, Supplier bean) throws RuntimeCamelException { - supplierRegistry.bind(id, type, bean); + if (bean != null) { + supplierRegistry.bind(id, type, bean); + } } @Override diff --git a/core/camel-support/src/main/java/org/apache/camel/support/SimpleRegistry.java b/core/camel-support/src/main/java/org/apache/camel/support/SimpleRegistry.java index 2e99abc929f94..2c57e9b9b3bab 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/SimpleRegistry.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/SimpleRegistry.java @@ -101,7 +101,9 @@ public Set findByType(Class type) { @Override public void bind(String id, Class type, Object bean) { - computeIfAbsent(id, k -> new LinkedHashMap<>()).put(type, wrap(bean)); + if (bean != null) { + computeIfAbsent(id, k -> new LinkedHashMap<>()).put(type, wrap(bean)); + } } @Override diff --git a/core/camel-support/src/main/java/org/apache/camel/support/SupplierRegistry.java b/core/camel-support/src/main/java/org/apache/camel/support/SupplierRegistry.java index 851847bbee21a..a4683d259f9f3 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/SupplierRegistry.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/SupplierRegistry.java @@ -112,6 +112,8 @@ public Map findByTypeWithName(Class type) { @Override public void bind(String id, Class type, Supplier bean) { - computeIfAbsent(id, k -> new LinkedHashMap<>()).put(type, wrap(bean)); + if (bean != null) { + computeIfAbsent(id, k -> new LinkedHashMap<>()).put(type, wrap(bean)); + } } } 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 1413e682bb718..b8e7249d9c71b 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 @@ -1023,12 +1023,23 @@ protected RestBindingDefinition doParseRestBindingDefinition() throws IOExceptio protected RouteTemplateBeanDefinition doParseRouteTemplateBeanDefinition() throws IOException, XmlPullParserException { return doParse(new RouteTemplateBeanDefinition(), (def, key, val) -> { switch (key) { - case "language": def.setLanguage(val); break; case "name": def.setName(val); break; + case "type": def.setType(val); break; + default: return false; + } + return true; + }, (def, key) -> { + switch (key) { + case "property": doAdd(doParsePropertyDefinition(), def.getProperties(), def::setProperties); break; + case "script": def.setScript(doParseRouteTemplateScriptDefinition()); break; default: return false; } return true; - }, noElementHandler(), (def, val) -> def.setScript(val)); + }, noValueHandler()); + } + protected RouteTemplateScriptDefinition doParseRouteTemplateScriptDefinition() throws IOException, XmlPullParserException { + return doParse(new RouteTemplateScriptDefinition(), + noAttributeHandler(), noElementHandler(), (def, val) -> def.setScript(val)); } protected RouteTemplateContextRefDefinition doParseRouteTemplateContextRefDefinition() throws IOException, XmlPullParserException { return doParse(new RouteTemplateContextRefDefinition(), (def, key, val) -> { diff --git a/docs/user-manual/modules/ROOT/pages/route-template.adoc b/docs/user-manual/modules/ROOT/pages/route-template.adoc index f2edf27cf410f..cb8afef9fc7a1 100644 --- a/docs/user-manual/modules/ROOT/pages/route-template.adoc +++ b/docs/user-manual/modules/ROOT/pages/route-template.adoc @@ -215,6 +215,77 @@ You should prefer to create the local beans directly from within the template (i ensures the route template has this out of the box. Otherwise the bean must be created or provided every time a new route is created from the route template. However the latter gives freedom to create the bean in any other custom way. +=== Binding beans to route templates using bean types + +You can create a local bean by referring to a fully qualified class name which Camel will use to create +a new local bean instance. When using this the created bean is created via default constructor of the class. + +The bean instance can be configured with properties via getter/setter style. +The previous example with creating the AWS S3Client would not support this kind as this uses _fluent builder_ pattern (not getter/setter). + +So suppose we have a class as follows + +[source,java] +---- +public class MyBar { + private String name; + private String address; + + // getter/setter omitted + + public String location() { + return "The bar " + name + " is located at " + address; + } +} +---- + +Then we can use the `MyBar` class as a local bean in a route template as follows: + +[source,java] +---- +routeTemplate("barTemplate") + .templateParameter("bar") + .templateParameter("street") + .templateBean("myBar") + .type("com.foo.MyBar") + .property("name", "{{bar}}") + .property("address", "{{street}}") + .end() + .from("direct:going-out") + .to("bean:{{myBar}}") +---- + +With Java DSL you can also refer to the bean class using type safe way: + +[source,java] +---- + .templateBean("myBar") + .type(MyBar.class) + .property("name", "{{bar}}") + .property("address", "{{street}}") + .end() +---- + +In XML DSL you would do: + +[source,xml] +---- + + + + + + + + + + + + + + +---- + === Binding beans to route templates using scripting languages You can use scripting languages like groovy, joor, mvel which creates the bean. @@ -268,13 +339,13 @@ The route template in XML DSL can then also use groovy language to create the be - - + + @@ -293,10 +364,8 @@ the bean creation code to an external file, by using `resource:` as prefix: - - - resource:classpath:s3bean.groovy - + + @@ -310,13 +379,13 @@ The languages supported are: [width="100%",cols="2s,8",options="header"] |=== -| Language | Description -| bean | Calling a method on a Java class. +| Type | Description +| bean | Calling a method on a Java class to create the bean. | groovy | Using groovy script to create the bean. | joor | Using joor (Java code which are runtime compiled) to create the bean. -| language | To use a 3rd party language to create the bean. | mvel | To use Mvel template script to create the bean. | ognl | To use OGNL template script to create the bean. +| _name_ | To use a 3rd party language by the given _name_ to create the bean. |=== Camel will bind `RouteTemplateContext` as the root object with name `rtc` when evaluating the script. @@ -354,10 +423,8 @@ then you can call this method from the route template in XML DSL: - - - com.foo.MyAwsHelper?method=createS3Client - + + 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 8992959a676bc..c1968d05c5f5d 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 @@ -73,6 +73,7 @@ import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.RouteTemplateBeanDefinition; import org.apache.camel.model.RouteTemplateParameterDefinition; +import org.apache.camel.model.RouteTemplateScriptDefinition; import org.apache.camel.model.RoutingSlipDefinition; import org.apache.camel.model.SagaActionUriDefinition; import org.apache.camel.model.SagaDefinition; @@ -11739,9 +11740,10 @@ protected boolean setProperty(RouteDefinition target, String propertyKey, order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1, nodes = "template-bean", properties = { - @YamlProperty(name = "language", type = "string", required = true), @YamlProperty(name = "name", type = "string", required = true), - @YamlProperty(name = "script", type = "string", required = true) + @YamlProperty(name = "property", type = "array:org.apache.camel.model.PropertyDefinition"), + @YamlProperty(name = "template-script", type = "object:org.apache.camel.model.RouteTemplateScriptDefinition"), + @YamlProperty(name = "type", type = "string", required = true) } ) public static class RouteTemplateBeanDefinitionDeserializer extends YamlDeserializerBase { @@ -11763,21 +11765,26 @@ protected RouteTemplateBeanDefinition newInstance(String value) { protected boolean setProperty(RouteTemplateBeanDefinition target, String propertyKey, String propertyName, Node node) { switch(propertyKey) { - case "language": { - String val = asText(node); - target.setLanguage(val); - break; - } case "name": { String val = asText(node); target.setName(val); break; } - case "script": { - String val = asText(node); + case "property": { + java.util.List val = asFlatList(node, org.apache.camel.model.PropertyDefinition.class); + target.setProperties(val); + break; + } + case "template-script": { + org.apache.camel.model.RouteTemplateScriptDefinition val = asType(node, org.apache.camel.model.RouteTemplateScriptDefinition.class); target.setScript(val); break; } + case "type": { + String val = asText(node); + target.setType(val); + break; + } default: { return false; } @@ -11839,6 +11846,39 @@ protected boolean setProperty(RouteTemplateParameterDefinition target, String pr } } + @YamlType( + types = org.apache.camel.model.RouteTemplateScriptDefinition.class, + order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1, + nodes = "template-script", + properties = @YamlProperty(name = "script", type = "string") + ) + public static class RouteTemplateScriptDefinitionDeserializer extends YamlDeserializerBase { + public RouteTemplateScriptDefinitionDeserializer() { + super(RouteTemplateScriptDefinition.class); + } + + @Override + protected RouteTemplateScriptDefinition newInstance() { + return new RouteTemplateScriptDefinition(); + } + + @Override + protected boolean setProperty(RouteTemplateScriptDefinition target, String propertyKey, + String propertyName, Node node) { + switch(propertyKey) { + case "script": { + String val = asText(node); + target.setScript(val); + break; + } + default: { + return false; + } + } + return true; + } + } + @YamlType( inline = true, types = org.apache.camel.model.RoutingSlipDefinition.class, diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java index 10d3065b7287e..b27616cf1f209 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java @@ -296,6 +296,8 @@ public ConstructNode resolve(String id) { case "org.apache.camel.model.RouteTemplateBeanDefinition": return new ModelDeserializers.RouteTemplateBeanDefinitionDeserializer(); case "template-parameter": return new ModelDeserializers.RouteTemplateParameterDefinitionDeserializer(); case "org.apache.camel.model.RouteTemplateParameterDefinition": return new ModelDeserializers.RouteTemplateParameterDefinitionDeserializer(); + case "template-script": return new ModelDeserializers.RouteTemplateScriptDefinitionDeserializer(); + case "org.apache.camel.model.RouteTemplateScriptDefinition": return new ModelDeserializers.RouteTemplateScriptDefinitionDeserializer(); case "routing-slip": return new ModelDeserializers.RoutingSlipDefinitionDeserializer(); case "org.apache.camel.model.RoutingSlipDefinition": return new ModelDeserializers.RoutingSlipDefinitionDeserializer(); case "rss": return new ModelDeserializers.RssDataFormatDeserializer(); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json index 53c4660ff714d..d68e8d60d5a43 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json @@ -2065,18 +2065,24 @@ }, { "type" : "object", "properties" : { - "language" : { - "type" : "string" - }, "name" : { "type" : "string" }, - "script" : { + "property" : { + "type" : "array", + "items" : { + "$ref" : "#/items/definitions/org.apache.camel.model.PropertyDefinition" + } + }, + "template-script" : { + "$ref" : "#/items/definitions/org.apache.camel.model.RouteTemplateScriptDefinition" + }, + "type" : { "type" : "string" } } } ], - "required" : [ "language", "name", "script" ] + "required" : [ "name", "type" ] }, "org.apache.camel.model.RouteTemplateDefinition" : { "type" : "object", @@ -2120,6 +2126,14 @@ }, "required" : [ "name" ] }, + "org.apache.camel.model.RouteTemplateScriptDefinition" : { + "type" : "object", + "properties" : { + "script" : { + "type" : "string" + } + } + }, "org.apache.camel.model.RoutingSlipDefinition" : { "oneOf" : [ { "type" : "string"