001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.component.bean;
018
019import org.apache.camel.AsyncCallback;
020import org.apache.camel.AsyncProcessor;
021import org.apache.camel.CamelContext;
022import org.apache.camel.Exchange;
023import org.apache.camel.Message;
024import org.apache.camel.Processor;
025import org.apache.camel.support.ServiceSupport;
026import org.apache.camel.util.AsyncProcessorHelper;
027import org.apache.camel.util.ServiceHelper;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * A {@link Processor} which converts the inbound exchange to a method
033 * invocation on a POJO
034 *
035 * @version 
036 */
037public class BeanProcessor extends ServiceSupport implements AsyncProcessor {
038    private static final Logger LOG = LoggerFactory.getLogger(BeanProcessor.class);
039
040    private final BeanHolder beanHolder;
041    private Processor processor;
042    private boolean multiParameterArray;
043    private String method;
044    private boolean shorthandMethod;
045
046    public BeanProcessor(Object pojo, BeanInfo beanInfo) {
047        this(new ConstantBeanHolder(pojo, beanInfo));
048    }
049
050    public BeanProcessor(Object pojo, CamelContext camelContext, ParameterMappingStrategy parameterMappingStrategy) {
051        this(pojo, new BeanInfo(camelContext, pojo.getClass(), parameterMappingStrategy));
052    }
053
054    public BeanProcessor(Object pojo, CamelContext camelContext) {
055        this(pojo, camelContext, BeanInfo.createParameterMappingStrategy(camelContext));
056    }
057
058    public BeanProcessor(BeanHolder beanHolder) {
059        this.beanHolder = beanHolder;
060    }
061
062    @Override
063    public String toString() {
064        return "BeanProcessor[" + beanHolder + "]";
065    }
066
067    public void process(Exchange exchange) throws Exception {
068        AsyncProcessorHelper.process(this, exchange);
069    }
070
071    public boolean process(Exchange exchange, AsyncCallback callback) {
072        // do we have an explicit method name we always should invoke (either configured on endpoint or as a header)
073        String explicitMethodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, method, String.class);
074
075        Object bean;
076        BeanInfo beanInfo;
077        try {
078            bean = beanHolder.getBean();
079            // get bean info for this bean instance (to avoid thread issue)
080            beanInfo = beanHolder.getBeanInfo(bean);
081            if (beanInfo == null) {
082                // fallback and use old way
083                beanInfo = beanHolder.getBeanInfo();
084            }
085        } catch (Throwable e) {
086            exchange.setException(e);
087            callback.done(true);
088            return true;
089        }
090
091        // do we have a custom adapter for this POJO to a Processor
092        // but only do this if allowed
093        if (allowProcessor(explicitMethodName, beanInfo)) {
094            Processor processor = getProcessor();
095            if (processor == null) {
096                // so if there is a custom type converter for the bean to processor
097                processor = exchange.getContext().getTypeConverter().tryConvertTo(Processor.class, exchange, bean);
098            }
099            if (processor != null) {
100                LOG.trace("Using a custom adapter as bean invocation: {}", processor);
101                try {
102                    processor.process(exchange);
103                } catch (Throwable e) {
104                    exchange.setException(e);
105                }
106                callback.done(true);
107                return true;
108            }
109        }
110
111        Message in = exchange.getIn();
112
113        // is the message proxied using a BeanInvocation?
114        BeanInvocation beanInvoke = null;
115        if (in.getBody() != null && in.getBody() instanceof BeanInvocation) {
116            // BeanInvocation would be stored directly as the message body
117            // do not force any type conversion attempts as it would just be unnecessary and cost a bit performance
118            // so a regular instanceof check is sufficient
119            beanInvoke = (BeanInvocation) in.getBody();
120        }
121        if (beanInvoke != null) {
122            // Now it gets a bit complicated as ProxyHelper can proxy beans which we later
123            // intend to invoke (for example to proxy and invoke using spring remoting).
124            // and therefore the message body contains a BeanInvocation object.
125            // However this can causes problem if we in a Camel route invokes another bean,
126            // so we must test whether BeanHolder and BeanInvocation is the same bean or not
127            LOG.trace("Exchange IN body is a BeanInvocation instance: {}", beanInvoke);
128            Class<?> clazz = beanInvoke.getMethod().getDeclaringClass();
129            boolean sameBean = clazz.isInstance(bean);
130            if (LOG.isDebugEnabled()) {
131                LOG.debug("BeanHolder bean: {} and beanInvocation bean: {} is same instance: {}", new Object[]{bean.getClass(), clazz, sameBean});
132            }
133            if (sameBean) {
134                try {
135                    beanInvoke.invoke(bean, exchange);
136                    if (exchange.hasOut()) {
137                        // propagate headers
138                        exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
139                    }
140                } catch (Throwable e) {
141                    exchange.setException(e);
142                }
143                callback.done(true);
144                return true;
145            }
146        }
147
148        // set temporary header which is a hint for the bean info that introspect the bean
149        if (in.getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) == null) {
150            in.setHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, isMultiParameterArray());
151        }
152
153        MethodInvocation invocation;
154        // set explicit method name to invoke as a header, which is how BeanInfo can detect it
155        if (explicitMethodName != null) {
156            in.setHeader(Exchange.BEAN_METHOD_NAME, explicitMethodName);
157        }
158        try {
159            invocation = beanInfo.createInvocation(bean, exchange);
160        } catch (Throwable e) {
161            exchange.setException(e);
162            callback.done(true);
163            return true;
164        } finally {
165            // must remove headers as they were provisional
166            in.removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY);
167            in.removeHeader(Exchange.BEAN_METHOD_NAME);
168        }
169
170        if (invocation == null) {
171            exchange.setException(new IllegalStateException("No method invocation could be created, no matching method could be found on: " + bean));
172            callback.done(true);
173            return true;
174        }
175
176        // invoke invocation
177        return invocation.proceed(callback);
178    }
179
180    protected Processor getProcessor() {
181        return processor;
182    }
183
184    public Object getBean() {
185        return beanHolder.getBean();
186    }
187
188    // Properties
189    // -----------------------------------------------------------------------
190
191    public String getMethod() {
192        return method;
193    }
194
195    public boolean isMultiParameterArray() {
196        return multiParameterArray;
197    }
198
199    public void setMultiParameterArray(boolean mpArray) {
200        multiParameterArray = mpArray;
201    }
202
203    /**
204     * Sets the method name to use
205     */
206    public void setMethod(String method) {
207        this.method = method;
208    }
209
210    public boolean isShorthandMethod() {
211        return shorthandMethod;
212    }
213
214    /**
215     * Sets whether to support getter style method name, so you can
216     * say the method is called 'name' but it will invoke the 'getName' method.
217     * <p/>
218     * Is by default turned off.
219     */
220    public void setShorthandMethod(boolean shorthandMethod) {
221        this.shorthandMethod = shorthandMethod;
222    }
223
224    // Implementation methods
225    //-------------------------------------------------------------------------
226    protected void doStart() throws Exception {
227        // optimize to only get (create) a processor if really needed
228        if (beanHolder.supportProcessor() && allowProcessor(method, beanHolder.getBeanInfo())) {
229            processor = beanHolder.getProcessor();
230            ServiceHelper.startService(processor);
231        }
232    }
233
234    protected void doStop() throws Exception {
235        ServiceHelper.stopService(processor);
236    }
237
238    private boolean allowProcessor(String explicitMethodName, BeanInfo info) {
239        if (explicitMethodName != null) {
240            // don't allow if explicit method name is given, as we then must invoke this method
241            return false;
242        }
243
244        // don't allow if any of the methods has a @Handler annotation
245        // as the @Handler annotation takes precedence and is supposed to trigger invocation
246        // of the given method
247        for (MethodInfo method : info.getMethods()) {
248            if (method.hasHandlerAnnotation()) {
249                return false;
250            }
251        }
252
253        // fallback and allow using the processor
254        return true;
255    }
256}