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    }