package org.jboss.seam.remoting; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.jboss.seam.Component; import org.jboss.seam.annotations.remoting.WebRemote; import org.jboss.seam.remoting.wrapper.ConversionException; import org.jboss.seam.remoting.wrapper.ConversionScore; import org.jboss.seam.remoting.wrapper.Wrapper; import org.jboss.seam.util.EJB; /** * * @author Shane Bryzak */ public class Call { private String id; private String componentName; private String methodName; // private String expression; private Throwable exception; private List<Wrapper> params = new ArrayList<Wrapper>(); private Object result; private CallContext context; private List<String> constraints = null; /** * Constructor. * * @param componentName * String * @param methodName * String */ public Call(String id, String componentName, String methodName) { this.id = id; this.componentName = componentName; this.methodName = methodName; this.context = new CallContext(); } /** * Return the call context. * * @return CallContext */ public CallContext getContext() { return context; } /** * Returns the exception thrown by the invoked method. If no exception was thrown, * will return null. */ public Throwable getException() { return exception; } /** * Add a parameter to this call. * * @param param */ public void addParameter(Wrapper param) { params.add(param); } /** * Returns the result of this call. * * @return Wrapper */ public Object getResult() { return result; } /** * Required for unit tests * * @param result */ public void setResult(Object result) { this.result = result; } /** * Returns the id of this call. * * @return String */ public String getId() { return id; } /** * Returns the object graph constraints annotated on the method that is * called. * * @return List The constraints */ public List<String> getConstraints() { return constraints; } /** * Required for unit tests * * @param constraints */ public void setConstraints(List<String> constraints) { this.constraints = constraints; } /** * Execute this call * * @throws Exception */ public void execute() throws Exception { if (componentName != null) { processInvocation(); } } private void processInvocation() throws Exception { // Find the component we're calling Component component = Component.forName(componentName); if (component == null) { throw new RuntimeException("No such component: " + componentName); } // Create an instance of the component Object instance = Component.getInstance(componentName, true); if (instance == null) { throw new RuntimeException(String.format( "Could not create instance of component %s", componentName)); } Class type = null; if (component.getType().isSessionBean() && component.getBusinessInterfaces().size() > 0) { for (Class c : component.getBusinessInterfaces()) { if (c.isAnnotationPresent(EJB.LOCAL)) { type = c; break; } } if (type == null) { throw new RuntimeException(String.format( "Type cannot be determined for component [%s]. Please ensure that it has a local interface.", component)); } } if (type == null) { type = component.getBeanClass(); } // Find the method according to the method name and the parameter classes Method m = findMethod(methodName, type); if (m == null) throw new RuntimeException("No compatible method found."); if (m.getAnnotation(WebRemote.class).exclude().length > 0) constraints = Arrays .asList(m.getAnnotation(WebRemote.class).exclude()); Object[] params = convertParams(m.getGenericParameterTypes()); // Invoke! try { result = m.invoke(instance, params); } catch (InvocationTargetException e) { this.exception = e.getCause(); } } /** * Convert our parameter values to an Object array of the specified target * types. * * @param targetTypes * Class[] An array containing the target class types. * @return Object[] The converted parameter values. */ private Object[] convertParams(Type[] targetTypes) throws ConversionException { Object[] paramValues = new Object[targetTypes.length]; for (int i = 0; i < targetTypes.length; i++) { paramValues[i] = params.get(i).convert(targetTypes[i]); } return paramValues; } /** * Find the best matching method within the specified class according to the * parameter types that were provided to the Call. * * @param name * String The name of the method. * @param cls * Class The Class to search in. * @return Method The best matching method. */ private Method findMethod(String name, Class cls) { Map<Method, Integer> candidates = new HashMap<Method, Integer>(); for (Method m : cls.getDeclaredMethods()) { if (m.getAnnotation(WebRemote.class) == null) continue; if (name.equals(m.getName()) && m.getParameterTypes().length == params.size()) { int score = 0; for (int i = 0; i < m.getParameterTypes().length; i++) { ConversionScore convScore = params.get(i).conversionScore( m.getParameterTypes()[i]); if (convScore == ConversionScore.nomatch) continue; score += convScore.getScore(); } candidates.put(m, score); } } Method bestMethod = null; int bestScore = 0; for (Entry<Method,Integer> entry : candidates.entrySet()) { int thisScore = entry.getValue(); if (bestMethod == null || thisScore > bestScore) { bestMethod = entry.getKey(); bestScore = thisScore; } } return bestMethod; } }