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 }