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