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