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 transient 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 beanInfo = beanHolder.getBeanInfo(); 079 } catch (Throwable e) { 080 exchange.setException(e); 081 callback.done(true); 082 return true; 083 } 084 085 // do we have a custom adapter for this POJO to a Processor 086 // but only do this if allowed 087 if (allowProcessor(explicitMethodName, beanInfo)) { 088 Processor processor = getProcessor(); 089 if (processor != null) { 090 LOG.trace("Using a custom adapter as bean invocation: {}", processor); 091 try { 092 processor.process(exchange); 093 } catch (Throwable e) { 094 exchange.setException(e); 095 } 096 callback.done(true); 097 return true; 098 } 099 } 100 101 Message in = exchange.getIn(); 102 103 // is the message proxied using a BeanInvocation? 104 BeanInvocation beanInvoke = null; 105 if (in.getBody() != null && in.getBody() instanceof BeanInvocation) { 106 // BeanInvocation would be stored directly as the message body 107 // do not force any type conversion attempts as it would just be unnecessary and cost a bit performance 108 // so a regular instanceof check is sufficient 109 beanInvoke = (BeanInvocation) in.getBody(); 110 } 111 if (beanInvoke != null) { 112 // Now it gets a bit complicated as ProxyHelper can proxy beans which we later 113 // intend to invoke (for example to proxy and invoke using spring remoting). 114 // and therefore the message body contains a BeanInvocation object. 115 // However this can causes problem if we in a Camel route invokes another bean, 116 // so we must test whether BeanHolder and BeanInvocation is the same bean or not 117 LOG.trace("Exchange IN body is a BeanInvocation instance: {}", beanInvoke); 118 Class<?> clazz = beanInvoke.getMethod().getDeclaringClass(); 119 boolean sameBean = clazz.isInstance(bean); 120 if (LOG.isDebugEnabled()) { 121 LOG.debug("BeanHolder bean: {} and beanInvocation bean: {} is same instance: {}", new Object[]{bean.getClass(), clazz, sameBean}); 122 } 123 if (sameBean) { 124 beanInvoke.invoke(bean, exchange); 125 // propagate headers 126 exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders()); 127 callback.done(true); 128 return true; 129 } 130 } 131 132 // set temporary header which is a hint for the bean info that introspect the bean 133 if (in.getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) == null) { 134 in.setHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, isMultiParameterArray()); 135 } 136 137 MethodInvocation invocation; 138 // set explicit method name to invoke as a header, which is how BeanInfo can detect it 139 if (explicitMethodName != null) { 140 in.setHeader(Exchange.BEAN_METHOD_NAME, explicitMethodName); 141 } 142 try { 143 invocation = beanInfo.createInvocation(bean, exchange); 144 } catch (Throwable e) { 145 exchange.setException(e); 146 callback.done(true); 147 return true; 148 } finally { 149 // must remove headers as they were provisional 150 in.removeHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY); 151 in.removeHeader(Exchange.BEAN_METHOD_NAME); 152 } 153 154 if (invocation == null) { 155 exchange.setException(new IllegalStateException("No method invocation could be created, no matching method could be found on: " + bean)); 156 callback.done(true); 157 return true; 158 } 159 160 // invoke invocation 161 return invocation.proceed(callback); 162 } 163 164 protected Processor getProcessor() { 165 return beanHolder.getProcessor(); 166 } 167 168 public Object getBean() { 169 return beanHolder.getBean(); 170 } 171 172 // Properties 173 // ----------------------------------------------------------------------- 174 175 public String getMethod() { 176 return method; 177 } 178 179 public boolean isMultiParameterArray() { 180 return multiParameterArray; 181 } 182 183 public void setMultiParameterArray(boolean mpArray) { 184 multiParameterArray = mpArray; 185 } 186 187 /** 188 * Sets the method name to use 189 */ 190 public void setMethod(String method) { 191 this.method = method; 192 } 193 194 public boolean isShorthandMethod() { 195 return shorthandMethod; 196 } 197 198 /** 199 * Sets whether to support getter style method name, so you can 200 * say the method is called 'name' but it will invoke the 'getName' method. 201 * <p/> 202 * Is by default turned off. 203 */ 204 public void setShorthandMethod(boolean shorthandMethod) { 205 this.shorthandMethod = shorthandMethod; 206 } 207 208 // Implementation methods 209 //------------------------------------------------------------------------- 210 protected void doStart() throws Exception { 211 ServiceHelper.startService(getProcessor()); 212 } 213 214 protected void doStop() throws Exception { 215 ServiceHelper.stopService(getProcessor()); 216 } 217 218 private boolean allowProcessor(String explicitMethodName, BeanInfo info) { 219 if (explicitMethodName != null) { 220 // don't allow if explicit method name is given, as we then must invoke this method 221 return false; 222 } 223 224 // don't allow if any of the methods has a @Handler annotation 225 // as the @Handler annotation takes precedence and is supposed to trigger invocation 226 // of the given method 227 for (MethodInfo method : info.getMethods()) { 228 if (method.hasHandlerAnnotation()) { 229 return false; 230 } 231 } 232 233 // fallback and allow using the processor 234 return true; 235 } 236 }