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