]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement incoming RPC call invocation on the server side (#8278).
authorHenri Sara <hesara@vaadin.com>
Tue, 24 Jan 2012 10:45:07 +0000 (12:45 +0200)
committerHenri Sara <hesara@vaadin.com>
Wed, 25 Jan 2012 11:49:14 +0000 (13:49 +0200)
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/RpcManager.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/RpcTarget.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/ServerRpcManager.java [new file with mode: 0644]
src/com/vaadin/ui/AbstractComponent.java

index 88c1c389d447e9a038bb06667130bcb0add8dfc1..f5f735d184def39b7a63a5b6b14e4b394d0edd53 100644 (file)
@@ -1112,7 +1112,20 @@ public class ApplicationConnection {
                 immediate);
     }
 
-    private void addMethodInvocationToQueue(MethodInvocation invocation,
+    /**
+     * Adds an explicit RPC method invocation to the send queue.
+     * 
+     * @since 7.0
+     * 
+     * @param invocation
+     *            RPC method invocation
+     * @param immediate
+     *            true to trigger sending within a short time window (possibly
+     *            combining subsequent calls to a single request), false to let
+     *            the framework delay sending of RPC calls and variable changes
+     *            until the next immediate change
+     */
+    public void addMethodInvocationToQueue(MethodInvocation invocation,
             boolean immediate) {
         pendingInvocations.add(invocation);
         if (immediate) {
index 9bc6813f1241853c354bbdf7eeceabf99b08776e..6ca5600d1df55fa1c156fb19b8fb6fd52e282b85 100644 (file)
@@ -1242,17 +1242,19 @@ public abstract class AbstractCommunicationManager implements
                     nextInvocation = invocations.get(i + 1);
                 }
 
+                final String methodName = invocation.getMethodName();
+
+                if (!ApplicationConnection.UPDATE_VARIABLE_METHOD
+                        .equals(methodName)) {
+                    // handle other RPC calls than variable changes
+                    applyInvocation(invocation);
+                    continue;
+                }
+
                 final VariableOwner owner = getVariableOwner(invocation
                         .getPaintableId());
-                final String methodName = invocation.getMethodName();
 
                 if (owner != null && owner.isEnabled()) {
-                    if (!ApplicationConnection.UPDATE_VARIABLE_METHOD
-                            .equals(methodName)) {
-                        // TODO handle other RPC calls
-                        continue;
-                    }
-
                     VariableChange change = new VariableChange(invocation);
 
                     // TODO could optimize with a single value map if only one
@@ -1288,8 +1290,8 @@ public abstract class AbstractCommunicationManager implements
                         }
                     }
                 } else {
-                    // TODO convert window close to a separate RPC call, not a
-                    // variable change
+                    // TODO convert window close to a separate RPC call and
+                    // handle above - not a variable change
 
                     VariableChange change = new VariableChange(invocation);
 
@@ -1331,6 +1333,23 @@ public abstract class AbstractCommunicationManager implements
         return success;
     }
 
+    /**
+     * Execute an RPC call from the client by finding its target and letting the
+     * RPC mechanism call the correct method for it.
+     * 
+     * @param invocation
+     */
+    protected void applyInvocation(MethodInvocation invocation) {
+        final RpcTarget target = getRpcTarget(invocation.getPaintableId());
+        if (null != target) {
+            ServerRpcManager.applyInvocation(target, invocation);
+        } else {
+            // TODO better exception?
+            throw new RuntimeException("No RPC target for paintable "
+                    + invocation.getPaintableId());
+        }
+    }
+
     /**
      * Parse a message burst from the client into a list of MethodInvocation
      * instances.
@@ -1377,6 +1396,25 @@ public abstract class AbstractCommunicationManager implements
         return owner;
     }
 
+    /**
+     * Returns the RPC call target for a paintable ID.
+     * 
+     * @since 7.0
+     * 
+     * @param string
+     *            paintable ID
+     * @return RPC call target or null if none found
+     */
+    protected RpcTarget getRpcTarget(String string) {
+        // TODO improve this - VariableOwner and RpcManager separate?
+        VariableOwner owner = getVariableOwner(string);
+        if (owner instanceof RpcTarget) {
+            return (RpcTarget) owner;
+        } else {
+            return null;
+        }
+    }
+
     private VariableOwner getDragAndDropService() {
         if (dragAndDropService == null) {
             dragAndDropService = new DragAndDropService(this);
diff --git a/src/com/vaadin/terminal/gwt/server/RpcManager.java b/src/com/vaadin/terminal/gwt/server/RpcManager.java
new file mode 100644 (file)
index 0000000..a297552
--- /dev/null
@@ -0,0 +1,13 @@
+package com.vaadin.terminal.gwt.server;
+
+import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
+
+/**
+ * Server side RPC manager that can invoke methods based on RPC calls received
+ * from the client.
+ * 
+ * @since 7.0
+ */
+public interface RpcManager {
+    public void applyInvocation(MethodInvocation invocation);
+}
diff --git a/src/com/vaadin/terminal/gwt/server/RpcTarget.java b/src/com/vaadin/terminal/gwt/server/RpcTarget.java
new file mode 100644 (file)
index 0000000..a5dbd7c
--- /dev/null
@@ -0,0 +1,14 @@
+package com.vaadin.terminal.gwt.server;
+
+import com.vaadin.terminal.VariableOwner;
+
+/**
+ * Marker interface for server side classes that can receive RPC calls.
+ * 
+ * This plays a role similar to that of {@link VariableOwner}.
+ * 
+ * @since 7.0
+ */
+public interface RpcTarget {
+    public RpcManager getRpcManager();
+}
diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java
new file mode 100644 (file)
index 0000000..0c8a6d1
--- /dev/null
@@ -0,0 +1,167 @@
+package com.vaadin.terminal.gwt.server;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.vaadin.terminal.Paintable;
+import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
+
+/**
+ * Server side RPC manager that handles RPC calls coming from the client.
+ * 
+ * Each {@link RpcTarget} (typically a {@link Paintable}) should have its own
+ * instance of {@link ServerRpcManager} if it wants to receive RPC calls from
+ * the client.
+ * 
+ * @since 7.0
+ */
+public class ServerRpcManager implements RpcManager {
+
+    private final RpcTarget target;
+    private final Object implementation;
+
+    private static final Map<String, Method> invocationMethodCache = new ConcurrentHashMap<String, Method>(
+            128, 0.75f, 1);
+
+    private static final Map<Class<?>, Class<?>> boxedTypes = new HashMap<Class<?>, Class<?>>();
+    static {
+        try {
+            Class<?>[] boxClasses = new Class<?>[] { Boolean.class, Byte.class,
+                    Short.class, Character.class, Integer.class, Long.class,
+                    Float.class, Double.class };
+            for (Class<?> boxClass : boxClasses) {
+                Field typeField = boxClass.getField("TYPE");
+                Class<?> primitiveType = (Class<?>) typeField.get(boxClass);
+                boxedTypes.put(primitiveType, boxClass);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Create a RPC manager for an RPC target.
+     * 
+     * @param target
+     *            RPC call target (normally a {@link Paintable})
+     * @param implementation
+     *            RPC interface implementation for the target
+     */
+    public ServerRpcManager(RpcTarget target, Object implementation) {
+        this.target = target;
+        this.implementation = implementation;
+    }
+
+    /**
+     * Invoke a method in a server side RPC target class. This method is to be
+     * used by the RPC framework and unit testing tools only.
+     * 
+     * @param target
+     *            non-null target of the RPC call
+     * @param invocation
+     *            method invocation to perform
+     */
+    public static void applyInvocation(RpcTarget target,
+            MethodInvocation invocation) {
+        RpcManager manager = target.getRpcManager();
+        if (manager != null) {
+            manager.applyInvocation(invocation);
+        } else {
+            throw new RuntimeException(
+                    "RPC call to a target without an RPC manager.");
+        }
+    }
+
+    /**
+     * Returns the RPC target of this RPC manager instance.
+     * 
+     * @return RpcTarget, typically a {@link Paintable}
+     */
+    public RpcTarget getTarget() {
+        return target;
+    }
+
+    /**
+     * Returns the RPC interface implementation for the RPC target.
+     * 
+     * @return RPC interface implementation
+     */
+    protected Object getImplementation() {
+        return implementation;
+    }
+
+    /**
+     * Invoke a method in a server side RPC target class. This method is to be
+     * used by the RPC framework and unit testing tools only.
+     * 
+     * @param invocation
+     *            method invocation to perform
+     */
+    public void applyInvocation(MethodInvocation invocation) {
+        String methodName = invocation.getMethodName();
+        Object[] arguments = invocation.getParameters();
+
+        Method method = findInvocationMethod(implementation.getClass(),
+                methodName, arguments.length);
+        if (method == null) {
+            throw new RuntimeException(implementation + " does not contain "
+                    + methodName + " with " + arguments.length + " parameters");
+        }
+
+        Class<?>[] parameterTypes = method.getParameterTypes();
+        Object[] args = new Object[parameterTypes.length];
+        for (int i = 0; i < args.length; i++) {
+            // no conversion needed for basic cases
+            // Class<?> type = parameterTypes[i];
+            // if (type.isPrimitive()) {
+            // type = boxedTypes.get(type);
+            // }
+            args[i] = arguments[i];
+        }
+        try {
+            method.invoke(implementation, args);
+        } catch (Exception e) {
+            throw new RuntimeException(methodName, e);
+        }
+    }
+
+    private static Method findInvocationMethod(Class<?> targetType,
+            String methodName, int parameterCount) {
+        // TODO currently only using method name and number of parameters as the
+        // signature
+        String signature = targetType.getName() + "." + methodName + "("
+                + parameterCount;
+        Method invocationMethod = invocationMethodCache.get(signature);
+
+        if (invocationMethod == null) {
+            invocationMethod = doFindInvocationMethod(targetType, methodName,
+                    parameterCount);
+
+            if (invocationMethod != null) {
+                invocationMethodCache.put(signature, invocationMethod);
+            }
+        }
+
+        return invocationMethod;
+    }
+
+    private static Method doFindInvocationMethod(Class<?> targetType,
+            String methodName, int parameterCount) {
+        Class<?>[] interfaces = targetType.getInterfaces();
+        for (Class<?> iface : interfaces) {
+            Method[] methods = iface.getMethods();
+            for (Method method : methods) {
+                Class<?>[] parameterTypes = method.getParameterTypes();
+                if (method.getName().equals(methodName)
+                        && parameterTypes.length == parameterCount) {
+                    return method;
+                }
+            }
+        }
+        return null;
+    }
+
+}
index 4b9449c598bf7c72baa9b0b0eda7bbe6801767fc..a5f2fa9601df52999354af36e11d4113113e70d4 100644 (file)
@@ -29,6 +29,9 @@ import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.Terminal;
 import com.vaadin.terminal.gwt.server.ComponentSizeValidator;
+import com.vaadin.terminal.gwt.server.RpcManager;
+import com.vaadin.terminal.gwt.server.RpcTarget;
+import com.vaadin.terminal.gwt.server.ServerRpcManager;
 import com.vaadin.tools.ReflectTools;
 
 /**
@@ -153,6 +156,11 @@ public abstract class AbstractComponent implements Component, MethodEventSource
      */
     private ActionManager actionManager;
 
+    /**
+     * RPC call manager that handles incoming RPC calls.
+     */
+    private RpcManager rpcManager = null;
+
     /* Constructor */
 
     /**
@@ -1529,4 +1537,28 @@ public abstract class AbstractComponent implements Component, MethodEventSource
         }
     }
 
+    /**
+     * Sets the RPC interface implementation for this component.
+     * 
+     * A component should have exactly one RPC interface and its implementation
+     * to be able to receive RPC calls.
+     * 
+     * @since 7.0
+     * 
+     * @param implementation
+     *            RPC interface implementation
+     */
+    protected void setRpcImplementation(Object implementation) {
+        if (this instanceof RpcTarget) {
+            rpcManager = new ServerRpcManager((RpcTarget) this, implementation);
+        } else {
+            throw new RuntimeException(
+                    "Cannot register an RPC implementation for a component that is not an RpcTarget");
+        }
+    }
+
+    public RpcManager getRpcManager() {
+        return rpcManager;
+    }
+
 }