This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-3.11.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 37a874791122091c8bc0c35373ed222064994365 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Fri Jun 25 09:21:24 2021 +0200 CAMEL-16759: camel-core - Kamelet add support for factory method in #class local bean --- .../java/org/apache/camel/impl/DefaultModel.java | 69 ++++++++++++++++++--- .../camel/builder/MyConstructorProcessor.java | 37 ++++++++++++ .../camel/builder/RouteTemplateLocalBeanTest.java | 70 ++++++++++++++++++++++ .../camel/support/PropertyBindingSupport.java | 24 +++++++- 4 files changed, 189 insertions(+), 11 deletions(-) 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 453a205..5802e61 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 @@ -70,6 +70,7 @@ 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; +import org.apache.camel.util.StringHelper; import org.apache.camel.util.function.Suppliers; public class DefaultModel implements Model { @@ -425,19 +426,69 @@ public class DefaultModel implements Model { })); } } 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 - // and memorize so the bean is only created once and the local bean is the same - // if a route template refers to the local bean multiple times - routeTemplateContext.bind(b.getName(), clazz, - Suppliers.memorize(() -> { - Object local = camelContext.getInjector().newInstance(clazz); + // if there is a factory method then the class/bean should be created in a different way + String className = null; + String factoryMethod = null; + String parameters = null; + if (b.getType() != null) { + className = b.getType().substring(7); + if (className.endsWith(")") && className.indexOf('(') != -1) { + parameters = StringHelper.after(className, "("); + parameters = parameters.substring(0, parameters.length() - 1); // clip last ) + className = StringHelper.before(className, "("); + } + if (className != null && className.indexOf('#') != -1) { + factoryMethod = StringHelper.after(className, "#"); + className = StringHelper.before(className, "#"); + } + } + if (className != null && (factoryMethod != null || parameters != null)) { + final Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(className); + final String fqn = className; + final String fm = factoryMethod; + final String fp = parameters; + routeTemplateContext.bind(b.getName(), Object.class, Suppliers.memorize(() -> { + try { + Object local; + if (fm != null) { + if (fp != null) { + // special to support factory method parameters + local = PropertyBindingSupport.newInstanceFactoryParameters(camelContext, clazz, fm, fp); + } else { + local = camelContext.getInjector().newInstance(clazz, fm); + } + if (local == null) { + throw new IllegalStateException( + "Cannot create bean instance using factory method: " + fqn + "#" + fm); + } + } else { + // special to support constructor parameters + local = PropertyBindingSupport.newInstanceConstructorParameters(camelContext, clazz, fp); + } if (!props.isEmpty()) { setPropertiesOnTarget(camelContext, local, props); } return local; - })); + } catch (Exception e) { + throw new IllegalStateException( + "Cannot create bean: " + b.getType()); + } + })); + } else { + Class<?> clazz = b.getBeanClass() != null + ? b.getBeanClass() : camelContext.getClassResolver().resolveMandatoryClass(className); + // we only have the bean class so we use that to create a new bean via the injector + // and memorize so the bean is only created once and the local bean is the same + // if a route template refers to the local bean multiple times + routeTemplateContext.bind(b.getName(), clazz, + Suppliers.memorize(() -> { + 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); diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/MyConstructorProcessor.java b/core/camel-core/src/test/java/org/apache/camel/builder/MyConstructorProcessor.java new file mode 100644 index 0000000..ae77098 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/builder/MyConstructorProcessor.java @@ -0,0 +1,37 @@ +/* + * 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.builder; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; + +public class MyConstructorProcessor implements Processor { + + private String prefix = ""; + + public MyConstructorProcessor() { + } + + public MyConstructorProcessor(String prefix) { + this.prefix = prefix; + } + + @Override + public void process(Exchange exchange) throws Exception { + exchange.getMessage().setBody(prefix + exchange.getMessage().getBody()); + } +} 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 7228b50..3780e38 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 @@ -692,6 +692,70 @@ public class RouteTemplateLocalBeanTest extends ContextTestSupport { context.stop(); } + @Test + public void testLocalBeanFactoryMethod() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar") + .templateBean("myBar") + .type("#class:org.apache.camel.builder.RouteTemplateLocalBeanTest#createBuilderProcessorThree('MyPrefix ')") + .end() + .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("MyPrefix Builder3 World", out); + + // should not be a global bean + assertNull(context.getRegistry().lookupByName("myBar")); + + context.stop(); + } + + @Test + public void testLocalBeanConstructorParameter() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar") + .templateBean("myBar").type("#class:org.apache.camel.builder.MyConstructorProcessor('MyCtr ')").end() + .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("MyCtr World", out); + + // should not be a global bean + assertNull(context.getRegistry().lookupByName("myBar")); + + context.stop(); + } + public static class BuilderTwoProcessor implements Processor { private String prefix = ""; @@ -739,4 +803,10 @@ public class RouteTemplateLocalBeanTest extends ContextTestSupport { return new BuilderTwoProcessor(rtc.getProperty("greeting", String.class)); } + public static Processor createBuilderProcessorThree(String prefix) { + BuilderThreeProcessor answer = new BuilderThreeProcessor(); + answer.setPrefix(prefix); + return answer; + } + } 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 aeee7e3..9d03a52 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 @@ -1202,7 +1202,17 @@ public final class PropertyBindingSupport { return true; } - private static Object newInstanceConstructorParameters(CamelContext camelContext, Class<?> type, String parameters) + /** + * Creates a new bean instance using the constructor that takes the given set of parameters. + * + * @param camelContext the camel context + * @param type the class type of the bean to create + * @param parameters the parameters for the constructor + * @return the created bean, or null if there was no constructor that matched the given set of + * parameters + * @throws Exception is thrown if error creating the bean + */ + public static Object newInstanceConstructorParameters(CamelContext camelContext, Class<?> type, String parameters) throws Exception { String[] params = StringQuoteHelper.splitSafeQuote(parameters, ','); Constructor found = findMatchingConstructor(type.getConstructors(), params); @@ -1276,7 +1286,17 @@ public final class PropertyBindingSupport { return candidates.size() == 1 ? candidates.get(0) : fallbackCandidate; } - private static Object newInstanceFactoryParameters( + /** + * Creates a new bean instance using a public static factory method from the given class + * + * @param camelContext the camel context + * @param type the class with the public static factory method + * @param parameters optional parameters for the factory method + * @return the created bean, or null if there was no factory method (optionally matched the given set + * of parameters) + * @throws Exception is thrown if error creating the bean + */ + public static Object newInstanceFactoryParameters( CamelContext camelContext, Class<?> type, String factoryMethod, String parameters) throws Exception { String[] params = StringQuoteHelper.splitSafeQuote(parameters, ',');