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