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 Processor processor;
041    private boolean multiParameterArray;
042    private String method;
043    private BeanHolder beanHolder;
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().convertTo(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                beanInvoke.invoke(bean, exchange);
135                // propagate headers
136                exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
137                callback.done(true);
138                return true;
139            }
140        }
141
142        // set temporary header which is a hint for the bean info that introspect the bean
143        if (in.getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) == null) {
144            in.setHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, isMultiParameterArray());
145        }
146
147        MethodInvocation invocation;
148        // set explicit method name to invoke as a header, which is how BeanInfo can detect it
149        if (explicitMethodName != null) {
150            in.setHeader(Exchange.BEAN_METHOD_NAME, explicitMethodName);
151        }
152        try {
153            invocation = beanInfo.createInvocation(bean, exchange);
154        } catch (Throwable e) {
155            exchange.setException(e);
156            callback.done(true);
157            return true;
158        } finally {
159            // must remove headers as they were provisional
160            in.removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY);
161            in.removeHeader(Exchange.BEAN_METHOD_NAME);
162        }
163
164        if (invocation == null) {
165            exchange.setException(new IllegalStateException("No method invocation could be created, no matching method could be found on: " + bean));
166            callback.done(true);
167            return true;
168        }
169
170        // invoke invocation
171        return invocation.proceed(callback);
172    }
173
174    protected Processor getProcessor() {
175        return processor;
176    }
177
178    public Object getBean() {
179        return beanHolder.getBean();
180    }
181
182    // Properties
183    // -----------------------------------------------------------------------
184
185    public String getMethod() {
186        return method;
187    }
188
189    public boolean isMultiParameterArray() {
190        return multiParameterArray;
191    }
192
193    public void setMultiParameterArray(boolean mpArray) {
194        multiParameterArray = mpArray;
195    }
196
197    /**
198     * Sets the method name to use
199     */
200    public void setMethod(String method) {
201        this.method = method;
202    }
203
204    public boolean isShorthandMethod() {
205        return shorthandMethod;
206    }
207
208    /**
209     * Sets whether to support getter style method name, so you can
210     * say the method is called 'name' but it will invoke the 'getName' method.
211     * <p/>
212     * Is by default turned off.
213     */
214    public void setShorthandMethod(boolean shorthandMethod) {
215        this.shorthandMethod = shorthandMethod;
216    }
217
218    // Implementation methods
219    //-------------------------------------------------------------------------
220    protected void doStart() throws Exception {
221        // optimize to only get (create) a processor if really needed
222        if (beanHolder.supportProcessor() && allowProcessor(method, beanHolder.getBeanInfo())) {
223            processor = beanHolder.getProcessor();
224            ServiceHelper.startService(processor);
225        }
226    }
227
228    protected void doStop() throws Exception {
229        ServiceHelper.stopService(processor);
230    }
231
232    private boolean allowProcessor(String explicitMethodName, BeanInfo info) {
233        if (explicitMethodName != null) {
234            // don't allow if explicit method name is given, as we then must invoke this method
235            return false;
236        }
237
238        // don't allow if any of the methods has a @Handler annotation
239        // as the @Handler annotation takes precedence and is supposed to trigger invocation
240        // of the given method
241        for (MethodInfo method : info.getMethods()) {
242            if (method.hasHandlerAnnotation()) {
243                return false;
244            }
245        }
246
247        // fallback and allow using the processor
248        return true;
249    }
250}