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