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