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