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 java.lang.annotation.Annotation; 020 import java.lang.reflect.AccessibleObject; 021 import java.lang.reflect.AnnotatedElement; 022 import java.lang.reflect.InvocationTargetException; 023 import java.lang.reflect.Method; 024 import java.util.ArrayList; 025 import java.util.Arrays; 026 import java.util.List; 027 028 import org.apache.camel.CamelContext; 029 import org.apache.camel.Exchange; 030 import org.apache.camel.ExchangePattern; 031 import org.apache.camel.Expression; 032 import org.apache.camel.NoTypeConversionAvailableException; 033 import org.apache.camel.Pattern; 034 import org.apache.camel.processor.RecipientList; 035 import org.apache.camel.util.ObjectHelper; 036 import org.apache.commons.logging.Log; 037 import org.apache.commons.logging.LogFactory; 038 039 import static org.apache.camel.util.ObjectHelper.asString; 040 041 /** 042 * Information about a method to be used for invocation. 043 * 044 * @version $Revision: 788297 $ 045 */ 046 public class MethodInfo { 047 private static final transient Log LOG = LogFactory.getLog(MethodInfo.class); 048 049 private CamelContext camelContext; 050 private Class type; 051 private Method method; 052 private final List<ParameterInfo> parameters; 053 private final List<ParameterInfo> bodyParameters; 054 private final boolean hasCustomAnnotation; 055 private final boolean hasHandlerAnnotation; 056 private Expression parametersExpression; 057 private ExchangePattern pattern = ExchangePattern.InOut; 058 private RecipientList recipientList; 059 060 public MethodInfo(CamelContext camelContext, Class type, Method method, List<ParameterInfo> parameters, List<ParameterInfo> bodyParameters, 061 boolean hasCustomAnnotation, boolean hasHandlerAnnotation) { 062 this.camelContext = camelContext; 063 this.type = type; 064 this.method = method; 065 this.parameters = parameters; 066 this.bodyParameters = bodyParameters; 067 this.hasCustomAnnotation = hasCustomAnnotation; 068 this.hasHandlerAnnotation = hasHandlerAnnotation; 069 this.parametersExpression = createParametersExpression(); 070 071 Pattern oneway = findOneWayAnnotation(method); 072 if (oneway != null) { 073 pattern = oneway.value(); 074 } 075 076 if (method.getAnnotation(org.apache.camel.RecipientList.class) != null 077 && matchContext(method.getAnnotation(org.apache.camel.RecipientList.class).context())) { 078 recipientList = new RecipientList(); 079 } 080 } 081 082 /** 083 * Does the given context match this camel context 084 */ 085 private boolean matchContext(String context) { 086 if (ObjectHelper.isNotEmpty(context)) { 087 if (!camelContext.getName().equals(context)) { 088 return false; 089 } 090 } 091 return true; 092 } 093 094 095 096 public String toString() { 097 return method.toString(); 098 } 099 100 public MethodInvocation createMethodInvocation(final Object pojo, final Exchange exchange) { 101 final Object[] arguments = parametersExpression.evaluate(exchange, Object[].class); 102 return new MethodInvocation() { 103 public Method getMethod() { 104 return method; 105 } 106 107 public Object[] getArguments() { 108 return arguments; 109 } 110 111 public Object proceed() throws Exception { 112 if (LOG.isTraceEnabled()) { 113 LOG.trace(">>>> invoking: " + method + " on bean: " + pojo + " with arguments: " + asString(arguments) + " for exchange: " + exchange); 114 } 115 Object result = invoke(method, pojo, arguments, exchange); 116 if (recipientList != null) { 117 recipientList.sendToRecipientList(exchange, result); 118 } 119 return result; 120 } 121 122 public Object getThis() { 123 return pojo; 124 } 125 126 public AccessibleObject getStaticPart() { 127 return method; 128 } 129 }; 130 } 131 132 public Class getType() { 133 return type; 134 } 135 136 public Method getMethod() { 137 return method; 138 } 139 140 /** 141 * Returns the {@link org.apache.camel.ExchangePattern} that should be used when invoking this method. This value 142 * defaults to {@link org.apache.camel.ExchangePattern#InOut} unless some {@link org.apache.camel.Pattern} annotation is used 143 * to override the message exchange pattern. 144 * 145 * @return the exchange pattern to use for invoking this method. 146 */ 147 public ExchangePattern getPattern() { 148 return pattern; 149 } 150 151 public Expression getParametersExpression() { 152 return parametersExpression; 153 } 154 155 public List<ParameterInfo> getBodyParameters() { 156 return bodyParameters; 157 } 158 159 public Class getBodyParameterType() { 160 if (bodyParameters.isEmpty()) { 161 return null; 162 } 163 ParameterInfo parameterInfo = bodyParameters.get(0); 164 return parameterInfo.getType(); 165 } 166 167 public boolean bodyParameterMatches(Class bodyType) { 168 Class actualType = getBodyParameterType(); 169 return actualType != null && ObjectHelper.isAssignableFrom(bodyType, actualType); 170 } 171 172 public List<ParameterInfo> getParameters() { 173 return parameters; 174 } 175 176 public boolean hasBodyParameter() { 177 return !bodyParameters.isEmpty(); 178 } 179 180 public boolean hasCustomAnnotation() { 181 return hasCustomAnnotation; 182 } 183 184 public boolean hasHandlerAnnotation() { 185 return hasHandlerAnnotation; 186 } 187 188 public boolean isReturnTypeVoid() { 189 return method.getReturnType().getName().equals("void"); 190 } 191 192 protected Object invoke(Method mth, Object pojo, Object[] arguments, Exchange exchange) throws IllegalAccessException, InvocationTargetException { 193 return mth.invoke(pojo, arguments); 194 } 195 196 protected Expression createParametersExpression() { 197 final int size = parameters.size(); 198 if (LOG.isTraceEnabled()) { 199 LOG.trace("Creating parameters expression for " + size + " parameters"); 200 } 201 202 final Expression[] expressions = new Expression[size]; 203 for (int i = 0; i < size; i++) { 204 Expression parameterExpression = parameters.get(i).getExpression(); 205 expressions[i] = parameterExpression; 206 if (LOG.isTraceEnabled()) { 207 LOG.trace("Parameter #" + i + " has expression: " + parameterExpression); 208 } 209 } 210 return new Expression() { 211 @SuppressWarnings("unchecked") 212 public <T> T evaluate(Exchange exchange, Class<T> type) { 213 Object[] answer = new Object[size]; 214 Object body = exchange.getIn().getBody(); 215 boolean multiParameterArray = false; 216 if (exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) != null) { 217 multiParameterArray = exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, Boolean.class); 218 } 219 for (int i = 0; i < size; i++) { 220 Object value = null; 221 if (multiParameterArray) { 222 value = ((Object[])body)[i]; 223 } else { 224 Expression expression = expressions[i]; 225 if (expression != null) { 226 // use object first to avoid type convertions so we know if there is a value or not 227 Object result = expression.evaluate(exchange, Object.class); 228 if (result != null) { 229 // we got a value now try to convert it to the expected type 230 value = exchange.getContext().getTypeConverter().convertTo(parameters.get(i).getType(), result); 231 if (LOG.isTraceEnabled()) { 232 LOG.trace("Parameter #" + i + " evaluated as: " + value + " type: " + ObjectHelper.type(value)); 233 } 234 if (value == null) { 235 Exception e = new NoTypeConversionAvailableException(result, parameters.get(i).getType()); 236 throw ObjectHelper.wrapRuntimeCamelException(e); 237 } 238 } else { 239 if (LOG.isTraceEnabled()) { 240 LOG.trace("Parameter #" + i + " evaluated as null"); 241 } 242 } 243 } 244 } 245 // now lets try to coerce the value to the required type 246 answer[i] = value; 247 } 248 return (T) answer; 249 } 250 251 @Override 252 public String toString() { 253 return "ParametersExpression: " + Arrays.asList(expressions); 254 } 255 256 }; 257 } 258 259 /** 260 * Finds the oneway annotation in priority order; look for method level annotations first, then the class level annotations, 261 * then super class annotations then interface annotations 262 * 263 * @param method the method on which to search 264 * @return the first matching annotation or none if it is not available 265 */ 266 protected Pattern findOneWayAnnotation(Method method) { 267 Pattern answer = getPatternAnnotation(method); 268 if (answer == null) { 269 Class<?> type = method.getDeclaringClass(); 270 271 // lets create the search order of types to scan 272 List<Class<?>> typesToSearch = new ArrayList<Class<?>>(); 273 addTypeAndSuperTypes(type, typesToSearch); 274 Class[] interfaces = type.getInterfaces(); 275 for (Class anInterface : interfaces) { 276 addTypeAndSuperTypes(anInterface, typesToSearch); 277 } 278 279 // now lets scan for a type which the current declared class overloads 280 answer = findOneWayAnnotationOnMethod(typesToSearch, method); 281 if (answer == null) { 282 answer = findOneWayAnnotation(typesToSearch); 283 } 284 } 285 return answer; 286 } 287 288 /** 289 * Returns the pattern annotation on the given annotated element; either as a direct annotation or 290 * on an annotation which is also annotated 291 * 292 * @param annotatedElement the element to look for the annotation 293 * @return the first matching annotation or null if none could be found 294 */ 295 protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement) { 296 return getPatternAnnotation(annotatedElement, 2); 297 } 298 299 /** 300 * Returns the pattern annotation on the given annotated element; either as a direct annotation or 301 * on an annotation which is also annotated 302 * 303 * @param annotatedElement the element to look for the annotation 304 * @param depth the current depth 305 * @return the first matching annotation or null if none could be found 306 */ 307 protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement, int depth) { 308 Pattern answer = annotatedElement.getAnnotation(Pattern.class); 309 int nextDepth = depth - 1; 310 311 if (nextDepth > 0) { 312 // lets look at all the annotations to see if any of those are annotated 313 Annotation[] annotations = annotatedElement.getAnnotations(); 314 for (Annotation annotation : annotations) { 315 Class<? extends Annotation> annotationType = annotation.annotationType(); 316 if (annotation instanceof Pattern || annotationType.equals(annotatedElement)) { 317 continue; 318 } else { 319 Pattern another = getPatternAnnotation(annotationType, nextDepth); 320 if (pattern != null) { 321 if (answer == null) { 322 answer = another; 323 } else { 324 LOG.warn("Duplicate pattern annotation: " + another + " found on annotation: " + annotation + " which will be ignored"); 325 } 326 } 327 } 328 } 329 } 330 return answer; 331 } 332 333 /** 334 * Adds the current class and all of its base classes (apart from {@link Object} to the given list 335 */ 336 protected void addTypeAndSuperTypes(Class<?> type, List<Class<?>> result) { 337 for (Class<?> t = type; t != null && t != Object.class; t = t.getSuperclass()) { 338 result.add(t); 339 } 340 } 341 342 /** 343 * Finds the first annotation on the base methods defined in the list of classes 344 */ 345 protected Pattern findOneWayAnnotationOnMethod(List<Class<?>> classes, Method method) { 346 for (Class<?> type : classes) { 347 try { 348 Method definedMethod = type.getMethod(method.getName(), method.getParameterTypes()); 349 Pattern answer = getPatternAnnotation(definedMethod); 350 if (answer != null) { 351 return answer; 352 } 353 } catch (NoSuchMethodException e) { 354 // ignore 355 } 356 } 357 return null; 358 } 359 360 361 /** 362 * Finds the first annotation on the given list of classes 363 */ 364 protected Pattern findOneWayAnnotation(List<Class<?>> classes) { 365 for (Class<?> type : classes) { 366 Pattern answer = getPatternAnnotation(type); 367 if (answer != null) { 368 return answer; 369 } 370 } 371 return null; 372 } 373 374 protected boolean hasExceptionParameter() { 375 for (ParameterInfo parameter : parameters) { 376 if (Exception.class.isAssignableFrom(parameter.getType())) { 377 return true; 378 } 379 } 380 return false; 381 } 382 383 }