package org.jboss.seam.remoting.gwt; import static org.jboss.seam.ScopeType.APPLICATION; import static org.jboss.seam.annotations.Install.BUILT_IN; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.jboss.seam.Component; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.annotations.remoting.WebRemote; import org.jboss.seam.util.EJB; /** * This class adapts GWT RPC mechanism to Seam actions. * * @author Michael Neale */ @Scope(APPLICATION) @Name("org.jboss.seam.remoting.gwt.gwtToSeamAdapter") @BypassInterceptors @Install(precedence=BUILT_IN) public class GWTToSeamAdapter { /** A very simple cache of previously looked up methods */ private final Map METHOD_CACHE = new HashMap(); public static GWTToSeamAdapter instance() { GWTToSeamAdapter adapter = (GWTToSeamAdapter) Component.getInstance(GWTToSeamAdapter.class); if (adapter == null) { throw new IllegalStateException("No GWTToSeamAdapter exists"); } return adapter; } /** * Call the service. * * @param serviceIntfName * The interface name - this will be the fully qualified name of * the remote service interface as understood by GWT. This * correlates to a component name in seam. * @param methodName * The method name of the service being invoked. * @param paramTypes * The types of parameters - needed for method lookup for * polymorphism. * @param args * The values to be passed to the service method. * @return A populated ReturnedObject - the returned object payload may be * null, but the type will not be. * @throws InvocationTargetException * @throws IllegalAccessException */ public ReturnedObject callWebRemoteMethod(String serviceIntfName, String methodName, Class[] paramTypes, Object[] args) throws InvocationTargetException, IllegalAccessException, SecurityException { // Find the component we're calling Component component = Component.forName(serviceIntfName); if (component == null) throw new RuntimeException("No such component: " + serviceIntfName); Object instance = getServiceComponent(serviceIntfName); Class clz = null; if (component.getType().isSessionBean() && component.getBusinessInterfaces().size() > 0) { for (Class c : component.getBusinessInterfaces()) { if (c.isAnnotationPresent(EJB.LOCAL)) { clz = c; break; } } if (clz == null) throw new RuntimeException( String .format( "Type cannot be determined for component [%s]. Please ensure that it has a local interface.", component)); } if (clz == null) clz = component.getBeanClass(); Method method = getMethod(serviceIntfName, methodName, clz, paramTypes); Object result = method.invoke(instance, args); return new ReturnedObject(method.getReturnType(), result); } /** * Get the method on the class, including walking up the class heirarchy if * needed. Methods have to be marked as "@WebRemote" to be allowed. * * @param methodName * @param clz * @param paramTypes */ private Method getMethod(String serviceName, String methodName, Class clz, Class[] paramTypes) { String key = getKey(serviceName, methodName, paramTypes); if (METHOD_CACHE.containsKey(key)) { return (Method) METHOD_CACHE.get(key); } else { try { synchronized (METHOD_CACHE) { Method m = findMethod(clz, methodName, paramTypes); if (m == null) throw new NoSuchMethodException(); METHOD_CACHE.put(key, m); return m; } } catch (NoSuchMethodException e) { throw new SecurityException( "Unable to access a service method called [" + methodName + "] on class [" + clz.getName() + "] without the @WebRemote attribute. " + "This may be a hack attempt, or someone simply neglected to use the @WebRemote attribute to indicate a method as" + " remotely accessible."); } } } private String getKey(String serviceName, String methodName, Class[] paramTypes) { if (paramTypes == null) { return serviceName + "." + methodName; } else { String pTypes = ""; for (int i = 0; i < paramTypes.length; i++) { pTypes += paramTypes[i].getName(); } return serviceName + "." + methodName + "(" + pTypes + ")"; } } /** * Recurse up the class hierarchy, looking for a compatable method that is * marked as "@WebRemote". If one is not found (or we hit Object.class) then * we barf - basically trust nothing from the client other then what we want * to allow them to call. */ private Method findMethod(Class clz, String methodName, Class[] paramTypes) throws NoSuchMethodException { if (clz == Object.class) { return null; } else { Method m = clz.getMethod(methodName, paramTypes); if (isWebRemoteAnnotated(m)) { return m; } else { return findMethod(clz.getSuperclass(), methodName, paramTypes); } } } /** * Only allow methods annotated with * * @WebRemote for security reasons. */ private boolean isWebRemoteAnnotated(Method method) { if (method == null) return false; return method.getAnnotation(WebRemote.class) != null; } /** * Return the service component that has been bound to the given name. */ protected Object getServiceComponent(String serviceIntfName) { return Component.getInstance(serviceIntfName); } /** * This is used for returning results to the GWT service endpoint. The class * is needed even if the result is null. a void.class responseType is * perfectly acceptable. * * @author Michael Neale */ static class ReturnedObject { public ReturnedObject(Class type, Object result) { this.returnType = type; this.returnedObject = result; } public Class returnType; public Object returnedObject; } }