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.Method; 021 import java.lang.reflect.Modifier; 022 import java.util.ArrayList; 023 import java.util.Arrays; 024 import java.util.Collection; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.concurrent.ConcurrentHashMap; 028 029 import org.apache.camel.Body; 030 import org.apache.camel.CamelContext; 031 import org.apache.camel.Exchange; 032 import org.apache.camel.Expression; 033 import org.apache.camel.Header; 034 import org.apache.camel.Headers; 035 import org.apache.camel.Message; 036 import org.apache.camel.OutHeaders; 037 import org.apache.camel.Properties; 038 import org.apache.camel.Property; 039 import org.apache.camel.RuntimeCamelException; 040 import org.apache.camel.builder.ExpressionBuilder; 041 import org.apache.camel.language.LanguageAnnotation; 042 import org.apache.camel.spi.Registry; 043 import org.apache.camel.util.ObjectHelper; 044 import org.apache.commons.logging.Log; 045 import org.apache.commons.logging.LogFactory; 046 047 import static org.apache.camel.util.ExchangeHelper.convertToType; 048 049 /** 050 * Represents the metadata about a bean type created via a combination of 051 * introspection and annotations together with some useful sensible defaults 052 * 053 * @version $Revision: 675938 $ 054 */ 055 public class BeanInfo { 056 private static final transient Log LOG = LogFactory.getLog(BeanInfo.class); 057 private final CamelContext camelContext; 058 private Class type; 059 private ParameterMappingStrategy strategy; 060 private Map<String, MethodInfo> operations = new ConcurrentHashMap<String, MethodInfo>(); 061 private MethodInfo defaultMethod; 062 private List<MethodInfo> operationsWithBody = new ArrayList<MethodInfo>(); 063 private List<MethodInfo> operationsWithCustomAnnotation = new ArrayList<MethodInfo>(); 064 065 public BeanInfo(CamelContext camelContext, Class type) { 066 this(camelContext, type, createParameterMappingStrategy(camelContext)); 067 } 068 069 public BeanInfo(CamelContext camelContext, Class type, ParameterMappingStrategy strategy) { 070 this.camelContext = camelContext; 071 this.type = type; 072 this.strategy = strategy; 073 introspect(getType()); 074 if (operations.size() == 1) { 075 Collection<MethodInfo> methodInfos = operations.values(); 076 for (MethodInfo methodInfo : methodInfos) { 077 defaultMethod = methodInfo; 078 } 079 } 080 } 081 082 public Class getType() { 083 return type; 084 } 085 086 public CamelContext getCamelContext() { 087 return camelContext; 088 } 089 090 public MethodInvocation createInvocation(Method method, Object pojo, Exchange exchange) 091 throws RuntimeCamelException { 092 MethodInfo methodInfo = introspect(type, method); 093 if (methodInfo != null) { 094 return methodInfo.createMethodInvocation(pojo, exchange); 095 } 096 return null; 097 } 098 099 public MethodInvocation createInvocation(Object pojo, Exchange exchange) throws RuntimeCamelException, 100 AmbiguousMethodCallException { 101 MethodInfo methodInfo = null; 102 103 // TODO use some other mechanism? 104 String name = exchange.getIn().getHeader(BeanProcessor.METHOD_NAME, String.class); 105 if (name != null) { 106 methodInfo = operations.get(name); 107 } 108 if (methodInfo == null) { 109 methodInfo = chooseMethod(pojo, exchange); 110 } 111 if (methodInfo == null) { 112 methodInfo = defaultMethod; 113 } 114 if (methodInfo != null) { 115 return methodInfo.createMethodInvocation(pojo, exchange); 116 } 117 return null; 118 } 119 120 protected void introspect(Class clazz) { 121 Method[] methods = clazz.getDeclaredMethods(); 122 for (Method method : methods) { 123 if (isValidMethod(clazz, method)) { 124 introspect(clazz, method); 125 } 126 } 127 Class superclass = clazz.getSuperclass(); 128 if (superclass != null && !superclass.equals(Object.class)) { 129 introspect(superclass); 130 } 131 } 132 133 protected MethodInfo introspect(Class clazz, Method method) { 134 Class[] parameterTypes = method.getParameterTypes(); 135 Annotation[][] parametersAnnotations = method.getParameterAnnotations(); 136 137 List<ParameterInfo> parameters = new ArrayList<ParameterInfo>(); 138 List<ParameterInfo> bodyParameters = new ArrayList<ParameterInfo>(); 139 140 boolean hasCustomAnnotation = false; 141 for (int i = 0; i < parameterTypes.length; i++) { 142 Class parameterType = parameterTypes[i]; 143 Annotation[] parameterAnnotations = parametersAnnotations[i]; 144 Expression expression = createParameterUnmarshalExpression(clazz, method, parameterType, 145 parameterAnnotations); 146 hasCustomAnnotation |= expression != null; 147 148 ParameterInfo parameterInfo = new ParameterInfo(i, parameterType, parameterAnnotations, 149 expression); 150 parameters.add(parameterInfo); 151 152 if (expression == null) { 153 hasCustomAnnotation |= ObjectHelper.hasAnnotation(parameterAnnotations, Body.class); 154 if (bodyParameters.isEmpty()) { 155 // lets assume its the body 156 if (Exchange.class.isAssignableFrom(parameterType)) { 157 expression = ExpressionBuilder.exchangeExpression(); 158 } else { 159 expression = ExpressionBuilder.bodyExpression(parameterType); 160 } 161 parameterInfo.setExpression(expression); 162 bodyParameters.add(parameterInfo); 163 } else { 164 if (LOG.isDebugEnabled()) { 165 LOG.debug("No expression available for method: " + method.toString() 166 + " which already has a body so ignoring parameter: " + i 167 + " so ignoring method"); 168 } 169 return null; 170 } 171 } 172 173 } 174 175 // now lets add the method to the repository 176 String opName = method.getName(); 177 178 // TODO allow an annotation to expose the operation name to use 179 /* if (method.getAnnotation(Operation.class) != null) { String name = 180 * method.getAnnotation(Operation.class).name(); if (name != null && 181 * name.length() > 0) { opName = name; } } 182 */ 183 MethodInfo methodInfo = new MethodInfo(clazz, method, parameters, bodyParameters, hasCustomAnnotation); 184 operations.put(opName, methodInfo); 185 if (methodInfo.hasBodyParameter()) { 186 operationsWithBody.add(methodInfo); 187 } 188 if (methodInfo.isHasCustomAnnotation() && !methodInfo.hasBodyParameter()) { 189 operationsWithCustomAnnotation.add(methodInfo); 190 } 191 return methodInfo; 192 } 193 194 /** 195 * Lets try choose one of the available methods to invoke if we can match 196 * the message body to the body parameter 197 * 198 * @param pojo the bean to invoke a method on 199 * @param exchange the message exchange 200 * @return the method to invoke or null if no definitive method could be 201 * matched 202 */ 203 protected MethodInfo chooseMethod(Object pojo, Exchange exchange) throws AmbiguousMethodCallException { 204 if (operationsWithBody.size() == 1) { 205 return operationsWithBody.get(0); 206 } else if (!operationsWithBody.isEmpty()) { 207 return chooseMethodWithMatchingBody(exchange, operationsWithBody); 208 } else if (operationsWithCustomAnnotation.size() == 1) { 209 return operationsWithCustomAnnotation.get(0); 210 } 211 return null; 212 } 213 214 protected MethodInfo chooseMethodWithMatchingBody(Exchange exchange, Collection<MethodInfo> operationList) throws AmbiguousMethodCallException { 215 // lets see if we can find a method who's body param type matches the message body 216 Message in = exchange.getIn(); 217 Object body = in.getBody(); 218 if (body != null) { 219 Class bodyType = body.getClass(); 220 221 List<MethodInfo> possibles = new ArrayList<MethodInfo>(); 222 for (MethodInfo methodInfo : operationList) { 223 // TODO: AOP proxies have additioan methods - consider having a static 224 // method exclude list to skip all known AOP proxy methods 225 // TODO: This class could use some TRACE logging 226 227 // test for MEP pattern matching 228 boolean out = exchange.getPattern().isOutCapable(); 229 if (out && methodInfo.isReturnTypeVoid()) { 230 // skip this method as the MEP is Out so the method must return someting 231 continue; 232 } 233 234 // try to match the arguments 235 if (methodInfo.bodyParameterMatches(bodyType)) { 236 possibles.add(methodInfo); 237 } 238 } 239 if (possibles.size() == 1) { 240 return possibles.get(0); 241 } else if (possibles.isEmpty()) { 242 // lets try converting 243 Object newBody = null; 244 MethodInfo matched = null; 245 for (MethodInfo methodInfo : operationList) { 246 Object value = convertToType(exchange, methodInfo.getBodyParameterType(), body); 247 if (value != null) { 248 if (newBody != null) { 249 throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched, 250 methodInfo)); 251 } else { 252 newBody = value; 253 matched = methodInfo; 254 } 255 } 256 } 257 if (matched != null) { 258 in.setBody(newBody); 259 return matched; 260 } 261 } else { 262 // if we only have a single method with custom annotations, lets use that one 263 if (operationsWithCustomAnnotation.size() == 1) { 264 return operationsWithCustomAnnotation.get(0); 265 } 266 return chooseMethodWithCustomAnnotations(exchange, possibles); 267 } 268 } 269 // no match so return null 270 return null; 271 } 272 273 protected MethodInfo chooseMethodWithCustomAnnotations(Exchange exchange, Collection<MethodInfo> possibles) throws AmbiguousMethodCallException { 274 // if we have only one method with custom annotations lets choose that 275 MethodInfo chosen = null; 276 for (MethodInfo possible : possibles) { 277 if (possible.isHasCustomAnnotation()) { 278 if (chosen != null) { 279 chosen = null; 280 break; 281 } else { 282 chosen = possible; 283 } 284 } 285 } 286 if (chosen != null) { 287 return chosen; 288 } 289 throw new AmbiguousMethodCallException(exchange, possibles); 290 } 291 292 /** 293 * Creates an expression for the given parameter type if the parameter can 294 * be mapped automatically or null if the parameter cannot be mapped due to 295 * unsufficient annotations or not fitting with the default type 296 * conventions. 297 */ 298 protected Expression createParameterUnmarshalExpression(Class clazz, Method method, Class parameterType, 299 Annotation[] parameterAnnotation) { 300 301 // TODO look for a parameter annotation that converts into an expression 302 for (Annotation annotation : parameterAnnotation) { 303 Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType, 304 annotation); 305 if (answer != null) { 306 return answer; 307 } 308 } 309 return strategy.getDefaultParameterTypeExpression(parameterType); 310 } 311 312 protected boolean isPossibleBodyParameter(Annotation[] annotations) { 313 if (annotations != null) { 314 for (Annotation annotation : annotations) { 315 if ((annotation instanceof Property) 316 || (annotation instanceof Header) 317 || (annotation instanceof Headers) 318 || (annotation instanceof OutHeaders) 319 || (annotation instanceof Properties)) { 320 return false; 321 } 322 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class); 323 if (languageAnnotation != null) { 324 return false; 325 } 326 } 327 } 328 return true; 329 } 330 331 protected Expression createParameterUnmarshalExpressionForAnnotation(Class clazz, Method method, 332 Class parameterType, 333 Annotation annotation) { 334 if (annotation instanceof Property) { 335 Property propertyAnnotation = (Property)annotation; 336 return ExpressionBuilder.propertyExpression(propertyAnnotation.name()); 337 } else if (annotation instanceof Properties) { 338 return ExpressionBuilder.propertiesExpression(); 339 } else if (annotation instanceof Header) { 340 Header headerAnnotation = (Header)annotation; 341 return ExpressionBuilder.headerExpression(headerAnnotation.name()); 342 } else if (annotation instanceof Headers) { 343 return ExpressionBuilder.headersExpression(); 344 } else if (annotation instanceof OutHeaders) { 345 return ExpressionBuilder.outHeadersExpression(); 346 } else { 347 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class); 348 if (languageAnnotation != null) { 349 Class<?> type = languageAnnotation.factory(); 350 Object object = camelContext.getInjector().newInstance(type); 351 if (object instanceof AnnotationExpressionFactory) { 352 AnnotationExpressionFactory expressionFactory = (AnnotationExpressionFactory) object; 353 return expressionFactory.createExpression(camelContext, annotation, languageAnnotation, parameterType); 354 } else { 355 LOG.error("Ignoring bad annotation: " + languageAnnotation + "on method: " + method 356 + " which declares a factory: " + type.getName() 357 + " which does not implement " + AnnotationExpressionFactory.class.getName()); 358 } 359 } 360 } 361 362 return null; 363 } 364 365 protected boolean isValidMethod(Class clazz, Method method) { 366 // must be a public method 367 if (!Modifier.isPublic(method.getModifiers())) { 368 return false; 369 } 370 371 // return type must not be an Exchange 372 if (method.getReturnType() != null && Exchange.class.isAssignableFrom(method.getReturnType())) { 373 return false; 374 } 375 376 return true; 377 } 378 379 public static ParameterMappingStrategy createParameterMappingStrategy(CamelContext camelContext) { 380 Registry registry = camelContext.getRegistry(); 381 ParameterMappingStrategy answer = registry.lookup(ParameterMappingStrategy.class.getName(), 382 ParameterMappingStrategy.class); 383 if (answer == null) { 384 answer = new DefaultParameterMappingStrategy(); 385 } 386 return answer; 387 } 388 }