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 }