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