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