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     */
017    package org.apache.camel.component.bean;
018    
019    import java.lang.reflect.InvocationTargetException;
020    import java.lang.reflect.Method;
021    import java.util.concurrent.atomic.AtomicBoolean;
022    
023    import org.apache.camel.AsyncCallback;
024    import org.apache.camel.AsyncProcessor;
025    import org.apache.camel.CamelContext;
026    import org.apache.camel.Exchange;
027    import org.apache.camel.Message;
028    import org.apache.camel.Processor;
029    import org.apache.camel.support.ServiceSupport;
030    import org.apache.camel.util.AsyncProcessorHelper;
031    import org.apache.camel.util.ServiceHelper;
032    import org.slf4j.Logger;
033    import org.slf4j.LoggerFactory;
034    
035    /**
036     * A {@link Processor} which converts the inbound exchange to a method
037     * invocation on a POJO
038     *
039     * @version 
040     */
041    public class BeanProcessor extends ServiceSupport implements AsyncProcessor {
042        private static final transient Logger LOG = LoggerFactory.getLogger(BeanProcessor.class);
043    
044        private boolean multiParameterArray;
045        private Method methodObject;
046        private String method;
047        private BeanHolder beanHolder;
048        private boolean shorthandMethod;
049    
050        public BeanProcessor(Object pojo, BeanInfo beanInfo) {
051            this(new ConstantBeanHolder(pojo, beanInfo));
052        }
053    
054        public BeanProcessor(Object pojo, CamelContext camelContext, ParameterMappingStrategy parameterMappingStrategy) {
055            this(pojo, new BeanInfo(camelContext, pojo.getClass(), parameterMappingStrategy));
056        }
057    
058        public BeanProcessor(Object pojo, CamelContext camelContext) {
059            this(pojo, camelContext, BeanInfo.createParameterMappingStrategy(camelContext));
060        }
061    
062        public BeanProcessor(BeanHolder beanHolder) {
063            this.beanHolder = beanHolder;
064        }
065    
066        @Override
067        public String toString() {
068            String description = methodObject != null ? " " + methodObject : "";
069            return "BeanProcessor[" + beanHolder + description + "]";
070        }
071    
072        public void process(Exchange exchange) throws Exception {
073            AsyncProcessorHelper.process(this, exchange);
074        }
075    
076        public boolean process(Exchange exchange, AsyncCallback callback) {
077            // do we have an explicit method name we always should invoke (either configured on endpoint or as a header)
078            String explicitMethodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, method, String.class);
079    
080            Object bean;
081            BeanInfo beanInfo;
082            try {
083                bean = beanHolder.getBean();
084                beanInfo = beanHolder.getBeanInfo();
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                    LOG.trace("Using a custom adapter as bean invocation: {}", processor);
097                    try {
098                        processor.process(exchange);
099                    } catch (Throwable e) {
100                        exchange.setException(e);
101                    }
102                    callback.done(true);
103                    return true;
104                }
105            }
106    
107            Message in = exchange.getIn();
108    
109            // is the message proxied using a BeanInvocation?
110            BeanInvocation beanInvoke = null;
111            if (in.getBody() != null && in.getBody() instanceof BeanInvocation) {
112                // BeanInvocation would be stored directly as the message body
113                // do not force any type conversion attempts as it would just be unnecessary and cost a bit performance
114                // so a regular instanceof check is sufficient
115                beanInvoke = (BeanInvocation) in.getBody();
116            }
117            if (beanInvoke != null) {
118                // Now it gets a bit complicated as ProxyHelper can proxy beans which we later
119                // intend to invoke (for example to proxy and invoke using spring remoting).
120                // and therefore the message body contains a BeanInvocation object.
121                // However this can causes problem if we in a Camel route invokes another bean,
122                // so we must test whether BeanHolder and BeanInvocation is the same bean or not
123                LOG.trace("Exchange IN body is a BeanInvocation instance: {}", beanInvoke);
124                Class<?> clazz = beanInvoke.getMethod().getDeclaringClass();
125                boolean sameBean = clazz.isInstance(bean);
126                if (LOG.isDebugEnabled()) {
127                    LOG.debug("BeanHolder bean: {} and beanInvocation bean: {} is same instance: {}", new Object[]{bean.getClass(), clazz, sameBean});
128                }
129                if (sameBean) {
130                    beanInvoke.invoke(bean, exchange);
131                    // propagate headers
132                    exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
133                    callback.done(true);
134                    return true;
135                }
136            }
137    
138            // set temporary header which is a hint for the bean info that introspect the bean
139            if (in.getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) == null) {
140                in.setHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, isMultiParameterArray());
141            }
142    
143            MethodInvocation invocation;
144            // set explicit method name to invoke as a header, which is how BeanInfo can detect it
145            if (explicitMethodName != null) {
146                in.setHeader(Exchange.BEAN_METHOD_NAME, explicitMethodName);
147            }
148            try {
149                invocation = beanInfo.createInvocation(bean, exchange);
150            } catch (Throwable e) {
151                exchange.setException(e);
152                callback.done(true);
153                return true;
154            } finally {
155                // must remove headers as they were provisional
156                in.removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY);
157                in.removeHeader(Exchange.BEAN_METHOD_NAME);
158            }
159            if (invocation == null) {
160                throw new IllegalStateException("No method invocation could be created, no matching method could be found on: " + bean);
161            }
162    
163            Object value;
164            try {
165                AtomicBoolean sync = new AtomicBoolean(true);
166                value = invocation.proceed(callback, sync);
167                if (!sync.get()) {
168                    LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId());
169                    // the remainder of the routing will be completed async
170                    // so we break out now, then the callback will be invoked which then continue routing from where we left here
171                    return false;
172                }
173    
174                LOG.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId());
175            } catch (InvocationTargetException e) {
176                // let's unwrap the exception when it's an invocation target exception
177                exchange.setException(e.getCause());
178                callback.done(true);
179                return true;
180            } catch (Throwable e) {
181                exchange.setException(e);
182                callback.done(true);
183                return true;
184            }
185    
186            // if the method returns something then set the value returned on the Exchange
187            if (!invocation.getMethod().getReturnType().equals(Void.TYPE) && value != Void.TYPE) {
188                if (exchange.getPattern().isOutCapable()) {
189                    // force out creating if not already created (as its lazy)
190                    LOG.debug("Setting bean invocation result on the OUT message: {}", value);
191                    exchange.getOut().setBody(value);
192                    // propagate headers
193                    exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
194                } else {
195                    // if not out then set it on the in
196                    LOG.debug("Setting bean invocation result on the IN message: {}", value);
197                    exchange.getIn().setBody(value);
198                }
199            }
200    
201            callback.done(true);
202            return true;
203        }
204    
205        protected Processor getProcessor() {
206            return beanHolder.getProcessor();
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        /**
229         * Sets the method name to use
230         */
231        public void setMethod(String method) {
232            this.method = method;
233        }
234    
235        public boolean isShorthandMethod() {
236            return shorthandMethod;
237        }
238    
239        /**
240         * Sets whether to support getter style method name, so you can
241         * say the method is called 'name' but it will invoke the 'getName' method.
242         * <p/>
243         * Is by default turned off.
244         */
245        public void setShorthandMethod(boolean shorthandMethod) {
246            this.shorthandMethod = shorthandMethod;
247        }
248    
249        // Implementation methods
250        //-------------------------------------------------------------------------
251        protected void doStart() throws Exception {
252            ServiceHelper.startService(getProcessor());
253        }
254    
255        protected void doStop() throws Exception {
256            ServiceHelper.stopService(getProcessor());
257        }
258    
259        private boolean allowProcessor(String explicitMethodName, BeanInfo info) {
260            if (explicitMethodName != null) {
261                // don't allow if explicit method name is given, as we then must invoke this method
262                return false;
263            }
264    
265            // don't allow if any of the methods has a @Handler annotation
266            // as the @Handler annotation takes precedence and is supposed to trigger invocation
267            // of the given method
268            for (MethodInfo method : info.getMethods()) {
269                if (method.hasHandlerAnnotation()) {
270                    return false;
271                }
272            }
273    
274            // fallback and allow using the processor
275            return true;
276        }
277    }