Repository: camel Updated Branches: refs/heads/master 43e66e47d -> d2defc15e
CAMEL-7452: Made bean component cache by default, and fixed setting cache=false will create a new instance of the bean on-demand. As before we really always created a singleton bean instance. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/d2defc15 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/d2defc15 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/d2defc15 Branch: refs/heads/master Commit: d2defc15e84f34b148bd88cfcb9f1400feb82228 Parents: 43e66e4 Author: Claus Ibsen <davscl...@apache.org> Authored: Mon Sep 1 16:56:24 2014 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Mon Sep 1 20:12:49 2014 +0200 ---------------------------------------------------------------------- .../camel/component/bean/BeanProcessor.java | 3 +- .../bean/ConstantStaticTypeBeanHolder.java | 47 +++++++++++++ .../component/bean/ConstantTypeBeanHolder.java | 17 ++++- .../camel/component/bean/RegistryBean.java | 48 +++++--------- .../org/apache/camel/model/BeanDefinition.java | 55 +++++++++++----- .../apache/camel/model/ProcessorDefinition.java | 22 +++++++ .../camel/component/bean/BeanNoCacheTest.java | 69 ++++++++++++++++++++ .../component/bean/BeanRefNoCacheTest.java | 69 ++++++++++++++++++++ .../camel/component/bean/NewInstanceTest.java | 2 +- .../component/bean/PredicateAsBeanTest.java | 3 - 10 files changed, 284 insertions(+), 51 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/main/java/org/apache/camel/component/bean/BeanProcessor.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/bean/BeanProcessor.java b/camel-core/src/main/java/org/apache/camel/component/bean/BeanProcessor.java index dc91566..80dd138 100644 --- a/camel-core/src/main/java/org/apache/camel/component/bean/BeanProcessor.java +++ b/camel-core/src/main/java/org/apache/camel/component/bean/BeanProcessor.java @@ -90,7 +90,8 @@ public class BeanProcessor extends ServiceSupport implements AsyncProcessor { // do we have a custom adapter for this POJO to a Processor // but only do this if allowed if (allowProcessor(explicitMethodName, beanInfo)) { - Processor processor = getProcessor(); + // see if there is a processor for the given bean + Processor processor = exchange.getContext().getTypeConverter().convertTo(Processor.class, exchange, bean); if (processor != null) { LOG.trace("Using a custom adapter as bean invocation: {}", processor); try { http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/main/java/org/apache/camel/component/bean/ConstantStaticTypeBeanHolder.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/bean/ConstantStaticTypeBeanHolder.java b/camel-core/src/main/java/org/apache/camel/component/bean/ConstantStaticTypeBeanHolder.java new file mode 100644 index 0000000..f052a54 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/component/bean/ConstantStaticTypeBeanHolder.java @@ -0,0 +1,47 @@ +/** + * 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.bean; + +import org.apache.camel.CamelContext; +import org.apache.camel.Processor; +import org.apache.camel.util.ObjectHelper; + +/** + * A constant (singleton) bean implementation of {@link BeanTypeHolder} + * + * @version + */ +public class ConstantStaticTypeBeanHolder extends ConstantTypeBeanHolder { + + public ConstantStaticTypeBeanHolder(Class<?> type, BeanInfo beanInfo) { + super(type, beanInfo); + } + + public ConstantStaticTypeBeanHolder(Class<?> type, CamelContext context) { + super(type, context); + } + + public ConstantStaticTypeBeanHolder(Class<?> type, CamelContext context, ParameterMappingStrategy parameterMappingStrategy) { + super(type, context, parameterMappingStrategy); + } + + @Override + public Object getBean() { + // we cannot create a bean as there is no default constructor + return null; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java b/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java index 515272b..800b88c 100644 --- a/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java +++ b/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java @@ -45,13 +45,28 @@ public class ConstantTypeBeanHolder implements BeanTypeHolder { this(type, new BeanInfo(context, type, parameterMappingStrategy)); } + /** + * Creates a cached and constant {@link org.apache.camel.component.bean.BeanHolder} from this holder. + * + * @return a new {@link org.apache.camel.component.bean.BeanHolder} that has cached the lookup of the bean. + */ + public ConstantBeanHolder createCacheHolder() throws Exception { + Object bean = getBean(); + return new ConstantBeanHolder(bean, beanInfo); + } + @Override public String toString() { return type.toString(); } public Object getBean() { - return null; + // create a new bean + if (ObjectHelper.hasDefaultPublicNoArgConstructor(type)) { + return getBeanInfo().getCamelContext().getInjector().newInstance(type); + } else { + return null; + } } public Processor getProcessor() { http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/main/java/org/apache/camel/component/bean/RegistryBean.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/bean/RegistryBean.java b/camel-core/src/main/java/org/apache/camel/component/bean/RegistryBean.java index 0c6e774..72d1c2a 100644 --- a/camel-core/src/main/java/org/apache/camel/component/bean/RegistryBean.java +++ b/camel-core/src/main/java/org/apache/camel/component/bean/RegistryBean.java @@ -28,13 +28,11 @@ import org.apache.camel.util.CamelContextHelper; * @version */ public class RegistryBean implements BeanHolder { - private final Object lock = new Object(); private final CamelContext context; private final String name; private final Registry registry; - private volatile Processor processor; private volatile BeanInfo beanInfo; - private volatile Object bean; + private volatile Class<?> clazz; private ParameterMappingStrategy parameterMappingStrategy; public RegistryBean(CamelContext context, String name) { @@ -54,6 +52,11 @@ public class RegistryBean implements BeanHolder { return "bean: " + name; } + /** + * Creates a cached and constant {@link org.apache.camel.component.bean.BeanHolder} from this holder. + * + * @return a new {@link org.apache.camel.component.bean.BeanHolder} that has cached the lookup of the bean. + */ public ConstantBeanHolder createCacheHolder() throws Exception { Object bean = getBean(); BeanInfo info = createBeanInfo(bean); @@ -70,52 +73,37 @@ public class RegistryBean implements BeanHolder { // bean is a class so create an instance of it value = context.getInjector().newInstance((Class<?>)value); } - bean = value; return value; } // okay bean is not in registry, so try to resolve if its a class name and create a shared instance - synchronized (lock) { - if (bean != null) { - return bean; - } + if (clazz == null) { + clazz = context.getClassResolver().resolveClass(name); + } - // maybe its a class - bean = context.getClassResolver().resolveClass(name); - if (bean == null) { - // no its not a class then we cannot find the bean - throw new NoSuchBeanException(name); - } - // could be a class then create an instance of it - if (bean instanceof Class) { - // bean is a class so create an instance of it - bean = context.getInjector().newInstance((Class<?>)bean); - } + if (clazz == null) { + // no its not a class then we cannot find the bean + throw new NoSuchBeanException(name); } - return bean; + // bean is a class so create an instance of it + return context.getInjector().newInstance(clazz); } public Processor getProcessor() { - if (processor == null && bean != null) { - processor = CamelContextHelper.convertTo(context, Processor.class, bean); - } - return processor; + return null; } public BeanInfo getBeanInfo() { - if (beanInfo == null && bean != null) { + if (beanInfo == null) { + Object bean = getBean(); this.beanInfo = createBeanInfo(bean); } return beanInfo; } public BeanInfo getBeanInfo(Object bean) { - if (this.bean == bean) { - return getBeanInfo(); - } else { - return createBeanInfo(bean); - } + return createBeanInfo(bean); } public String getName() { http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/main/java/org/apache/camel/model/BeanDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/BeanDefinition.java b/camel-core/src/main/java/org/apache/camel/model/BeanDefinition.java index 182f661..a8789bf 100644 --- a/camel-core/src/main/java/org/apache/camel/model/BeanDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/BeanDefinition.java @@ -27,6 +27,7 @@ import org.apache.camel.component.bean.BeanHolder; import org.apache.camel.component.bean.BeanInfo; import org.apache.camel.component.bean.BeanProcessor; import org.apache.camel.component.bean.ConstantBeanHolder; +import org.apache.camel.component.bean.ConstantStaticTypeBeanHolder; import org.apache.camel.component.bean.ConstantTypeBeanHolder; import org.apache.camel.component.bean.MethodNotFoundException; import org.apache.camel.component.bean.RegistryBean; @@ -222,14 +223,16 @@ public class BeanDefinition extends NoOutputDefinition<BeanDefinition> { BeanHolder beanHolder; if (ObjectHelper.isNotEmpty(ref)) { - if (cache != null && cache) { + // lets cache by default + if (isCacheBean()) { // cache the registry lookup which avoids repeat lookup in the registry beanHolder = new RegistryBean(routeContext.getCamelContext(), ref).createCacheHolder(); + // bean holder will check if the bean exists + bean = beanHolder.getBean(); } else { + // we do not cache so we invoke on-demand beanHolder = new RegistryBean(routeContext.getCamelContext(), ref); } - // bean holder will check if the bean exists - bean = beanHolder.getBean(); answer = new BeanProcessor(beanHolder); } else { if (bean == null) { @@ -250,7 +253,7 @@ public class BeanDefinition extends NoOutputDefinition<BeanDefinition> { } // create a bean if there is a default public no-arg constructor - if (ObjectHelper.hasDefaultPublicNoArgConstructor(clazz)) { + if (isCacheBean() && ObjectHelper.hasDefaultPublicNoArgConstructor(clazz)) { bean = CamelContextHelper.newInstance(routeContext.getCamelContext(), clazz); ObjectHelper.notNull(bean, "bean", this); } @@ -264,7 +267,21 @@ public class BeanDefinition extends NoOutputDefinition<BeanDefinition> { } // the holder should either be bean or type based - beanHolder = bean != null ? new ConstantBeanHolder(bean, routeContext.getCamelContext()) : new ConstantTypeBeanHolder(clazz, routeContext.getCamelContext()); + if (bean != null) { + beanHolder = new ConstantBeanHolder(bean, routeContext.getCamelContext()); + } else { + if (isCacheBean() && ObjectHelper.hasDefaultPublicNoArgConstructor(clazz)) { + // we can only cache if we can create an instance of the bean, and for that we need a public constructor + beanHolder = new ConstantTypeBeanHolder(clazz, routeContext.getCamelContext()).createCacheHolder(); + } else { + if (ObjectHelper.hasDefaultPublicNoArgConstructor(clazz)) { + beanHolder = new ConstantTypeBeanHolder(clazz, routeContext.getCamelContext()); + } else { + // this is only for invoking static methods on the bean + beanHolder = new ConstantStaticTypeBeanHolder(clazz, routeContext.getCamelContext()); + } + } + } answer = new BeanProcessor(beanHolder); } @@ -278,16 +295,20 @@ public class BeanDefinition extends NoOutputDefinition<BeanDefinition> { answer.setMethod(method); // check there is a method with the given name, and leverage BeanInfo for that - BeanInfo beanInfo = beanHolder.getBeanInfo(); - if (bean != null) { - // there is a bean instance, so check for any methods - if (!beanInfo.hasMethod(method)) { - throw ObjectHelper.wrapRuntimeCamelException(new MethodNotFoundException(null, bean, method)); - } - } else if (clazz != null) { - // there is no bean instance, so check for static methods only - if (!beanInfo.hasStaticMethod(method)) { - throw ObjectHelper.wrapRuntimeCamelException(new MethodNotFoundException(null, clazz, method, true)); + // which we only do if we are caching the bean as otherwise we will create a bean instance for this check + // which we only want to do if we cache the bean + if (isCacheBean()) { + BeanInfo beanInfo = beanHolder.getBeanInfo(); + if (bean != null) { + // there is a bean instance, so check for any methods + if (!beanInfo.hasMethod(method)) { + throw ObjectHelper.wrapRuntimeCamelException(new MethodNotFoundException(null, bean, method)); + } + } else if (clazz != null) { + // there is no bean instance, so check for static methods only + if (!beanInfo.hasStaticMethod(method)) { + throw ObjectHelper.wrapRuntimeCamelException(new MethodNotFoundException(null, clazz, method, true)); + } } } } @@ -295,4 +316,8 @@ public class BeanDefinition extends NoOutputDefinition<BeanDefinition> { return answer; } + private boolean isCacheBean() { + return cache == null || cache; + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java b/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java index af44b18..534e976 100644 --- a/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java @@ -2534,6 +2534,28 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> * <a href="http://camel.apache.org/message-translator.html">Message Translator EIP:</a> * Adds a bean which is invoked which could be a final destination, or could be a transformation in a pipeline * + * @param beanType the bean class, Camel will instantiate an object at runtime + * @param method the method name to invoke on the bean (can be used to avoid ambiguity) + * @param multiParameterArray if it is true, camel will treat the message body as an object array which holds + * the multi parameter + * @param cache if enabled, Camel will cache the result of the first Registry look-up. + * Cache can be enabled if the bean in the Registry is defined as a singleton scope. + * @return the builder + */ + public Type bean(Class<?> beanType, String method, boolean multiParameterArray, boolean cache) { + BeanDefinition answer = new BeanDefinition(); + answer.setBeanType(beanType); + answer.setMethod(method); + answer.setMultiParameterArray(multiParameterArray); + answer.setCache(cache); + addOutput(answer); + return (Type) this; + } + + /** + * <a href="http://camel.apache.org/message-translator.html">Message Translator EIP:</a> + * Adds a bean which is invoked which could be a final destination, or could be a transformation in a pipeline + * * @param ref reference to a bean to lookup in the registry * @return the builder */ http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/test/java/org/apache/camel/component/bean/BeanNoCacheTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/component/bean/BeanNoCacheTest.java b/camel-core/src/test/java/org/apache/camel/component/bean/BeanNoCacheTest.java new file mode 100644 index 0000000..11a3f0d --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/component/bean/BeanNoCacheTest.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.bean; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; + +/** + * @version + */ +public class BeanNoCacheTest extends ContextTestSupport { + + private static final AtomicInteger counter = new AtomicInteger(); + + public void testBeanRefNoCache() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello1", "Bye2", "Camel3"); + + template.sendBody("direct:start", "Hello"); + template.sendBody("direct:start", "Bye"); + template.sendBody("direct:start", "Camel"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .bean(MyCoolBean.class, "doSomething", false, false) + .to("mock:result"); + } + }; + } + + public static class MyCoolBean { + + private final int count; + + public MyCoolBean() { + count = counter.incrementAndGet(); + } + + public int getCount() { + return count; + } + + public String doSomething(String s) { + return s + count; + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/test/java/org/apache/camel/component/bean/BeanRefNoCacheTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/component/bean/BeanRefNoCacheTest.java b/camel-core/src/test/java/org/apache/camel/component/bean/BeanRefNoCacheTest.java new file mode 100644 index 0000000..bff02a7 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/component/bean/BeanRefNoCacheTest.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.bean; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; + +/** + * @version + */ +public class BeanRefNoCacheTest extends ContextTestSupport { + + private static final AtomicInteger counter = new AtomicInteger(); + + public void testBeanRefNoCache() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello1", "Bye2", "Camel3"); + + template.sendBody("direct:start", "Hello"); + template.sendBody("direct:start", "Bye"); + template.sendBody("direct:start", "Camel"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .beanRef(MyCoolBean.class.getName(), "doSomething", false) + .to("mock:result"); + } + }; + } + + public static class MyCoolBean { + + private final int count; + + public MyCoolBean() { + count = counter.incrementAndGet(); + } + + public int getCount() { + return count; + } + + public String doSomething(String s) { + return s + count; + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/test/java/org/apache/camel/component/bean/NewInstanceTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/component/bean/NewInstanceTest.java b/camel-core/src/test/java/org/apache/camel/component/bean/NewInstanceTest.java index f8e4400..188c066 100644 --- a/camel-core/src/test/java/org/apache/camel/component/bean/NewInstanceTest.java +++ b/camel-core/src/test/java/org/apache/camel/component/bean/NewInstanceTest.java @@ -60,7 +60,7 @@ public class NewInstanceTest extends ContextTestSupport { protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { - from("direct:start").beanRef("myBean").to("mock:result"); + from("direct:start").beanRef("myBean", false).to("mock:result"); } }; } http://git-wip-us.apache.org/repos/asf/camel/blob/d2defc15/camel-core/src/test/java/org/apache/camel/component/bean/PredicateAsBeanTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/component/bean/PredicateAsBeanTest.java b/camel-core/src/test/java/org/apache/camel/component/bean/PredicateAsBeanTest.java index 6e67226..df050da 100644 --- a/camel-core/src/test/java/org/apache/camel/component/bean/PredicateAsBeanTest.java +++ b/camel-core/src/test/java/org/apache/camel/component/bean/PredicateAsBeanTest.java @@ -66,8 +66,5 @@ public class PredicateAsBeanTest extends ContextTestSupport { return (null != body) && (body.equals("Wobble")); } - public void assertMatches(String text, Exchange exchange) throws AssertionError { - LOG.info("assertMatches(text, exchange) called with: " + text + ", " + exchange); - } } }