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