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