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