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.AccessibleObject;
021 import java.lang.reflect.AnnotatedElement;
022 import java.lang.reflect.InvocationTargetException;
023 import java.lang.reflect.Method;
024 import java.util.ArrayList;
025 import java.util.Arrays;
026 import java.util.List;
027
028 import org.apache.camel.CamelContext;
029 import org.apache.camel.Exchange;
030 import org.apache.camel.ExchangePattern;
031 import org.apache.camel.Expression;
032 import org.apache.camel.NoTypeConversionAvailableException;
033 import org.apache.camel.Pattern;
034 import org.apache.camel.processor.RecipientList;
035 import org.apache.camel.util.ObjectHelper;
036 import org.apache.commons.logging.Log;
037 import org.apache.commons.logging.LogFactory;
038
039 import static org.apache.camel.util.ObjectHelper.asString;
040
041 /**
042 * Information about a method to be used for invocation.
043 *
044 * @version $Revision: 788297 $
045 */
046 public class MethodInfo {
047 private static final transient Log LOG = LogFactory.getLog(MethodInfo.class);
048
049 private CamelContext camelContext;
050 private Class type;
051 private Method method;
052 private final List<ParameterInfo> parameters;
053 private final List<ParameterInfo> bodyParameters;
054 private final boolean hasCustomAnnotation;
055 private final boolean hasHandlerAnnotation;
056 private Expression parametersExpression;
057 private ExchangePattern pattern = ExchangePattern.InOut;
058 private RecipientList recipientList;
059
060 public MethodInfo(CamelContext camelContext, Class type, Method method, List<ParameterInfo> parameters, List<ParameterInfo> bodyParameters,
061 boolean hasCustomAnnotation, boolean hasHandlerAnnotation) {
062 this.camelContext = camelContext;
063 this.type = type;
064 this.method = method;
065 this.parameters = parameters;
066 this.bodyParameters = bodyParameters;
067 this.hasCustomAnnotation = hasCustomAnnotation;
068 this.hasHandlerAnnotation = hasHandlerAnnotation;
069 this.parametersExpression = createParametersExpression();
070
071 Pattern oneway = findOneWayAnnotation(method);
072 if (oneway != null) {
073 pattern = oneway.value();
074 }
075
076 if (method.getAnnotation(org.apache.camel.RecipientList.class) != null
077 && matchContext(method.getAnnotation(org.apache.camel.RecipientList.class).context())) {
078 recipientList = new RecipientList();
079 }
080 }
081
082 /**
083 * Does the given context match this camel context
084 */
085 private boolean matchContext(String context) {
086 if (ObjectHelper.isNotEmpty(context)) {
087 if (!camelContext.getName().equals(context)) {
088 return false;
089 }
090 }
091 return true;
092 }
093
094
095
096 public String toString() {
097 return method.toString();
098 }
099
100 public MethodInvocation createMethodInvocation(final Object pojo, final Exchange exchange) {
101 final Object[] arguments = parametersExpression.evaluate(exchange, Object[].class);
102 return new MethodInvocation() {
103 public Method getMethod() {
104 return method;
105 }
106
107 public Object[] getArguments() {
108 return arguments;
109 }
110
111 public Object proceed() throws Exception {
112 if (LOG.isTraceEnabled()) {
113 LOG.trace(">>>> invoking: " + method + " on bean: " + pojo + " with arguments: " + asString(arguments) + " for exchange: " + exchange);
114 }
115 Object result = invoke(method, pojo, arguments, exchange);
116 if (recipientList != null) {
117 recipientList.sendToRecipientList(exchange, result);
118 }
119 return result;
120 }
121
122 public Object getThis() {
123 return pojo;
124 }
125
126 public AccessibleObject getStaticPart() {
127 return method;
128 }
129 };
130 }
131
132 public Class getType() {
133 return type;
134 }
135
136 public Method getMethod() {
137 return method;
138 }
139
140 /**
141 * Returns the {@link org.apache.camel.ExchangePattern} that should be used when invoking this method. This value
142 * defaults to {@link org.apache.camel.ExchangePattern#InOut} unless some {@link org.apache.camel.Pattern} annotation is used
143 * to override the message exchange pattern.
144 *
145 * @return the exchange pattern to use for invoking this method.
146 */
147 public ExchangePattern getPattern() {
148 return pattern;
149 }
150
151 public Expression getParametersExpression() {
152 return parametersExpression;
153 }
154
155 public List<ParameterInfo> getBodyParameters() {
156 return bodyParameters;
157 }
158
159 public Class getBodyParameterType() {
160 if (bodyParameters.isEmpty()) {
161 return null;
162 }
163 ParameterInfo parameterInfo = bodyParameters.get(0);
164 return parameterInfo.getType();
165 }
166
167 public boolean bodyParameterMatches(Class bodyType) {
168 Class actualType = getBodyParameterType();
169 return actualType != null && ObjectHelper.isAssignableFrom(bodyType, actualType);
170 }
171
172 public List<ParameterInfo> getParameters() {
173 return parameters;
174 }
175
176 public boolean hasBodyParameter() {
177 return !bodyParameters.isEmpty();
178 }
179
180 public boolean hasCustomAnnotation() {
181 return hasCustomAnnotation;
182 }
183
184 public boolean hasHandlerAnnotation() {
185 return hasHandlerAnnotation;
186 }
187
188 public boolean isReturnTypeVoid() {
189 return method.getReturnType().getName().equals("void");
190 }
191
192 protected Object invoke(Method mth, Object pojo, Object[] arguments, Exchange exchange) throws IllegalAccessException, InvocationTargetException {
193 return mth.invoke(pojo, arguments);
194 }
195
196 protected Expression createParametersExpression() {
197 final int size = parameters.size();
198 if (LOG.isTraceEnabled()) {
199 LOG.trace("Creating parameters expression for " + size + " parameters");
200 }
201
202 final Expression[] expressions = new Expression[size];
203 for (int i = 0; i < size; i++) {
204 Expression parameterExpression = parameters.get(i).getExpression();
205 expressions[i] = parameterExpression;
206 if (LOG.isTraceEnabled()) {
207 LOG.trace("Parameter #" + i + " has expression: " + parameterExpression);
208 }
209 }
210 return new Expression() {
211 @SuppressWarnings("unchecked")
212 public <T> T evaluate(Exchange exchange, Class<T> type) {
213 Object[] answer = new Object[size];
214 Object body = exchange.getIn().getBody();
215 boolean multiParameterArray = false;
216 if (exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY) != null) {
217 multiParameterArray = exchange.getIn().getHeader(Exchange.BEAN_MULTI_PARAMETER_ARRAY, Boolean.class);
218 }
219 for (int i = 0; i < size; i++) {
220 Object value = null;
221 if (multiParameterArray) {
222 value = ((Object[])body)[i];
223 } else {
224 Expression expression = expressions[i];
225 if (expression != null) {
226 // use object first to avoid type convertions so we know if there is a value or not
227 Object result = expression.evaluate(exchange, Object.class);
228 if (result != null) {
229 // we got a value now try to convert it to the expected type
230 value = exchange.getContext().getTypeConverter().convertTo(parameters.get(i).getType(), result);
231 if (LOG.isTraceEnabled()) {
232 LOG.trace("Parameter #" + i + " evaluated as: " + value + " type: " + ObjectHelper.type(value));
233 }
234 if (value == null) {
235 Exception e = new NoTypeConversionAvailableException(result, parameters.get(i).getType());
236 throw ObjectHelper.wrapRuntimeCamelException(e);
237 }
238 } else {
239 if (LOG.isTraceEnabled()) {
240 LOG.trace("Parameter #" + i + " evaluated as null");
241 }
242 }
243 }
244 }
245 // now lets try to coerce the value to the required type
246 answer[i] = value;
247 }
248 return (T) answer;
249 }
250
251 @Override
252 public String toString() {
253 return "ParametersExpression: " + Arrays.asList(expressions);
254 }
255
256 };
257 }
258
259 /**
260 * Finds the oneway annotation in priority order; look for method level annotations first, then the class level annotations,
261 * then super class annotations then interface annotations
262 *
263 * @param method the method on which to search
264 * @return the first matching annotation or none if it is not available
265 */
266 protected Pattern findOneWayAnnotation(Method method) {
267 Pattern answer = getPatternAnnotation(method);
268 if (answer == null) {
269 Class<?> type = method.getDeclaringClass();
270
271 // lets create the search order of types to scan
272 List<Class<?>> typesToSearch = new ArrayList<Class<?>>();
273 addTypeAndSuperTypes(type, typesToSearch);
274 Class[] interfaces = type.getInterfaces();
275 for (Class anInterface : interfaces) {
276 addTypeAndSuperTypes(anInterface, typesToSearch);
277 }
278
279 // now lets scan for a type which the current declared class overloads
280 answer = findOneWayAnnotationOnMethod(typesToSearch, method);
281 if (answer == null) {
282 answer = findOneWayAnnotation(typesToSearch);
283 }
284 }
285 return answer;
286 }
287
288 /**
289 * Returns the pattern annotation on the given annotated element; either as a direct annotation or
290 * on an annotation which is also annotated
291 *
292 * @param annotatedElement the element to look for the annotation
293 * @return the first matching annotation or null if none could be found
294 */
295 protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement) {
296 return getPatternAnnotation(annotatedElement, 2);
297 }
298
299 /**
300 * Returns the pattern annotation on the given annotated element; either as a direct annotation or
301 * on an annotation which is also annotated
302 *
303 * @param annotatedElement the element to look for the annotation
304 * @param depth the current depth
305 * @return the first matching annotation or null if none could be found
306 */
307 protected Pattern getPatternAnnotation(AnnotatedElement annotatedElement, int depth) {
308 Pattern answer = annotatedElement.getAnnotation(Pattern.class);
309 int nextDepth = depth - 1;
310
311 if (nextDepth > 0) {
312 // lets look at all the annotations to see if any of those are annotated
313 Annotation[] annotations = annotatedElement.getAnnotations();
314 for (Annotation annotation : annotations) {
315 Class<? extends Annotation> annotationType = annotation.annotationType();
316 if (annotation instanceof Pattern || annotationType.equals(annotatedElement)) {
317 continue;
318 } else {
319 Pattern another = getPatternAnnotation(annotationType, nextDepth);
320 if (pattern != null) {
321 if (answer == null) {
322 answer = another;
323 } else {
324 LOG.warn("Duplicate pattern annotation: " + another + " found on annotation: " + annotation + " which will be ignored");
325 }
326 }
327 }
328 }
329 }
330 return answer;
331 }
332
333 /**
334 * Adds the current class and all of its base classes (apart from {@link Object} to the given list
335 */
336 protected void addTypeAndSuperTypes(Class<?> type, List<Class<?>> result) {
337 for (Class<?> t = type; t != null && t != Object.class; t = t.getSuperclass()) {
338 result.add(t);
339 }
340 }
341
342 /**
343 * Finds the first annotation on the base methods defined in the list of classes
344 */
345 protected Pattern findOneWayAnnotationOnMethod(List<Class<?>> classes, Method method) {
346 for (Class<?> type : classes) {
347 try {
348 Method definedMethod = type.getMethod(method.getName(), method.getParameterTypes());
349 Pattern answer = getPatternAnnotation(definedMethod);
350 if (answer != null) {
351 return answer;
352 }
353 } catch (NoSuchMethodException e) {
354 // ignore
355 }
356 }
357 return null;
358 }
359
360
361 /**
362 * Finds the first annotation on the given list of classes
363 */
364 protected Pattern findOneWayAnnotation(List<Class<?>> classes) {
365 for (Class<?> type : classes) {
366 Pattern answer = getPatternAnnotation(type);
367 if (answer != null) {
368 return answer;
369 }
370 }
371 return null;
372 }
373
374 protected boolean hasExceptionParameter() {
375 for (ParameterInfo parameter : parameters) {
376 if (Exception.class.isAssignableFrom(parameter.getType())) {
377 return true;
378 }
379 }
380 return false;
381 }
382
383 }