From: Henri Sara Date: Tue, 24 Jan 2012 10:45:07 +0000 (+0200) Subject: Implement incoming RPC call invocation on the server side (#8278). X-Git-Tag: 7.0.0.alpha2~440^2~37 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a311d94ac921a42a83c67b123a21cff4d1220179;p=vaadin-framework.git Implement incoming RPC call invocation on the server side (#8278). --- diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 88c1c389d4..f5f735d184 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -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) { diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 9bc6813f12..6ca5600d1d 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -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 index 0000000000..a29755215f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/RpcManager.java @@ -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 index 0000000000..a5dbd7ce11 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/RpcTarget.java @@ -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 index 0000000000..0c8a6d1735 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java @@ -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 invocationMethodCache = new ConcurrentHashMap( + 128, 0.75f, 1); + + private static final Map, Class> boxedTypes = new HashMap, 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; + } + +} diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java index 4b9449c598..a5f2fa9601 100644 --- a/src/com/vaadin/ui/AbstractComponent.java +++ b/src/com/vaadin/ui/AbstractComponent.java @@ -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; + } + }