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: 42486 $ 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 216 // the message body 217 Message in = exchange.getIn(); 218 Object body = in.getBody(); 219 if (body != null) { 220 Class bodyType = body.getClass(); 221 222 List<MethodInfo> possibles = new ArrayList<MethodInfo>(); 223 for (MethodInfo methodInfo : operationList) { 224 if (methodInfo.bodyParameterMatches(bodyType)) { 225 possibles.add(methodInfo); 226 } 227 } 228 if (possibles.size() == 1) { 229 return possibles.get(0); 230 } else if (possibles.isEmpty()) { 231 // lets try converting 232 Object newBody = null; 233 MethodInfo matched = null; 234 for (MethodInfo methodInfo : operationList) { 235 Object value = convertToType(exchange, methodInfo.getBodyParameterType(), body); 236 if (value != null) { 237 if (newBody != null) { 238 throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched, 239 methodInfo)); 240 } else { 241 newBody = value; 242 matched = methodInfo; 243 } 244 } 245 } 246 if (matched != null) { 247 in.setBody(newBody); 248 return matched; 249 } 250 } else { 251 // if we only have a single method with custom annotations, lets use that one 252 if (operationsWithCustomAnnotation.size() == 1) { 253 return operationsWithCustomAnnotation.get(0); 254 } 255 return chooseMethodWithCustomAnnotations(exchange, possibles); 256 } 257 } 258 return null; 259 } 260 261 protected MethodInfo chooseMethodWithCustomAnnotations(Exchange exchange, Collection<MethodInfo> possibles) throws AmbiguousMethodCallException { 262 // if we have only one method with custom annotations lets choose that 263 MethodInfo chosen = null; 264 for (MethodInfo possible : possibles) { 265 if (possible.isHasCustomAnnotation()) { 266 if (chosen != null) { 267 chosen = null; 268 break; 269 } else { 270 chosen = possible; 271 } 272 } 273 } 274 if (chosen != null) { 275 return chosen; 276 } 277 throw new AmbiguousMethodCallException(exchange, possibles); 278 } 279 280 /** 281 * Creates an expression for the given parameter type if the parameter can 282 * be mapped automatically or null if the parameter cannot be mapped due to 283 * unsufficient annotations or not fitting with the default type 284 * conventions. 285 */ 286 protected Expression createParameterUnmarshalExpression(Class clazz, Method method, Class parameterType, 287 Annotation[] parameterAnnotation) { 288 289 // TODO look for a parameter annotation that converts into an expression 290 for (Annotation annotation : parameterAnnotation) { 291 Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType, 292 annotation); 293 if (answer != null) { 294 return answer; 295 } 296 } 297 return strategy.getDefaultParameterTypeExpression(parameterType); 298 } 299 300 protected boolean isPossibleBodyParameter(Annotation[] annotations) { 301 if (annotations != null) { 302 for (Annotation annotation : annotations) { 303 if ((annotation instanceof Property) 304 || (annotation instanceof Header) 305 || (annotation instanceof Headers) 306 || (annotation instanceof OutHeaders) 307 || (annotation instanceof Properties)) { 308 return false; 309 } 310 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class); 311 if (languageAnnotation != null) { 312 return false; 313 } 314 } 315 } 316 return true; 317 } 318 319 protected Expression createParameterUnmarshalExpressionForAnnotation(Class clazz, Method method, 320 Class parameterType, 321 Annotation annotation) { 322 if (annotation instanceof Property) { 323 Property propertyAnnotation = (Property)annotation; 324 return ExpressionBuilder.propertyExpression(propertyAnnotation.name()); 325 } else if (annotation instanceof Properties) { 326 return ExpressionBuilder.propertiesExpression(); 327 } else if (annotation instanceof Header) { 328 Header headerAnnotation = (Header)annotation; 329 return ExpressionBuilder.headerExpression(headerAnnotation.name()); 330 } else if (annotation instanceof Headers) { 331 return ExpressionBuilder.headersExpression(); 332 } else if (annotation instanceof OutHeaders) { 333 return ExpressionBuilder.outHeadersExpression(); 334 } else { 335 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class); 336 if (languageAnnotation != null) { 337 Class<?> type = languageAnnotation.factory(); 338 Object object = camelContext.getInjector().newInstance(type); 339 if (object instanceof AnnotationExpressionFactory) { 340 AnnotationExpressionFactory expressionFactory = (AnnotationExpressionFactory) object; 341 return expressionFactory.createExpression(camelContext, annotation, languageAnnotation, parameterType); 342 } else { 343 LOG.error("Ignoring bad annotation: " + languageAnnotation + "on method: " + method 344 + " which declares a factory: " + type.getName() 345 + " which does not implement " + AnnotationExpressionFactory.class.getName()); 346 } 347 } 348 } 349 350 return null; 351 } 352 353 protected boolean isValidMethod(Class clazz, Method method) { 354 return Modifier.isPublic(method.getModifiers()); 355 } 356 357 public static ParameterMappingStrategy createParameterMappingStrategy(CamelContext camelContext) { 358 Registry registry = camelContext.getRegistry(); 359 ParameterMappingStrategy answer = registry.lookup(ParameterMappingStrategy.class.getName(), 360 ParameterMappingStrategy.class); 361 if (answer == null) { 362 answer = new DefaultParameterMappingStrategy(); 363 } 364 return answer; 365 } 366 }