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