private MethodInfo chooseMethodWithMatchingParameters( Exchange exchange, String parameters, Collection<MethodInfo> operationList) throws AmbiguousMethodCallException { // we have hardcoded parameters so need to match that with the given operations Iterator<?> it = ObjectHelper.createIterator(parameters); int count = 0; while (it.hasNext()) { it.next(); count++; } List<MethodInfo> operations = new ArrayList<MethodInfo>(); for (MethodInfo info : operationList) { if (info.getParameters().size() == count) { operations.add(info); } } if (operations.isEmpty()) { return null; } else if (operations.size() == 1) { return operations.get(0); } // okay we still got multiple operations, so need to match the best one List<MethodInfo> candidates = new ArrayList<MethodInfo>(); for (MethodInfo info : operations) { it = ObjectHelper.createIterator(parameters); int index = 0; boolean matches = true; while (it.hasNext()) { String parameter = (String) it.next(); Class<?> parameterType = BeanHelper.getValidParameterType(parameter); Class<?> expectedType = info.getParameters().get(index).getType(); if (parameterType != null && expectedType != null) { if (!parameterType.isAssignableFrom(expectedType)) { matches = false; break; } } index++; } if (matches) { candidates.add(info); } } if (candidates.size() > 1) { MethodInfo answer = getSingleCovariantMethod(candidates); if (answer == null) { throw new AmbiguousMethodCallException(exchange, candidates); } return answer; } return candidates.size() == 1 ? candidates.get(0) : null; }
/** * Returns true if this method is covariant with the specified method (this method may above or * below the specified method in the class hierarchy) */ public boolean isCovariantWith(MethodInfo method) { return method.getMethod().getName().equals(this.getMethod().getName()) && (method.getMethod().getReturnType().isAssignableFrom(this.getMethod().getReturnType()) || this.getMethod() .getReturnType() .isAssignableFrom(method.getMethod().getReturnType())) && Arrays.deepEquals( method.getMethod().getParameterTypes(), this.getMethod().getParameterTypes()); }
private MethodInfo getSingleCovariantMethod(Collection<MethodInfo> candidates) { // if all the candidates are actually covariant, it doesn't matter which one we call MethodInfo firstCandidate = candidates.iterator().next(); for (MethodInfo candidate : candidates) { if (!firstCandidate.isCovariantWith(candidate)) { return null; } } return firstCandidate; }
private void removeNonMatchingMethods(List<MethodInfo> methods, String name) { Iterator<MethodInfo> it = methods.iterator(); while (it.hasNext()) { MethodInfo info = it.next(); if (!matchMethod(info.getMethod(), name)) { // method does not match so remove it it.remove(); } } }
/** * Do we have a static method with the given name. * * <p>Shorthand method names for getters is supported, so you can pass in eg 'name' and Camel will * can find the real 'getName' method instead. * * @param methodName the method name * @return <tt>true</tt> if we have such a static method. */ public boolean hasStaticMethod(String methodName) { List<MethodInfo> methods = getOperations(methodName); if (methods == null || methods.isEmpty()) { return false; } for (MethodInfo method : methods) { if (method.isStaticMethod()) { return true; } } return false; }
private MethodInfo chooseMethodWithMatchingBody( Exchange exchange, Collection<MethodInfo> operationList, List<MethodInfo> operationsWithCustomAnnotation) throws AmbiguousMethodCallException { // see if we can find a method whose body param type matches the message body Message in = exchange.getIn(); Object body = in.getBody(); if (body != null) { Class<?> bodyType = body.getClass(); if (LOG.isTraceEnabled()) { LOG.trace( "Matching for method with a single parameter that matches type: {}", bodyType.getCanonicalName()); } List<MethodInfo> possibles = new ArrayList<MethodInfo>(); List<MethodInfo> possiblesWithException = new ArrayList<MethodInfo>(); for (MethodInfo methodInfo : operationList) { // test for MEP pattern matching boolean out = exchange.getPattern().isOutCapable(); if (out && methodInfo.isReturnTypeVoid()) { // skip this method as the MEP is Out so the method must return something continue; } // try to match the arguments if (methodInfo.bodyParameterMatches(bodyType)) { LOG.trace("Found a possible method: {}", methodInfo); if (methodInfo.hasExceptionParameter()) { // methods with accepts exceptions possiblesWithException.add(methodInfo); } else { // regular methods with no exceptions possibles.add(methodInfo); } } } // find best suited method to use return chooseBestPossibleMethodInfo( exchange, operationList, body, possibles, possiblesWithException, operationsWithCustomAnnotation); } // no match so return null return null; }
private void removeAllAbstractMethods(List<MethodInfo> methods) { Iterator<MethodInfo> it = methods.iterator(); while (it.hasNext()) { MethodInfo info = it.next(); // if the class is an interface then keep the method boolean isFromInterface = Modifier.isInterface(info.getMethod().getDeclaringClass().getModifiers()); if (!isFromInterface && Modifier.isAbstract(info.getMethod().getModifiers())) { // we cannot invoke an abstract method it.remove(); } } }
private static void removeAllSetterOrGetterMethods(List<MethodInfo> methods) { Iterator<MethodInfo> it = methods.iterator(); while (it.hasNext()) { MethodInfo info = it.next(); if (IntrospectionSupport.isGetter(info.getMethod())) { // skip getters it.remove(); } else if (IntrospectionSupport.isSetter(info.getMethod())) { // skip setters it.remove(); } } }
/** * Does the given method info override an existing method registered before (from a subclass) * * @param methodInfo the method to test * @return the already registered method to use, null if not overriding any */ private MethodInfo overridesExistingMethod(MethodInfo methodInfo) { for (MethodInfo info : methodMap.values()) { Method source = info.getMethod(); Method target = methodInfo.getMethod(); boolean override = ObjectHelper.isOverridingMethod(source, target); if (override) { // same name, same parameters, then its overrides an existing class return info; } } return null; }
/** * Introspects the given method * * @param clazz the class * @param method the method * @return the method info, is newer <tt>null</tt> */ private MethodInfo introspect(Class<?> clazz, Method method) { LOG.trace("Introspecting class: {}, method: {}", clazz, method); String opName = method.getName(); MethodInfo methodInfo = createMethodInfo(clazz, method); // methods already registered should be preferred to use instead of super classes of existing // methods // we want to us the method from the sub class over super classes, so if we have already // registered // the method then use it (we are traversing upwards: sub (child) -> super (farther) ) MethodInfo existingMethodInfo = overridesExistingMethod(methodInfo); if (existingMethodInfo != null) { LOG.trace( "This method is already overridden in a subclass, so the method from the sub class is preferred: {}", existingMethodInfo); return existingMethodInfo; } LOG.trace("Adding operation: {} for method: {}", opName, methodInfo); List<MethodInfo> existing = getOperations(opName); if (existing != null) { // we have an overloaded method so add the method info to the same key existing.add(methodInfo); } else { // its a new method we have not seen before so wrap it in a list and add it List<MethodInfo> methods = new ArrayList<MethodInfo>(); methods.add(methodInfo); operations.put(opName, methods); } if (methodInfo.hasCustomAnnotation()) { operationsWithCustomAnnotation.add(methodInfo); } else if (methodInfo.hasBodyParameter()) { operationsWithBody.add(methodInfo); } else { operationsWithNoBody.add(methodInfo); } if (methodInfo.hasHandlerAnnotation()) { operationsWithHandlerAnnotation.add(methodInfo); } // must add to method map last otherwise we break stuff methodMap.put(method, methodInfo); return methodInfo; }
private MethodInfo chooseMethodWithCustomAnnotations( Exchange exchange, Collection<MethodInfo> possibles) throws AmbiguousMethodCallException { // if we have only one method with custom annotations let's choose that MethodInfo chosen = null; for (MethodInfo possible : possibles) { if (possible.hasCustomAnnotation()) { if (chosen != null) { chosen = null; break; } else { chosen = possible; } } } return chosen; }
private MethodInfo chooseBestPossibleMethodInfo( Exchange exchange, Collection<MethodInfo> operationList, Object body, List<MethodInfo> possibles, List<MethodInfo> possiblesWithException, List<MethodInfo> possibleWithCustomAnnotation) throws AmbiguousMethodCallException { Exception exception = ExpressionBuilder.exchangeExceptionExpression().evaluate(exchange, Exception.class); if (exception != null && possiblesWithException.size() == 1) { LOG.trace( "Exchange has exception set so we prefer method that also has exception as parameter"); // prefer the method that accepts exception in case we have an exception also return possiblesWithException.get(0); } else if (possibles.size() == 1) { return possibles.get(0); } else if (possibles.isEmpty()) { LOG.trace("No possible methods so now trying to convert body to parameter types"); // let's try converting Object newBody = null; MethodInfo matched = null; int matchCounter = 0; for (MethodInfo methodInfo : operationList) { if (methodInfo.getBodyParameterType() != null) { if (methodInfo.getBodyParameterType().isInstance(body)) { return methodInfo; } // we should only try to convert, as we are looking for best match Object value = exchange .getContext() .getTypeConverter() .tryConvertTo(methodInfo.getBodyParameterType(), exchange, body); if (value != null) { if (LOG.isTraceEnabled()) { LOG.trace( "Converted body from: {} to: {}", body.getClass().getCanonicalName(), methodInfo.getBodyParameterType().getCanonicalName()); } matchCounter++; newBody = value; matched = methodInfo; } } } if (matchCounter > 1) { throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched, matched)); } if (matched != null) { LOG.trace("Setting converted body: {}", body); Message in = exchange.getIn(); in.setBody(newBody); return matched; } } else { // if we only have a single method with custom annotations, let's use that one if (possibleWithCustomAnnotation.size() == 1) { MethodInfo answer = possibleWithCustomAnnotation.get(0); LOG.trace("There are only one method with annotations so we choose it: {}", answer); return answer; } // try to choose among multiple methods with annotations MethodInfo chosen = chooseMethodWithCustomAnnotations(exchange, possibles); if (chosen != null) { return chosen; } // just make sure the methods aren't all actually the same chosen = getSingleCovariantMethod(possibles); if (chosen != null) { return chosen; } throw new AmbiguousMethodCallException(exchange, possibles); } // cannot find a good method to use return null; }
private MethodInfo chooseMethodWithMatchingParameters( Exchange exchange, String parameters, Collection<MethodInfo> operationList) throws AmbiguousMethodCallException { // we have hardcoded parameters so need to match that with the given operations Iterator<?> it = ObjectHelper.createIterator(parameters); int count = 0; while (it.hasNext()) { it.next(); count++; } List<MethodInfo> operations = new ArrayList<MethodInfo>(); for (MethodInfo info : operationList) { if (info.getParameters().size() == count) { operations.add(info); } } if (operations.isEmpty()) { return null; } else if (operations.size() == 1) { return operations.get(0); } // okay we still got multiple operations, so need to match the best one List<MethodInfo> candidates = new ArrayList<MethodInfo>(); MethodInfo fallbackCandidate = null; for (MethodInfo info : operations) { it = ObjectHelper.createIterator(parameters, ",", false); int index = 0; boolean matches = true; while (it.hasNext()) { String parameter = (String) it.next(); if (parameter != null) { // must trim parameter = parameter.trim(); } Class<?> parameterType = BeanHelper.getValidParameterType(parameter); Class<?> expectedType = info.getParameters().get(index).getType(); if (parameterType != null && expectedType != null) { // if its a simple language then we need to evaluate the expression // so we have the result and can find out what type the parameter actually is if (StringHelper.hasStartToken(parameter, "simple")) { LOG.trace( "Evaluating simple expression for parameter #{}: {} to determine the class type of the parameter", index, parameter); Object out = getCamelContext() .resolveLanguage("simple") .createExpression(parameter) .evaluate(exchange, Object.class); if (out != null) { parameterType = out.getClass(); } } // skip java.lang.Object type, when we have multiple possible methods we want to avoid it // if possible if (Object.class.equals(expectedType)) { fallbackCandidate = info; matches = false; break; } boolean matchingTypes = isParameterMatchingType(parameterType, expectedType); if (!matchingTypes) { matches = false; break; } } index++; } if (matches) { candidates.add(info); } } if (candidates.size() > 1) { MethodInfo answer = getSingleCovariantMethod(candidates); if (answer != null) { return answer; } } return candidates.size() == 1 ? candidates.get(0) : fallbackCandidate; }
private MethodInvocation createInvocation(Object pojo, Exchange exchange, Method explicitMethod) throws AmbiguousMethodCallException, MethodNotFoundException { MethodInfo methodInfo = null; // find the explicit method to invoke if (explicitMethod != null) { for (List<MethodInfo> infos : operations.values()) { for (MethodInfo info : infos) { if (explicitMethod.equals(info.getMethod())) { return info.createMethodInvocation(pojo, exchange); } } } throw new MethodNotFoundException(exchange, pojo, explicitMethod.getName()); } String methodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, String.class); if (methodName != null) { // do not use qualifier for name String name = methodName; if (methodName.contains("(")) { name = ObjectHelper.before(methodName, "("); // the must be a ending parenthesis if (!methodName.endsWith(")")) { throw new IllegalArgumentException( "Method should end with parenthesis, was " + methodName); } } boolean emptyParameters = methodName.endsWith("()"); // special for getClass, as we want the user to be able to invoke this method // for example to log the class type or the likes if ("class".equals(name) || "getClass".equals(name)) { try { Method method = pojo.getClass().getMethod("getClass"); methodInfo = new MethodInfo( exchange.getContext(), pojo.getClass(), method, Collections.<ParameterInfo>emptyList(), Collections.<ParameterInfo>emptyList(), false, false); } catch (NoSuchMethodException e) { throw new MethodNotFoundException(exchange, pojo, "getClass"); } // special for length on an array type } else if ("length".equals(name) && pojo.getClass().isArray()) { try { // need to use arrayLength method from ObjectHelper as Camel's bean OGNL support is method // invocation based // and not for accessing fields. And hence we need to create a MethodInfo instance with a // method to call // and therefore use arrayLength from ObjectHelper to return the array length field. Method method = ObjectHelper.class.getMethod("arrayLength", Object[].class); ParameterInfo pi = new ParameterInfo( 0, Object[].class, null, ExpressionBuilder.mandatoryBodyExpression(Object[].class, true)); List<ParameterInfo> lpi = new ArrayList<ParameterInfo>(1); lpi.add(pi); methodInfo = new MethodInfo( exchange.getContext(), pojo.getClass(), method, lpi, lpi, false, false); // Need to update the message body to be pojo for the invocation exchange.getIn().setBody(pojo); } catch (NoSuchMethodException e) { throw new MethodNotFoundException(exchange, pojo, "getClass"); } } else { List<MethodInfo> methods = getOperations(name); if (methods != null && methods.size() == 1) { // only one method then choose it methodInfo = methods.get(0); // validate that if we want an explicit no-arg method, then that's what we get if (emptyParameters && methodInfo.hasParameters()) { throw new MethodNotFoundException(exchange, pojo, methodName, "(with no parameters)"); } } else if (methods != null) { // there are more methods with that name so we cannot decide which to use // but first let's try to choose a method and see if that complies with the name // must use the method name which may have qualifiers methodInfo = chooseMethod(pojo, exchange, methodName); // validate that if we want an explicit no-arg method, then that's what we get if (emptyParameters) { if (methodInfo == null || methodInfo.hasParameters()) { // we could not find a no-arg method with that name throw new MethodNotFoundException(exchange, pojo, methodName, "(with no parameters)"); } } if (methodInfo == null || (name != null && !name.equals(methodInfo.getMethod().getName()))) { throw new AmbiguousMethodCallException(exchange, methods); } } else { // a specific method was given to invoke but not found throw new MethodNotFoundException(exchange, pojo, methodName); } } } if (methodInfo == null) { // no name or type methodInfo = chooseMethod(pojo, exchange, null); } if (methodInfo == null) { methodInfo = defaultMethod; } if (methodInfo != null) { LOG.trace("Chosen method to invoke: {} on bean: {}", methodInfo, pojo); return methodInfo.createMethodInvocation(pojo, exchange); } LOG.debug("Cannot find suitable method to invoke on bean: {}", pojo); return null; }