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