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 }