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