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.Exchange; 031 import org.apache.camel.Expression; 032 import org.apache.camel.Header; 033 import org.apache.camel.Message; 034 import org.apache.camel.Property; 035 import org.apache.camel.RuntimeCamelException; 036 import org.apache.camel.builder.ExpressionBuilder; 037 import org.apache.commons.logging.Log; 038 import org.apache.commons.logging.LogFactory; 039 040 import static org.apache.camel.util.ExchangeHelper.convertToType; 041 042 /** 043 * Represents the metadata about a bean type created via a combination of 044 * introspection and annotations together with some useful sensible defaults 045 * 046 * @version $Revision: $ 047 */ 048 public class BeanInfo { 049 private static final transient Log LOG = LogFactory.getLog(BeanInfo.class); 050 private Class type; 051 private ParameterMappingStrategy strategy; 052 private Map<String, MethodInfo> operations = new ConcurrentHashMap<String, MethodInfo>(); 053 private MethodInfo defaultMethod; 054 private List<MethodInfo> operationsWithBody = new ArrayList<MethodInfo>(); 055 056 public BeanInfo(Class type, ParameterMappingStrategy strategy) { 057 this.type = type; 058 this.strategy = strategy; 059 introspect(getType()); 060 if (operations.size() == 1) { 061 Collection<MethodInfo> methodInfos = operations.values(); 062 for (MethodInfo methodInfo : methodInfos) { 063 defaultMethod = methodInfo; 064 } 065 } 066 } 067 068 public Class getType() { 069 return type; 070 } 071 072 public MethodInvocation createInvocation(Method method, Object pojo, Exchange exchange) 073 throws RuntimeCamelException { 074 MethodInfo methodInfo = introspect(type, method); 075 if (methodInfo != null) { 076 return methodInfo.createMethodInvocation(pojo, exchange); 077 } 078 return null; 079 } 080 081 public MethodInvocation createInvocation(Object pojo, Exchange exchange) throws RuntimeCamelException, 082 AmbiguousMethodCallException { 083 MethodInfo methodInfo = null; 084 085 // TODO use some other mechanism? 086 String name = exchange.getIn().getHeader(BeanProcessor.METHOD_NAME, String.class); 087 if (name != null) { 088 methodInfo = operations.get(name); 089 } 090 if (methodInfo == null) { 091 methodInfo = chooseMethod(pojo, exchange); 092 } 093 if (methodInfo == null) { 094 methodInfo = defaultMethod; 095 } 096 if (methodInfo != null) { 097 return methodInfo.createMethodInvocation(pojo, exchange); 098 } 099 return null; 100 } 101 102 protected void introspect(Class clazz) { 103 Method[] methods = clazz.getDeclaredMethods(); 104 for (Method method : methods) { 105 if (isValidMethod(clazz, method)) { 106 introspect(clazz, method); 107 } 108 } 109 Class superclass = clazz.getSuperclass(); 110 if (superclass != null && !superclass.equals(Object.class)) { 111 introspect(superclass); 112 } 113 } 114 115 protected MethodInfo introspect(Class clazz, Method method) { 116 Class[] parameterTypes = method.getParameterTypes(); 117 Annotation[][] parametersAnnotations = method.getParameterAnnotations(); 118 final Expression[] parameterExpressions = new Expression[parameterTypes.length]; 119 120 List<ParameterInfo> parameters = new ArrayList<ParameterInfo>(); 121 List<ParameterInfo> bodyParameters = new ArrayList<ParameterInfo>(); 122 123 for (int i = 0; i < parameterTypes.length; i++) { 124 Class parameterType = parameterTypes[i]; 125 Annotation[] parameterAnnotations = parametersAnnotations[i]; 126 Expression expression = createParameterUnmarshalExpression(clazz, method, parameterType, 127 parameterAnnotations); 128 if (expression == null) { 129 if (parameterTypes.length == 1 && bodyParameters.isEmpty()) { 130 // lets assume its the body 131 expression = ExpressionBuilder.bodyExpression(parameterType); 132 } else { 133 if (LOG.isDebugEnabled()) { 134 LOG.debug("No expression available for method: " + method.toString() 135 + " which already has a body so ignoring parameter: " + i 136 + " so ignoring method"); 137 } 138 return null; 139 } 140 } 141 142 ParameterInfo parameterInfo = new ParameterInfo(i, parameterType, parameterAnnotations, 143 expression); 144 parameters.add(parameterInfo); 145 if (isPossibleBodyParameter(parameterAnnotations)) { 146 bodyParameters.add(parameterInfo); 147 } 148 } 149 150 // now lets add the method to the repository 151 String opName = method.getName(); 152 153 /* 154 * 155 * TODO allow an annotation to expose the operation name to use 156 * 157 * if (method.getAnnotation(Operation.class) != null) { String name = 158 * method.getAnnotation(Operation.class).name(); if (name != null && 159 * name.length() > 0) { opName = name; } } 160 */ 161 MethodInfo methodInfo = new MethodInfo(clazz, method, parameters, bodyParameters); 162 operations.put(opName, methodInfo); 163 if (methodInfo.hasBodyParameter()) { 164 operationsWithBody.add(methodInfo); 165 } 166 return methodInfo; 167 } 168 169 /** 170 * Lets try choose one of the available methods to invoke if we can match 171 * the message body to the body parameter 172 * 173 * @param pojo the bean to invoke a method on 174 * @param exchange the message exchange 175 * @return the method to invoke or null if no definitive method could be 176 * matched 177 */ 178 protected MethodInfo chooseMethod(Object pojo, Exchange exchange) throws AmbiguousMethodCallException { 179 if (operationsWithBody.size() == 1) { 180 return operationsWithBody.get(0); 181 } else if (!operationsWithBody.isEmpty()) { 182 // lets see if we can find a method who's body param type matches 183 // the message body 184 Message in = exchange.getIn(); 185 Object body = in.getBody(); 186 if (body != null) { 187 Class bodyType = body.getClass(); 188 189 List<MethodInfo> possibles = new ArrayList<MethodInfo>(); 190 for (MethodInfo methodInfo : operationsWithBody) { 191 if (methodInfo.bodyParameterMatches(bodyType)) { 192 possibles.add(methodInfo); 193 } 194 } 195 if (possibles.size() == 1) { 196 return possibles.get(0); 197 } else if (possibles.isEmpty()) { 198 // lets try converting 199 Object newBody = null; 200 MethodInfo matched = null; 201 for (MethodInfo methodInfo : operationsWithBody) { 202 Object value = convertToType(exchange, methodInfo.getBodyParameterType(), body); 203 if (value != null) { 204 if (newBody != null) { 205 throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched, 206 methodInfo)); 207 } else { 208 newBody = value; 209 matched = methodInfo; 210 } 211 } 212 } 213 if (matched != null) { 214 in.setBody(newBody); 215 return matched; 216 } 217 } else { 218 throw new AmbiguousMethodCallException(exchange, possibles); 219 } 220 } 221 return null; 222 } 223 return null; 224 } 225 226 /** 227 * Creates an expression for the given parameter type if the parameter can 228 * be mapped automatically or null if the parameter cannot be mapped due to 229 * unsufficient annotations or not fitting with the default type 230 * conventions. 231 */ 232 protected Expression createParameterUnmarshalExpression(Class clazz, Method method, Class parameterType, 233 Annotation[] parameterAnnotation) { 234 235 // TODO look for a parameter annotation that converts into an expression 236 for (Annotation annotation : parameterAnnotation) { 237 Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType, 238 annotation); 239 if (answer != null) { 240 return answer; 241 } 242 } 243 return strategy.getDefaultParameterTypeExpression(parameterType); 244 } 245 246 protected boolean isPossibleBodyParameter(Annotation[] annotations) { 247 if (annotations != null) { 248 for (Annotation annotation : annotations) { 249 if ((annotation instanceof Property) || (annotation instanceof Header)) { 250 return false; 251 } 252 } 253 } 254 return true; 255 } 256 257 protected Expression createParameterUnmarshalExpressionForAnnotation(Class clazz, Method method, 258 Class parameterType, 259 Annotation annotation) { 260 if (annotation instanceof Property) { 261 Property propertyAnnotation = (Property)annotation; 262 return ExpressionBuilder.propertyExpression(propertyAnnotation.name()); 263 } else if (annotation instanceof Header) { 264 Header headerAnnotation = (Header)annotation; 265 return ExpressionBuilder.headerExpression(headerAnnotation.name()); 266 } else if (annotation instanceof Body) { 267 Body content = (Body)annotation; 268 return ExpressionBuilder.bodyExpression(parameterType); 269 270 // TODO allow annotations to be used to create expressions? 271 /* 272 * } else if (annotation instanceof XPath) { XPath xpathAnnotation = 273 * (XPath) annotation; return new 274 * JAXPStringXPathExpression(xpathAnnotation.xpath()); } 275 */ 276 } 277 return null; 278 } 279 280 protected boolean isValidMethod(Class clazz, Method method) { 281 return Modifier.isPublic(method.getModifiers()); 282 } 283 }