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