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