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