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