CAMEL-11375: Optimise - BeanProcessor - Make light-weight not as service
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/b3ca9219 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/b3ca9219 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/b3ca9219 Branch: refs/heads/master Commit: b3ca9219fb8ce935321555973c200d5d0185f102 Parents: ba91b0d Author: Claus Ibsen <davscl...@apache.org> Authored: Thu Jun 1 10:48:11 2017 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu Jun 1 10:48:11 2017 +0200 ---------------------------------------------------------------------- .../component/bean/AbstractBeanProcessor.java | 278 +++++++++++++++++++ .../component/bean/BeanExpressionProcessor.java | 28 ++ .../camel/component/bean/BeanProcessor.java | 232 +++------------- .../camel/language/bean/BeanExpression.java | 4 +- 4 files changed, 345 insertions(+), 197 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/b3ca9219/camel-core/src/main/java/org/apache/camel/component/bean/AbstractBeanProcessor.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/bean/AbstractBeanProcessor.java b/camel-core/src/main/java/org/apache/camel/component/bean/AbstractBeanProcessor.java new file mode 100644 index 0000000..beeb692 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/component/bean/AbstractBeanProcessor.java @@ -0,0 +1,278 @@ +/** + * 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.AsyncCallback; +import org.apache.camel.AsyncProcessor; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.NoSuchBeanException; +import org.apache.camel.Processor; +import org.apache.camel.util.AsyncProcessorHelper; +import org.apache.camel.util.ServiceHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A {@link Processor} which converts the inbound exchange to a method + * invocation on a POJO + * + * @version + */ +public abstract class AbstractBeanProcessor implements AsyncProcessor { + private static final Logger LOG = LoggerFactory.getLogger(AbstractBeanProcessor.class); + + private final BeanHolder beanHolder; + private Processor processor; + private boolean multiParameterArray; + private String method; + private boolean shorthandMethod; + + public AbstractBeanProcessor(Object pojo, BeanInfo beanInfo) { + this(new ConstantBeanHolder(pojo, beanInfo)); + } + + public AbstractBeanProcessor(Object pojo, CamelContext camelContext, ParameterMappingStrategy parameterMappingStrategy) { + this(pojo, new BeanInfo(camelContext, pojo.getClass(), parameterMappingStrategy)); + } + + public AbstractBeanProcessor(Object pojo, CamelContext camelContext) { + this(pojo, camelContext, BeanInfo.createParameterMappingStrategy(camelContext)); + } + + public AbstractBeanProcessor(BeanHolder beanHolder) { + this.beanHolder = beanHolder; + } + + @Override + public String toString() { + return "BeanProcessor[" + beanHolder + "]"; + } + + public void process(Exchange exchange) throws Exception { + AsyncProcessorHelper.process(this, exchange); + } + + public boolean process(Exchange exchange, AsyncCallback callback) { + // do we have an explicit method name we always should invoke (either configured on endpoint or as a header) + String explicitMethodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, method, String.class); + + Object bean; + BeanInfo beanInfo; + try { + bean = beanHolder.getBean(); + // get bean info for this bean instance (to avoid thread issue) + beanInfo = beanHolder.getBeanInfo(bean); + if (beanInfo == null) { + // fallback and use old way + beanInfo = beanHolder.getBeanInfo(); + } + } catch (Throwable e) { + exchange.setException(e); + callback.done(true); + return true; + } + + // 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(); + if (processor == null) { + // so if there is a custom type converter for the bean to processor + processor = exchange.getContext().getTypeConverter().tryConvertTo(Processor.class, exchange, bean); + } + if (processor != null) { + LOG.trace("Using a custom adapter as bean invocation: {}", processor); + try { + processor.process(exchange); + } catch (Throwable e) { + exchange.setException(e); + } + callback.done(true); + return true; + } + } + + Message in = exchange.getIn(); + + // is the message proxied using a BeanInvocation? + BeanInvocation beanInvoke = null; + if (in.getBody() != null && in.getBody() instanceof BeanInvocation) { + // BeanInvocation would be stored directly as the message body + // do not force any type conversion attempts as it would just be unnecessary and cost a bit performance + // so a regular instanceof check is sufficient + beanInvoke = (BeanInvocation) in.getBody(); + } + if (beanInvoke != null) { + // Now it gets a bit complicated as ProxyHelper can proxy beans which we later + // intend to invoke (for example to proxy and invoke using spring remoting). + // and therefore the message body contains a BeanInvocation object. + // However this can causes problem if we in a Camel route invokes another bean, + // so we must test whether BeanHolder and BeanInvocation is the same bean or not + LOG.trace("Exchange IN body is a BeanInvocation instance: {}", beanInvoke); + Class<?> clazz = beanInvoke.getMethod().getDeclaringClass(); + boolean sameBean = clazz.isInstance(bean); + if (LOG.isDebugEnabled()) { + LOG.debug("BeanHolder bean: {} and beanInvocation bean: {} is same instance: {}", new Object[]{bean.getClass(), clazz, sameBean}); + } + if (sameBean) { + try { + beanInvoke.invoke(bean, exchange); + if (exchange.hasOut()) { + // propagate headers + exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders()); + } + } catch (Throwable e) { + exchange.setException(e); + } + callback.done(true); + return true; + } + } + + // set temporary header which is a hint for the bean info that introspect the bean + if (in.getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) == null) { + in.setHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, isMultiParameterArray()); + } + + MethodInvocation invocation; + // set explicit method name to invoke as a header, which is how BeanInfo can detect it + if (explicitMethodName != null) { + in.setHeader(Exchange.BEAN_METHOD_NAME, explicitMethodName); + } + try { + invocation = beanInfo.createInvocation(bean, exchange); + } catch (Throwable e) { + exchange.setException(e); + callback.done(true); + return true; + } finally { + // must remove headers as they were provisional + in.removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY); + in.removeHeader(Exchange.BEAN_METHOD_NAME); + } + + if (invocation == null) { + exchange.setException(new IllegalStateException("No method invocation could be created, no matching method could be found on: " + bean)); + callback.done(true); + return true; + } + + // invoke invocation + return invocation.proceed(callback); + } + + protected Processor getProcessor() { + return processor; + } + + protected BeanHolder getBeanHolder() { + return this.beanHolder; + } + + public Object getBean() { + return beanHolder.getBean(); + } + + // Properties + // ----------------------------------------------------------------------- + + public String getMethod() { + return method; + } + + public boolean isMultiParameterArray() { + return multiParameterArray; + } + + public void setMultiParameterArray(boolean mpArray) { + multiParameterArray = mpArray; + } + + /** + * Sets the method name to use + */ + public void setMethod(String method) { + this.method = method; + } + + public boolean isShorthandMethod() { + return shorthandMethod; + } + + /** + * Sets whether to support getter style method name, so you can + * say the method is called 'name' but it will invoke the 'getName' method. + * <p/> + * Is by default turned off. + */ + public void setShorthandMethod(boolean shorthandMethod) { + this.shorthandMethod = shorthandMethod; + } + + // Implementation methods + //------------------------------------------------------------------------- + protected void doStart() throws Exception { + // optimize to only get (create) a processor if really needed + if (beanHolder.supportProcessor() && allowProcessor(method, beanHolder.getBeanInfo())) { + processor = beanHolder.getProcessor(); + ServiceHelper.startService(processor); + } else if (beanHolder instanceof ConstantBeanHolder) { + try { + // Start the bean if it implements Service interface and if cached + // so meant to be reused + ServiceHelper.startService(beanHolder.getBean()); + } catch (NoSuchBeanException e) { + // ignore + } + } + } + + protected void doStop() throws Exception { + if (processor != null) { + ServiceHelper.stopService(processor); + } else if (beanHolder instanceof ConstantBeanHolder) { + try { + // Stop the bean if it implements Service interface and if cached + // so meant to be reused + ServiceHelper.stopService(beanHolder.getBean()); + } catch (NoSuchBeanException e) { + // ignore + } + } + } + + private boolean allowProcessor(String explicitMethodName, BeanInfo info) { + if (explicitMethodName != null) { + // don't allow if explicit method name is given, as we then must invoke this method + return false; + } + + // don't allow if any of the methods has a @Handler annotation + // as the @Handler annotation takes precedence and is supposed to trigger invocation + // of the given method + for (MethodInfo method : info.getMethods()) { + if (method.hasHandlerAnnotation()) { + return false; + } + } + + // fallback and allow using the processor + return true; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b3ca9219/camel-core/src/main/java/org/apache/camel/component/bean/BeanExpressionProcessor.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/bean/BeanExpressionProcessor.java b/camel-core/src/main/java/org/apache/camel/component/bean/BeanExpressionProcessor.java new file mode 100644 index 0000000..d99015c --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/component/bean/BeanExpressionProcessor.java @@ -0,0 +1,28 @@ +/** + * 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; + +/** + * A bean processor that is optimised for being invoked one time from an {@link org.apache.camel.language.bean.BeanExpression}. + * Where as {@link BeanProcessor} is a bean that is a {@link org.apache.camel.Service} and intended for long lifecycle. + */ +public class BeanExpressionProcessor extends AbstractBeanProcessor { + + public BeanExpressionProcessor(BeanHolder beanHolder) { + super(beanHolder); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b3ca9219/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 e7fe819..9e31582 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 @@ -20,260 +20,102 @@ import org.apache.camel.AsyncCallback; import org.apache.camel.AsyncProcessor; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; -import org.apache.camel.Message; -import org.apache.camel.NoSuchBeanException; import org.apache.camel.Processor; import org.apache.camel.support.ServiceSupport; -import org.apache.camel.util.AsyncProcessorHelper; -import org.apache.camel.util.ServiceHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -/** - * A {@link Processor} which converts the inbound exchange to a method - * invocation on a POJO - * - * @version - */ public class BeanProcessor extends ServiceSupport implements AsyncProcessor { - private static final Logger LOG = LoggerFactory.getLogger(BeanProcessor.class); - private final BeanHolder beanHolder; - private Processor processor; - private boolean multiParameterArray; - private String method; - private boolean shorthandMethod; + private final DelegateBeanProcessor delegate; public BeanProcessor(Object pojo, BeanInfo beanInfo) { - this(new ConstantBeanHolder(pojo, beanInfo)); + this.delegate = new DelegateBeanProcessor(pojo, beanInfo); } public BeanProcessor(Object pojo, CamelContext camelContext, ParameterMappingStrategy parameterMappingStrategy) { - this(pojo, new BeanInfo(camelContext, pojo.getClass(), parameterMappingStrategy)); + this.delegate = new DelegateBeanProcessor(pojo, camelContext, parameterMappingStrategy); } public BeanProcessor(Object pojo, CamelContext camelContext) { - this(pojo, camelContext, BeanInfo.createParameterMappingStrategy(camelContext)); + this.delegate = new DelegateBeanProcessor(pojo, camelContext); } public BeanProcessor(BeanHolder beanHolder) { - this.beanHolder = beanHolder; + this.delegate = new DelegateBeanProcessor(beanHolder); } @Override - public String toString() { - return "BeanProcessor[" + beanHolder + "]"; - } - public void process(Exchange exchange) throws Exception { - AsyncProcessorHelper.process(this, exchange); + delegate.process(exchange); } + @Override public boolean process(Exchange exchange, AsyncCallback callback) { - // do we have an explicit method name we always should invoke (either configured on endpoint or as a header) - String explicitMethodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, method, String.class); - - Object bean; - BeanInfo beanInfo; - try { - bean = beanHolder.getBean(); - // get bean info for this bean instance (to avoid thread issue) - beanInfo = beanHolder.getBeanInfo(bean); - if (beanInfo == null) { - // fallback and use old way - beanInfo = beanHolder.getBeanInfo(); - } - } catch (Throwable e) { - exchange.setException(e); - callback.done(true); - return true; - } - - // 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(); - if (processor == null) { - // so if there is a custom type converter for the bean to processor - processor = exchange.getContext().getTypeConverter().tryConvertTo(Processor.class, exchange, bean); - } - if (processor != null) { - LOG.trace("Using a custom adapter as bean invocation: {}", processor); - try { - processor.process(exchange); - } catch (Throwable e) { - exchange.setException(e); - } - callback.done(true); - return true; - } - } - - Message in = exchange.getIn(); - - // is the message proxied using a BeanInvocation? - BeanInvocation beanInvoke = null; - if (in.getBody() != null && in.getBody() instanceof BeanInvocation) { - // BeanInvocation would be stored directly as the message body - // do not force any type conversion attempts as it would just be unnecessary and cost a bit performance - // so a regular instanceof check is sufficient - beanInvoke = (BeanInvocation) in.getBody(); - } - if (beanInvoke != null) { - // Now it gets a bit complicated as ProxyHelper can proxy beans which we later - // intend to invoke (for example to proxy and invoke using spring remoting). - // and therefore the message body contains a BeanInvocation object. - // However this can causes problem if we in a Camel route invokes another bean, - // so we must test whether BeanHolder and BeanInvocation is the same bean or not - LOG.trace("Exchange IN body is a BeanInvocation instance: {}", beanInvoke); - Class<?> clazz = beanInvoke.getMethod().getDeclaringClass(); - boolean sameBean = clazz.isInstance(bean); - if (LOG.isDebugEnabled()) { - LOG.debug("BeanHolder bean: {} and beanInvocation bean: {} is same instance: {}", new Object[]{bean.getClass(), clazz, sameBean}); - } - if (sameBean) { - try { - beanInvoke.invoke(bean, exchange); - if (exchange.hasOut()) { - // propagate headers - exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders()); - } - } catch (Throwable e) { - exchange.setException(e); - } - callback.done(true); - return true; - } - } - - // set temporary header which is a hint for the bean info that introspect the bean - if (in.getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) == null) { - in.setHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, isMultiParameterArray()); - } - - MethodInvocation invocation; - // set explicit method name to invoke as a header, which is how BeanInfo can detect it - if (explicitMethodName != null) { - in.setHeader(Exchange.BEAN_METHOD_NAME, explicitMethodName); - } - try { - invocation = beanInfo.createInvocation(bean, exchange); - } catch (Throwable e) { - exchange.setException(e); - callback.done(true); - return true; - } finally { - // must remove headers as they were provisional - in.removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY); - in.removeHeader(Exchange.BEAN_METHOD_NAME); - } - - if (invocation == null) { - exchange.setException(new IllegalStateException("No method invocation could be created, no matching method could be found on: " + bean)); - callback.done(true); - return true; - } - - // invoke invocation - return invocation.proceed(callback); + return delegate.process(exchange, callback); } - protected Processor getProcessor() { - return processor; + public Processor getProcessor() { + return delegate.getProcessor(); } - protected BeanHolder getBeanHolder() { - return this.beanHolder; + public BeanHolder getBeanHolder() { + return delegate.getBeanHolder(); } public Object getBean() { - return beanHolder.getBean(); + return delegate.getBean(); } - // Properties - // ----------------------------------------------------------------------- - public String getMethod() { - return method; + return delegate.getMethod(); } public boolean isMultiParameterArray() { - return multiParameterArray; + return delegate.isMultiParameterArray(); } public void setMultiParameterArray(boolean mpArray) { - multiParameterArray = mpArray; + delegate.setMultiParameterArray(mpArray); } - /** - * Sets the method name to use - */ public void setMethod(String method) { - this.method = method; + delegate.setMethod(method); } public boolean isShorthandMethod() { - return shorthandMethod; + return delegate.isShorthandMethod(); } - /** - * Sets whether to support getter style method name, so you can - * say the method is called 'name' but it will invoke the 'getName' method. - * <p/> - * Is by default turned off. - */ public void setShorthandMethod(boolean shorthandMethod) { - this.shorthandMethod = shorthandMethod; + delegate.setShorthandMethod(shorthandMethod); } - // Implementation methods - //------------------------------------------------------------------------- + @Override protected void doStart() throws Exception { - // optimize to only get (create) a processor if really needed - if (beanHolder.supportProcessor() && allowProcessor(method, beanHolder.getBeanInfo())) { - processor = beanHolder.getProcessor(); - ServiceHelper.startService(processor); - } else if (beanHolder instanceof ConstantBeanHolder) { - try { - // Start the bean if it implements Service interface and if cached - // so meant to be reused - ServiceHelper.startService(beanHolder.getBean()); - } catch (NoSuchBeanException e) { - // ignore - } - } + delegate.doStart(); } + @Override protected void doStop() throws Exception { - if (processor != null) { - ServiceHelper.stopService(processor); - } else if (beanHolder instanceof ConstantBeanHolder) { - try { - // Stop the bean if it implements Service interface and if cached - // so meant to be reused - ServiceHelper.stopService(beanHolder.getBean()); - } catch (NoSuchBeanException e) { - // ignore - } - } + delegate.doStop(); } - private boolean allowProcessor(String explicitMethodName, BeanInfo info) { - if (explicitMethodName != null) { - // don't allow if explicit method name is given, as we then must invoke this method - return false; + private static final class DelegateBeanProcessor extends AbstractBeanProcessor { + + public DelegateBeanProcessor(Object pojo, BeanInfo beanInfo) { + super(pojo, beanInfo); } - // don't allow if any of the methods has a @Handler annotation - // as the @Handler annotation takes precedence and is supposed to trigger invocation - // of the given method - for (MethodInfo method : info.getMethods()) { - if (method.hasHandlerAnnotation()) { - return false; - } + public DelegateBeanProcessor(Object pojo, CamelContext camelContext, ParameterMappingStrategy parameterMappingStrategy) { + super(pojo, camelContext, parameterMappingStrategy); } - // fallback and allow using the processor - return true; + public DelegateBeanProcessor(Object pojo, CamelContext camelContext) { + super(pojo, camelContext); + } + + public DelegateBeanProcessor(BeanHolder beanHolder) { + super(beanHolder); + } } + } http://git-wip-us.apache.org/repos/asf/camel/blob/b3ca9219/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java b/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java index 3b70cd8..020733f 100644 --- a/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java +++ b/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java @@ -26,8 +26,8 @@ import org.apache.camel.Expression; import org.apache.camel.ExpressionIllegalSyntaxException; import org.apache.camel.Predicate; import org.apache.camel.Processor; +import org.apache.camel.component.bean.BeanExpressionProcessor; import org.apache.camel.component.bean.BeanHolder; -import org.apache.camel.component.bean.BeanProcessor; import org.apache.camel.component.bean.ConstantBeanHolder; import org.apache.camel.component.bean.ConstantTypeBeanHolder; import org.apache.camel.component.bean.RegistryBean; @@ -197,7 +197,7 @@ public class BeanExpression implements Expression, Predicate { } public void process(Exchange exchange) throws Exception { - BeanProcessor processor = new BeanProcessor(beanHolder); + BeanExpressionProcessor processor = new BeanExpressionProcessor(beanHolder); if (methodName != null) { processor.setMethod(methodName); // enable OGNL like invocation