]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement server to client RPC (#8426).
authorHenri Sara <hesara@vaadin.com>
Wed, 29 Feb 2012 15:16:00 +0000 (17:16 +0200)
committerHenri Sara <hesara@vaadin.com>
Wed, 29 Feb 2012 15:16:18 +0000 (17:16 +0200)
22 files changed:
src/com/vaadin/data/Buffered.java
src/com/vaadin/data/Validator.java
src/com/vaadin/terminal/CompositeErrorMessage.java
src/com/vaadin/terminal/Paintable.java
src/com/vaadin/terminal/SystemError.java
src/com/vaadin/terminal/UserError.java
src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
src/com/vaadin/terminal/gwt/client/Connector.java
src/com/vaadin/terminal/gwt/client/Util.java
src/com/vaadin/terminal/gwt/client/communication/ClientRpc.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java
src/com/vaadin/terminal/gwt/client/communication/RpcManager.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/communication/ServerRpc.java
src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java
src/com/vaadin/terminal/gwt/client/ui/MediaBaseConnector.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java
src/com/vaadin/ui/AbstractComponent.java
src/com/vaadin/ui/AbstractMedia.java

index f6398f585b82a7ee902626db7a0621ede40024a8..552bd16d04c4458ea1a31401c1b132ab4dfe4da4 100644 (file)
@@ -5,6 +5,8 @@
 package com.vaadin.data;
 
 import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
 
 import com.vaadin.data.Validator.InvalidValueException;
 import com.vaadin.terminal.ErrorMessage;
@@ -12,6 +14,7 @@ import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.SystemError;
 import com.vaadin.terminal.gwt.client.communication.SharedState;
+import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
 
 /**
  * <p>
@@ -338,6 +341,10 @@ public interface Buffered extends Serializable {
             return null;
         }
 
+        public List<ClientMethodInvocation> retrievePendingRpcCalls() {
+            return Collections.emptyList();
+        }
+
         /* Documented in super interface */
         public void addListener(RepaintRequestListener listener) {
         }
index bc64594edb900ae3a26ea06af385e9bea9c9f7c6..f07e957395f2ddacc2f42b91a4b8048d84fe129b 100644 (file)
@@ -5,12 +5,15 @@
 package com.vaadin.data;
 
 import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
 
 import com.vaadin.terminal.ErrorMessage;
 import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
+import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
 
 /**
  * Interface that implements a method for validating if an {@link Object} is
@@ -173,6 +176,10 @@ public interface Validator extends Serializable {
             return null;
         }
 
+        public List<ClientMethodInvocation> retrievePendingRpcCalls() {
+            return Collections.emptyList();
+        }
+
         /**
          * Returns the message of the error in HTML.
          * 
index 2ec1fc949e0db544f5611f65338834896888cadc..efe29196d86a385f400c34e12068c8af2e50cf39 100644 (file)
@@ -7,10 +7,12 @@ package com.vaadin.terminal;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
 import com.vaadin.terminal.gwt.client.communication.SharedState;
+import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
 
 /**
  * Class for combining multiple error messages together.
@@ -138,6 +140,10 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable {
         return null;
     }
 
+    public List<ClientMethodInvocation> retrievePendingRpcCalls() {
+        return Collections.emptyList();
+    }
+
     /* Documented in super interface */
     public void addListener(RepaintRequestListener listener) {
     }
index 9026a45ca3e5a12fab786ab02517944c61170c9c..435b00a0e4bf7e5c8a95bebab9195c9ee99b3567 100644 (file)
@@ -6,8 +6,10 @@ package com.vaadin.terminal;
 
 import java.io.Serializable;
 import java.util.EventObject;
+import java.util.List;
 
 import com.vaadin.terminal.gwt.client.communication.SharedState;
+import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
 
 /**
  * Interface implemented by all classes that can be painted. Classes
@@ -54,6 +56,17 @@ public interface Paintable extends java.util.EventListener, Serializable {
      */
     public SharedState getState();
 
+    /**
+     * Returns the list of pending server to client RPC calls and clears the
+     * list.
+     * 
+     * @return unmodifiable ordered list of pending server to client method
+     *         calls (not null)
+     * 
+     * @since 7.0
+     */
+    public List<ClientMethodInvocation> retrievePendingRpcCalls();
+
     /**
      * Requests that the paintable should be repainted as soon as possible.
      */
index 55e62abb0a28bd66e9ec28b952fa7810cfdba038..462450422b7cead9cfd1d15b7866f84518f492aa 100644 (file)
@@ -6,9 +6,12 @@ package com.vaadin.terminal;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.Collections;
+import java.util.List;
 
 import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
+import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
 
 /**
  * <code>SystemError</code> is a runtime exception caused by error in system.
@@ -96,6 +99,10 @@ public class SystemError extends RuntimeException implements ErrorMessage {
         return null;
     }
 
+    public List<ClientMethodInvocation> retrievePendingRpcCalls() {
+        return Collections.emptyList();
+    }
+
     /**
      * Returns the message of the error in HTML.
      * 
index 50cf32ab0cfe5752da146d7236214692ac39aa4c..15473e47e1298b81aea738f3c905b6eaa8f91123 100644 (file)
@@ -4,8 +4,12 @@
 
 package com.vaadin.terminal;
 
+import java.util.Collections;
+import java.util.List;
+
 import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
+import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
 
 /**
  * <code>UserError</code> is a controlled error occurred in application. User
@@ -149,6 +153,10 @@ public class UserError implements ErrorMessage {
         return null;
     }
 
+    public List<ClientMethodInvocation> retrievePendingRpcCalls() {
+        return Collections.emptyList();
+    }
+
     /* Documented in interface */
     public void requestRepaintRequests() {
     }
index 04f291d5992887185089c347177dd77d04151a5c..6a8a5df7078b2f36c1f2ab87990b5d99c48c9276 100644 (file)
                <when-type-assignable class="com.vaadin.terminal.gwt.client.communication.ServerRpc" />
        </generate-with> -->    
 
+       <!-- Generate client side RPC manager for server to client RPC -->
+       <generate-with
+               class="com.vaadin.terminal.gwt.widgetsetutils.RpcManagerGenerator">
+               <when-type-assignable class="com.vaadin.terminal.gwt.client.communication.RpcManager" />
+       </generate-with> -->    
+
        <!-- Fall through to this rule for everything but IE -->
        <replace-with
                class="com.vaadin.terminal.gwt.client.ui.UploadIFrameOnloadStrategy">
index ec1ca7e5859ee9790e6cb140777e390268f9897f..869c387a2f23240e49d0787449789fe76c4900fd 100644 (file)
@@ -39,6 +39,7 @@ import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage;
 import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
 import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
 import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
+import com.vaadin.terminal.gwt.client.communication.RpcManager;
 import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.client.ui.RootConnector;
 import com.vaadin.terminal.gwt.client.ui.VContextMenu;
@@ -54,8 +55,8 @@ import com.vaadin.terminal.gwt.server.AbstractCommunicationManager;
  * 
  * Client-side widgets receive updates from the corresponding server-side
  * components as calls to
- * {@link VPaintableWidget#updateFromUIDL(UIDL, ApplicationConnection)} (not to
- * be confused with the server side interface
+ * {@link ComponentConnector#updateFromUIDL(UIDL, ApplicationConnection)} (not
+ * to be confused with the server side interface
  * {@link com.vaadin.terminal.Paintable} ). Any client-side changes (typically
  * resulting from user actions) are sent back to the server as variable changes
  * (see {@link #updateVariable()}).
@@ -167,6 +168,8 @@ public class ApplicationConnection {
 
     private final LayoutManager layoutManager = new LayoutManager(this);
 
+    private final RpcManager rpcManager = GWT.create(RpcManager.class);
+
     public ApplicationConnection() {
         view = GWT.create(RootConnector.class);
     }
@@ -808,7 +811,7 @@ public class ApplicationConnection {
      */
     private void cleanVariableBurst(ArrayList<MethodInvocation> variableBurst) {
         for (int i = 1; i < variableBurst.size(); i++) {
-            String id = variableBurst.get(i).getPaintableId();
+            String id = variableBurst.get(i).getConnectorId();
             if (!getConnectorMap().hasConnector(id)
                     && !getConnectorMap().isDragAndDropPaintable(id)) {
                 // variable owner does not exist anymore
@@ -1088,6 +1091,27 @@ public class ApplicationConnection {
                     }
                 }
 
+                if (json.containsKey("rpc")) {
+                    VConsole.log(" * Performing server to client RPC calls");
+
+                    JSONArray rpcCalls = new JSONArray(
+                            json.getJavaScriptObject("rpc"));
+
+                    int rpcLength = rpcCalls.size();
+                    for (int i = 0; i < rpcLength; i++) {
+                        try {
+                            JSONArray rpcCall = (JSONArray) rpcCalls.get(i);
+                            MethodInvocation invocation = parseMethodInvocation(rpcCall);
+                            VConsole.log("Server to client RPC call: "
+                                    + invocation);
+                            rpcManager.applyInvocation(invocation,
+                                    getConnectorMap());
+                        } catch (final Throwable e) {
+                            VConsole.error(e);
+                        }
+                    }
+                }
+
                 if (json.containsKey("dd")) {
                     // response contains data for drag and drop service
                     VDragAndDropManager.get().handleServerResponse(
@@ -1163,10 +1187,25 @@ public class ApplicationConnection {
                 endRequest();
 
             }
+
         };
         ApplicationConfiguration.runWhenWidgetsLoaded(c);
     }
 
+    private MethodInvocation parseMethodInvocation(JSONArray rpcCall) {
+        String connectorId = ((JSONString) rpcCall.get(0)).stringValue();
+        String interfaceName = ((JSONString) rpcCall.get(1)).stringValue();
+        String methodName = ((JSONString) rpcCall.get(2)).stringValue();
+        JSONArray parametersJson = (JSONArray) rpcCall.get(3);
+        Object[] parameters = new Object[parametersJson.size()];
+        for (int j = 0; j < parametersJson.size(); ++j) {
+            parameters[j] = JsonDecoder.convertValue(
+                    (JSONArray) parametersJson.get(j), getConnectorMap());
+        }
+        return new MethodInvocation(connectorId, interfaceName, methodName,
+                parameters);
+    }
+
     // Redirect browser, null reloads current page
     private static native void redirect(String url)
     /*-{
@@ -1177,11 +1216,11 @@ public class ApplicationConnection {
        }
     }-*/;
 
-    private void addVariableToQueue(String paintableId, String variableName,
+    private void addVariableToQueue(String connectorId, String variableName,
             Object value, boolean immediate) {
         // note that type is now deduced from value
         // TODO could eliminate invocations of same shared variable setter
-        addMethodInvocationToQueue(new MethodInvocation(paintableId,
+        addMethodInvocationToQueue(new MethodInvocation(connectorId,
                 UPDATE_VARIABLE_INTERFACE, UPDATE_VARIABLE_METHOD,
                 new Object[] { variableName, value }), immediate);
     }
@@ -1273,7 +1312,7 @@ public class ApplicationConnection {
             for (MethodInvocation invocation : pendingInvocations) {
                 JSONArray invocationJson = new JSONArray();
                 invocationJson.set(0,
-                        new JSONString(invocation.getPaintableId()));
+                        new JSONString(invocation.getConnectorId()));
                 invocationJson.set(1,
                         new JSONString(invocation.getInterfaceName()));
                 invocationJson.set(2,
index eee525865bd7933a397897988e6b559a182b03d2..67202ce02b08baddb555ab771fc3ad1503857a35 100644 (file)
@@ -3,6 +3,9 @@
  */
 package com.vaadin.terminal.gwt.client;
 
+import java.util.Collection;
+
+import com.vaadin.terminal.gwt.client.communication.ClientRpc;
 import com.vaadin.terminal.gwt.client.communication.SharedState;
 
 /**
@@ -73,4 +76,18 @@ public interface Connector {
      */
     public void doInit(String connectorId, ApplicationConnection connection);
 
+    /**
+     * For internal use by the framework: returns the registered RPC
+     * implementations for an RPC interface identifier.
+     * 
+     * TODO interface identifier type or format may change
+     * 
+     * @param rpcInterfaceId
+     *            RPC interface identifier: fully qualified interface type name
+     * @return RPC interface implementations registered for an RPC interface,
+     *         not null
+     */
+    public <T extends ClientRpc> Collection<T> getRpcImplementations(
+            String rpcInterfaceId);
+
 }
index 5ed30eaff1e45e766d8f8ad610695803f9def1d8..572a9bebb27b70abbf7e851b12e1d68f6a28ace6 100644 (file)
@@ -858,7 +858,7 @@ public class Util {
             String curId = null;
             ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>();
             for (int i = 0; i < loggedBurst.size(); i++) {
-                String id = loggedBurst.get(i).getPaintableId();
+                String id = loggedBurst.get(i).getConnectorId();
 
                 if (curId == null) {
                     curId = id;
diff --git a/src/com/vaadin/terminal/gwt/client/communication/ClientRpc.java b/src/com/vaadin/terminal/gwt/client/communication/ClientRpc.java
new file mode 100644 (file)
index 0000000..45dbe69
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.client.communication;
+
+import java.io.Serializable;
+
+/**
+ * Interface to be extended by all server to client RPC interfaces.
+ * 
+ * On the server side, proxies of the interface can be obtained from
+ * AbstractComponent. On the client, RPC implementations can be registered with
+ * AbstractConnector.registerRpc().
+ * 
+ * Note: Currently, each RPC interface may not contain multiple methods with the
+ * same name, even if their parameter lists would differ.
+ * 
+ * @since 7.0
+ */
+public interface ClientRpc extends Serializable {
+
+}
index 17e1ce30e3ab0fe8feb7aa24ad5fd08dac09b4a0..f4305ffcaaeaa3de2bfbbd3b5a529de3946dd4bd 100644 (file)
@@ -4,6 +4,8 @@
 
 package com.vaadin.terminal.gwt.client.communication;
 
+import java.util.Arrays;
+
 /**
  * Information needed by the framework to send an RPC method invocation from the
  * client to the server or vice versa.
@@ -12,21 +14,21 @@ package com.vaadin.terminal.gwt.client.communication;
  */
 public class MethodInvocation {
 
-    private final String paintableId;
+    private final String connectorId;
     private final String interfaceName;
     private final String methodName;
     private final Object[] parameters;
 
-    public MethodInvocation(String paintableId, String interfaceName,
+    public MethodInvocation(String connectorId, String interfaceName,
             String methodName, Object[] parameters) {
-        this.paintableId = paintableId;
+        this.connectorId = connectorId;
         this.interfaceName = interfaceName;
         this.methodName = methodName;
         this.parameters = parameters;
     }
 
-    public String getPaintableId() {
-        return paintableId;
+    public String getConnectorId() {
+        return connectorId;
     }
 
     public String getInterfaceName() {
@@ -40,4 +42,10 @@ public class MethodInvocation {
     public Object[] getParameters() {
         return parameters;
     }
+
+    @Override
+    public String toString() {
+        return connectorId + ":" + interfaceName + "." + methodName + "("
+                + Arrays.toString(parameters) + ")";
+    }
 }
\ No newline at end of file
diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java
new file mode 100644 (file)
index 0000000..302e6ea
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.client.communication;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.gwt.client.ConnectorMap;
+
+/**
+ * Client side RPC manager that can invoke methods based on RPC calls received
+ * from the server.
+ * 
+ * A GWT generator is used to create an implementation of this class at
+ * run-time.
+ * 
+ * @since 7.0
+ */
+public interface RpcManager extends Serializable {
+    /**
+     * Perform server to client RPC invocation.
+     * 
+     * @param invocation
+     *            method to invoke
+     * @param connectorMap
+     *            mapper used to find Connector for the method call and any
+     *            connectors referenced in parameters
+     */
+    public void applyInvocation(MethodInvocation invocation,
+            ConnectorMap connectorMap);
+}
index c08008e5c10a896e5f808c739c3cac48ea306b59..36f23a0cc16634ed5fe76fde36d8ea91df27d315 100644 (file)
@@ -11,8 +11,8 @@ import com.vaadin.terminal.gwt.client.ApplicationConnection;
 /**
  * Interface to be extended by all client to server RPC interfaces.
  * 
- * The nested interface InitializableClientToServerRpc The
- * {@link #initRpc(String, ApplicationConnection)} method is created
+ * The nested interface InitializableClientToServerRpc has an
+ * {@link #initRpc(String, ApplicationConnection)} method, which is created
  * automatically by a GWT generator and must be called on the client side before
  * generated implementations of the interface are used to perform RPC calls.
  * 
index 2b5bccd66f7f244067cc844eae9ec77718ea0020..7232f4ed7e0fe39082b37bd20b1a8b8fee807329 100755 (executable)
@@ -1,13 +1,22 @@
 package com.vaadin.terminal.gwt.client.ui;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import com.vaadin.terminal.gwt.client.ApplicationConnection;
 import com.vaadin.terminal.gwt.client.Connector;
+import com.vaadin.terminal.gwt.client.communication.ClientRpc;
 
 public abstract class AbstractConnector implements Connector {
 
     private ApplicationConnection connection;
     private String id;
 
+    private Map<String, Collection<ClientRpc>> rpcImplementations;
+
     /*
      * (non-Javadoc)
      * 
@@ -61,4 +70,52 @@ public abstract class AbstractConnector implements Connector {
 
     }
 
+    /**
+     * Registers an implementation for a server to client RPC interface.
+     * 
+     * Multiple registrations can be made for a single interface, in which case
+     * all of them receive corresponding RPC calls.
+     * 
+     * @param rpcInterface
+     *            RPC interface
+     * @param implementation
+     *            implementation that should receive RPC calls
+     */
+    protected <T extends ClientRpc> void registerRpc(Class<T> rpcInterface,
+            T implementation) {
+        String rpcInterfaceId = rpcInterface.getName().replaceAll("\\$", ".");
+        if (null == rpcImplementations) {
+            rpcImplementations = new HashMap<String, Collection<ClientRpc>>();
+        }
+        if (null == rpcImplementations.get(rpcInterfaceId)) {
+            rpcImplementations.put(rpcInterfaceId, new ArrayList<ClientRpc>());
+        }
+        rpcImplementations.get(rpcInterfaceId).add(implementation);
+    }
+
+    /**
+     * Unregisters an implementation for a server to client RPC interface.
+     * 
+     * @param rpcInterface
+     *            RPC interface
+     * @param implementation
+     *            implementation to unregister
+     */
+    protected <T extends ClientRpc> void unregisterRpc(Class<T> rpcInterface,
+            T implementation) {
+        String rpcInterfaceId = rpcInterface.getName().replaceAll("\\$", ".");
+        if (null != rpcImplementations
+                && null != rpcImplementations.get(rpcInterfaceId)) {
+            rpcImplementations.get(rpcInterfaceId).remove(implementation);
+        }
+    }
+
+    public <T extends ClientRpc> Collection<T> getRpcImplementations(
+            String rpcInterfaceId) {
+        if (null == rpcImplementations) {
+            return Collections.emptyList();
+        }
+        return (Collection<T>) rpcImplementations.get(rpcInterfaceId);
+    }
+
 }
index 1ba3fd69301956bd706149f6e22cf8ffe5dd2293..bd27b8244da3b7213312873b237936f7a798279d 100644 (file)
@@ -6,13 +6,12 @@ package com.vaadin.terminal.gwt.client.ui;
 import com.vaadin.terminal.gwt.client.ApplicationConnection;
 import com.vaadin.terminal.gwt.client.UIDL;
 import com.vaadin.terminal.gwt.client.Util;
+import com.vaadin.terminal.gwt.client.communication.ClientRpc;
 
 public abstract class MediaBaseConnector extends AbstractComponentConnector {
 
     public static final String TAG_SOURCE = "src";
 
-    public static final String ATTR_PAUSE = "pause";
-    public static final String ATTR_PLAY = "play";
     public static final String ATTR_MUTED = "muted";
     public static final String ATTR_CONTROLS = "ctrl";
     public static final String ATTR_AUTOPLAY = "auto";
@@ -21,6 +20,38 @@ public abstract class MediaBaseConnector extends AbstractComponentConnector {
     public static final String ATTR_HTML = "html";
     public static final String ATTR_ALT_TEXT = "alt";
 
+    /**
+     * Server to client RPC interface for controlling playback of the media.
+     * 
+     * @since 7.0
+     */
+    public static interface MediaControl extends ClientRpc {
+        /**
+         * Start playing the media.
+         */
+        public void play();
+
+        /**
+         * Pause playback of the media.
+         */
+        public void pause();
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+
+        registerRpc(MediaControl.class, new MediaControl() {
+            public void play() {
+                getWidget().play();
+            }
+
+            public void pause() {
+                getWidget().pause();
+            }
+        });
+    }
+
     @Override
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
         super.updateFromUIDL(uidl, client);
@@ -41,9 +72,6 @@ public abstract class MediaBaseConnector extends AbstractComponentConnector {
             }
         }
         setAltText(uidl);
-
-        evalPauseCommand(uidl);
-        evalPlayCommand(uidl);
     }
 
     protected boolean shouldShowControls(UIDL uidl) {
@@ -62,18 +90,6 @@ public abstract class MediaBaseConnector extends AbstractComponentConnector {
         return uidl.getBooleanAttribute(ATTR_HTML);
     }
 
-    private void evalPlayCommand(UIDL uidl) {
-        if (uidl.hasAttribute(ATTR_PLAY)) {
-            getWidget().play();
-        }
-    }
-
-    private void evalPauseCommand(UIDL uidl) {
-        if (uidl.hasAttribute(ATTR_PAUSE)) {
-            getWidget().pause();
-        }
-    }
-
     @Override
     public VMediaBase getWidget() {
         return (VMediaBase) super.getWidget();
index c52b6fad6a1b759d5a6a92c13ad939568266d6e6..9143d61aca7db15ceed625f9932d6a028b36ba6b 100644 (file)
@@ -807,6 +807,7 @@ public abstract class AbstractCommunicationManager implements
         }
 
         LinkedList<Paintable> stateQueue = new LinkedList<Paintable>();
+        LinkedList<Paintable> rpcPendingQueue = new LinkedList<Paintable>();
 
         if (paintables != null) {
 
@@ -815,9 +816,11 @@ public abstract class AbstractCommunicationManager implements
             paintQueue.addAll(paintables);
 
             while (!paintQueue.isEmpty()) {
-                final Paintable p = paintQueue.pop();
+                final Paintable p = paintQueue.removeFirst();
                 // for now, all painted components may need a state refresh
                 stateQueue.push(p);
+                // ... and RPC calls to be sent
+                rpcPendingQueue.push(p);
 
                 // // TODO CLEAN
                 // if (p instanceof Root) {
@@ -898,10 +901,50 @@ public abstract class AbstractCommunicationManager implements
             }
             outWriter.print("\"state\":");
             outWriter.append(sharedStates.toString());
-            outWriter.print(", "); // close changes
+            outWriter.print(", "); // close states
         }
 
-        // TODO send server to client RPC calls in call order here
+        // send server to client RPC calls for components in the root, in call
+        // order
+        if (!rpcPendingQueue.isEmpty()) {
+            // collect RPC calls from components in the root in the order in
+            // which they were performed, remove the calls from components
+            List<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(rpcPendingQueue);
+
+            JSONArray rpcCalls = new JSONArray();
+            for (ClientMethodInvocation invocation : pendingInvocations) {
+                // add invocation to rpcCalls
+                try {
+                    JSONArray invocationJson = new JSONArray();
+                    invocationJson
+                            .put(getPaintableId(invocation.getPaintable()));
+                    invocationJson.put(invocation.getInterfaceName());
+                    invocationJson.put(invocation.getMethodName());
+                    JSONArray paramJson = new JSONArray();
+                    for (int i = 0; i < invocation.getParameters().length; ++i) {
+                        paramJson.put(JsonCodec.encode(
+                                invocation.getParameters()[i], this));
+                    }
+                    invocationJson.put(paramJson);
+                    rpcCalls.put(invocationJson);
+                } catch (JSONException e) {
+                    throw new PaintException(
+                            "Failed to serialize RPC method call parameters for paintable "
+                                    + getPaintableId(invocation.getPaintable())
+                                    + " method "
+                                    + invocation.getInterfaceName() + "."
+                                    + invocation.getMethodName() + ": "
+                                    + e.getMessage());
+                }
+
+            }
+
+            if (rpcCalls.length() > 0) {
+                outWriter.print("\"rpc\" : ");
+                outWriter.append(rpcCalls.toString());
+                outWriter.print(", "); // close rpc
+            }
+        }
 
         outWriter.print("\"meta\" : {");
         boolean metaOpen = false;
@@ -1050,6 +1093,45 @@ public abstract class AbstractCommunicationManager implements
         }
     }
 
+    /**
+     * Collects all pending RPC calls from listed {@link Paintable}s and clears
+     * their RPC queues.
+     * 
+     * @param rpcPendingQueue
+     *            list of {@link Paintable} of interest
+     * @return ordered list of pending RPC calls
+     */
+    private List<ClientMethodInvocation> collectPendingRpcCalls(
+            LinkedList<Paintable> rpcPendingQueue) {
+        List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
+        for (Paintable paintable : rpcPendingQueue) {
+            List<ClientMethodInvocation> paintablePendingRpc = paintable
+                    .retrievePendingRpcCalls();
+            if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) {
+                List<ClientMethodInvocation> oldPendingRpc = pendingInvocations;
+                int totalCalls = pendingInvocations.size()
+                        + paintablePendingRpc.size();
+                pendingInvocations = new ArrayList<ClientMethodInvocation>(
+                        totalCalls);
+
+                // merge two ordered comparable lists
+                for (int destIndex = 0, oldIndex = 0, paintableIndex = 0; destIndex < totalCalls; destIndex++) {
+                    if (paintableIndex >= paintablePendingRpc.size()
+                            || (oldIndex < oldPendingRpc.size() && ((Comparable<ClientMethodInvocation>) oldPendingRpc
+                                    .get(oldIndex))
+                                    .compareTo(paintablePendingRpc
+                                            .get(paintableIndex)) <= 0)) {
+                        pendingInvocations.add(oldPendingRpc.get(oldIndex++));
+                    } else {
+                        pendingInvocations.add(paintablePendingRpc
+                                .get(paintableIndex++));
+                    }
+                }
+            }
+        }
+        return pendingInvocations;
+    }
+
     protected abstract InputStream getThemeResourceAsStream(Root root,
             String themeName, String resource);
 
@@ -1279,7 +1361,7 @@ public abstract class AbstractCommunicationManager implements
                 }
 
                 final VariableOwner owner = getVariableOwner(invocation
-                        .getPaintableId());
+                        .getConnectorId());
 
                 if (owner != null && owner.isEnabled()) {
                     VariableChange change = new VariableChange(invocation);
@@ -1290,8 +1372,8 @@ public abstract class AbstractCommunicationManager implements
                     Map<String, Object> m = new HashMap<String, Object>();
                     m.put(change.getName(), change.getValue());
                     while (nextInvocation != null
-                            && invocation.getPaintableId().equals(
-                                    nextInvocation.getPaintableId())
+                            && invocation.getConnectorId().equals(
+                                    nextInvocation.getConnectorId())
                             && ApplicationConnection.UPDATE_VARIABLE_METHOD
                                     .equals(nextInvocation.getMethodName())) {
                         i++;
@@ -1343,7 +1425,7 @@ public abstract class AbstractCommunicationManager implements
                         }
                     } else {
                         msg += "non-existent component, VAR_PID="
-                                + invocation.getPaintableId();
+                                + invocation.getConnectorId();
                         // TODO should this cause the message to be ignored?
                         success = false;
                     }
@@ -1369,13 +1451,13 @@ public abstract class AbstractCommunicationManager implements
      * @param invocation
      */
     protected void applyInvocation(MethodInvocation invocation) {
-        final RpcTarget target = getRpcTarget(invocation.getPaintableId());
+        final RpcTarget target = getRpcTarget(invocation.getConnectorId());
         if (null != target) {
             ServerRpcManager.applyInvocation(target, invocation);
         } else {
             // TODO better exception?
             throw new RuntimeException("No RPC target for paintable "
-                    + invocation.getPaintableId());
+                    + invocation.getConnectorId());
         }
     }
 
@@ -1397,7 +1479,7 @@ public abstract class AbstractCommunicationManager implements
         // parse JSON to MethodInvocations
         for (int i = 0; i < invocationsJson.length(); ++i) {
             JSONArray invocationJson = invocationsJson.getJSONArray(i);
-            String paintableId = invocationJson.getString(0);
+            String connectorId = invocationJson.getString(0);
             String interfaceName = invocationJson.getString(1);
             String methodName = invocationJson.getString(2);
             JSONArray parametersJson = invocationJson.getJSONArray(3);
@@ -1406,7 +1488,7 @@ public abstract class AbstractCommunicationManager implements
                 parameters[j] = JsonCodec.convertVariableValue(
                         parametersJson.getJSONArray(j), this);
             }
-            MethodInvocation invocation = new MethodInvocation(paintableId,
+            MethodInvocation invocation = new MethodInvocation(connectorId,
                     interfaceName, methodName, parameters);
             invocations.add(invocation);
         }
diff --git a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java
new file mode 100644 (file)
index 0000000..8133a61
--- /dev/null
@@ -0,0 +1,64 @@
+/* 
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.server;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.Paintable;
+
+/**
+ * Internal class for keeping track of pending server to client method
+ * invocations for a Paintable.
+ * 
+ * @since 7.0
+ */
+public class ClientMethodInvocation implements Serializable,
+        Comparable<ClientMethodInvocation> {
+    private final Paintable paintable;
+    private final String interfaceName;
+    private final String methodName;
+    private final Object[] parameters;
+
+    // used for sorting calls between different Paintables in the same Root
+    private final long sequenceNumber;
+    // TODO may cause problems when clustering etc.
+    private static long counter = 0;
+
+    public ClientMethodInvocation(Paintable paintable, String interfaceName,
+            String methodName, Object[] parameters) {
+        this.paintable = paintable;
+        this.interfaceName = interfaceName;
+        this.methodName = methodName;
+        this.parameters = (null != parameters) ? parameters : new Object[0];
+        sequenceNumber = ++counter;
+    }
+
+    public Paintable getPaintable() {
+        return paintable;
+    }
+
+    public String getInterfaceName() {
+        return interfaceName;
+    }
+
+    public String getMethodName() {
+        return methodName;
+    }
+
+    public Object[] getParameters() {
+        return parameters;
+    }
+
+    protected long getSequenceNumber() {
+        return sequenceNumber;
+    }
+
+    public int compareTo(ClientMethodInvocation o) {
+        if (null == o) {
+            return 0;
+        }
+        return Long.signum(getSequenceNumber() - o.getSequenceNumber());
+    }
+}
\ No newline at end of file
diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java
new file mode 100644 (file)
index 0000000..844b602
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.terminal.gwt.client.Connector;
+import com.vaadin.terminal.gwt.client.ConnectorMap;
+import com.vaadin.terminal.gwt.client.communication.ClientRpc;
+import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
+import com.vaadin.terminal.gwt.client.communication.RpcManager;
+
+/**
+ * GWT generator that creates an implementation for {@link RpcManager} on the
+ * client side classes for executing RPC calls received from the the server.
+ * 
+ * @since 7.0
+ */
+public class RpcManagerGenerator extends Generator {
+
+    @Override
+    public String generate(TreeLogger logger, GeneratorContext context,
+            String typeName) throws UnableToCompleteException {
+
+        String packageName = null;
+        String className = null;
+        try {
+            TypeOracle typeOracle = context.getTypeOracle();
+
+            // get classType and save instance variables
+            JClassType classType = typeOracle.getType(typeName);
+            packageName = classType.getPackage().getName();
+            className = classType.getSimpleSourceName() + "Impl";
+            // Generate class source code for SerializerMapImpl
+            generateClass(logger, context, packageName, className);
+        } catch (Exception e) {
+            logger.log(TreeLogger.ERROR,
+                    "SerializerMapGenerator creation failed", e);
+        }
+        // return the fully qualifed name of the class generated
+        return packageName + "." + className;
+    }
+
+    /**
+     * Generate source code for RpcManagerImpl
+     * 
+     * @param logger
+     *            Logger object
+     * @param context
+     *            Generator context
+     * @param packageName
+     *            package name for the class to generate
+     * @param className
+     *            class name for the class to generate
+     */
+    private void generateClass(TreeLogger logger, GeneratorContext context,
+            String packageName, String className) {
+        // get print writer that receives the source code
+        PrintWriter printWriter = null;
+        printWriter = context.tryCreate(logger, packageName, className);
+        // print writer if null, source code has ALREADY been generated
+        if (printWriter == null) {
+            return;
+        }
+        logger.log(Type.INFO,
+                "Detecting server to client RPC interface types...");
+        Date date = new Date();
+        TypeOracle typeOracle = context.getTypeOracle();
+        JClassType serverToClientRpcType = typeOracle.findType(ClientRpc.class
+                .getName());
+        JClassType[] rpcInterfaceSubtypes = serverToClientRpcType.getSubtypes();
+
+        // init composer, set class properties, create source writer
+        ClassSourceFileComposerFactory composer = null;
+        composer = new ClassSourceFileComposerFactory(packageName, className);
+        composer.addImport("com.google.gwt.core.client.GWT");
+        composer.addImplementedInterface(RpcManager.class.getName());
+        SourceWriter sourceWriter = composer.createSourceWriter(context,
+                printWriter);
+        sourceWriter.indent();
+
+        List<JClassType> rpcInterfaces = new ArrayList<JClassType>();
+
+        // iterate over RPC interfaces and create helper methods for each
+        // interface
+        for (JClassType type : rpcInterfaceSubtypes) {
+            if (null == type.isInterface()) {
+                // only interested in interfaces here, not implementations
+                continue;
+            }
+            rpcInterfaces.add(type);
+            // generate method to call methods of an RPC interface
+            sourceWriter.println("private void " + getInvokeMethodName(type)
+                    + "(" + MethodInvocation.class.getName() + " invocation, "
+                    + ConnectorMap.class.getName() + " connectorMap) {");
+            sourceWriter.indent();
+
+            // loop over the methods of the interface and its superinterfaces
+            // methods
+            for (JClassType currentType : type.getFlattenedSupertypeHierarchy()) {
+                for (JMethod method : currentType.getMethods()) {
+                    sourceWriter.println("if (\"" + method.getName()
+                            + "\".equals(invocation.getMethodName())) {");
+                    sourceWriter.indent();
+                    // construct parameter string with appropriate casts
+                    String paramString = "";
+                    JType[] parameterTypes = method.getParameterTypes();
+                    for (int i = 0; i < parameterTypes.length; ++i) {
+                        paramString = paramString + "("
+                                + parameterTypes[i].getQualifiedSourceName()
+                                + ") invocation.getParameters()[i]";
+                        if (i < parameterTypes.length - 1) {
+                            paramString = paramString + ", ";
+                        }
+                    }
+                    sourceWriter
+                            .println(Connector.class.getName()
+                                    + " connector = connectorMap.getConnector(invocation.getConnectorId());");
+                    sourceWriter
+                            .println("for ("
+                                    + ClientRpc.class.getName()
+                                    + " rpcImplementation : connector.getRpcImplementations(\""
+                                    + type.getQualifiedSourceName() + "\")) {");
+                    sourceWriter.indent();
+                    sourceWriter.println("((" + type.getQualifiedSourceName()
+                            + ") rpcImplementation)." + method.getName() + "("
+                            + paramString + ");");
+                    sourceWriter.outdent();
+                    sourceWriter.println("}");
+                    sourceWriter.println("return;");
+                    sourceWriter.outdent();
+                    sourceWriter.println("}");
+                }
+            }
+
+            sourceWriter.outdent();
+            sourceWriter.println("}");
+
+            logger.log(Type.DEBUG,
+                    "Constructed helper method for server to client RPC for "
+                            + type.getName());
+        }
+
+        // generate top-level "switch-case" method to select the correct
+        // previously generated method based on the RPC interface
+        sourceWriter.println("public void applyInvocation("
+                + MethodInvocation.class.getName() + " invocation, "
+                + ConnectorMap.class.getName() + " connectorMap) {");
+        sourceWriter.indent();
+
+        for (JClassType type : rpcInterfaces) {
+            sourceWriter.println("if (\"" + type.getQualifiedSourceName()
+                    + "\".equals(invocation.getInterfaceName())) {");
+            sourceWriter.indent();
+            sourceWriter.println(getInvokeMethodName(type)
+                    + "(invocation, connectorMap);");
+            sourceWriter.println("return;");
+            sourceWriter.outdent();
+            sourceWriter.println("}");
+
+            logger.log(Type.INFO,
+                    "Configured server to client RPC for " + type.getName());
+        }
+        sourceWriter.outdent();
+        sourceWriter.println("}");
+
+        // close generated class
+        sourceWriter.outdent();
+        sourceWriter.println("}");
+        // commit generated class
+        context.commit(logger, printWriter);
+        logger.log(Type.INFO,
+                "Done. (" + (new Date().getTime() - date.getTime()) / 1000
+                        + "seconds)");
+
+    }
+
+    private String getInvokeMethodName(JClassType type) {
+        return "invoke" + type.getQualifiedSourceName().replaceAll("\\.", "_");
+    }
+}
index 2d5e2b704fcb36e6494cfcaa61d4949623323f88..778ced783ba896f4c3d5c6b304856a5e06a3b0d9 100644 (file)
@@ -66,7 +66,7 @@ public class SerializerMapGenerator extends Generator {
     }
 
     /**
-     * Generate source code for WidgetMapImpl
+     * Generate source code for SerializerMapImpl
      * 
      * @param typesNeedingSerializers
      * 
@@ -80,8 +80,7 @@ public class SerializerMapGenerator extends Generator {
         // get print writer that receives the source code
         PrintWriter printWriter = null;
         printWriter = context.tryCreate(logger, packageName, className);
-        // print writer if null, source code has ALREADY been generated,
-        // return (WidgetMap is equal to all permutations atm)
+        // print writer if null, source code has ALREADY been generated
         if (printWriter == null) {
             return;
         }
index 1757c6a7d93183e64cadf7665f3f2ed8bfecfb18..40e4771aa44b7489412c076eb2009aaaeb487ca8 100644 (file)
@@ -5,7 +5,9 @@
 package com.vaadin.ui;
 
 import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -13,6 +15,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -31,7 +34,9 @@ import com.vaadin.terminal.PaintTarget.PaintStatus;
 import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.Terminal;
 import com.vaadin.terminal.gwt.client.ComponentState;
+import com.vaadin.terminal.gwt.client.communication.ClientRpc;
 import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector;
+import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
 import com.vaadin.terminal.gwt.server.ComponentSizeValidator;
 import com.vaadin.terminal.gwt.server.RpcManager;
 import com.vaadin.terminal.gwt.server.RpcTarget;
@@ -161,17 +166,28 @@ public abstract class AbstractComponent implements Component, MethodEventSource
     private ActionManager actionManager;
 
     /**
-     * A map from RPC interface class to the RPC call manager that handles
-     * incoming RPC calls for that interface.
+     * A map from client to server RPC interface class to the RPC call manager
+     * that handles incoming RPC calls for that interface.
      */
     private Map<Class<?>, RpcManager> rpcManagerMap = new HashMap<Class<?>, RpcManager>();
 
+    /**
+     * A map from server to client RPC interface class to the RPC proxy that
+     * sends ourgoing RPC calls for that interface.
+     */
+    private Map<Class<?>, ClientRpc> rpcProxyMap = new HashMap<Class<?>, ClientRpc>();
+
     /**
      * Shared state object to be communicated from the server to the client when
      * modified.
      */
     private ComponentState sharedState;
 
+    /**
+     * Pending RPC method invocations to be sent.
+     */
+    private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
+
     /* Constructor */
 
     /**
@@ -1631,8 +1647,86 @@ public abstract class AbstractComponent implements Component, MethodEventSource
         }
     }
 
+    /**
+     * Returns an RPC proxy for a given server to client RPC interface for this
+     * component.
+     * 
+     * TODO more javadoc, subclasses, ...
+     * 
+     * @param rpcInterface
+     *            RPC interface type
+     * 
+     * @since 7.0
+     */
+    public <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface) {
+        // create, initialize and return a dynamic proxy for RPC
+        try {
+            if (!rpcProxyMap.containsKey(rpcInterface)) {
+                InvocationHandler handler = new InvocationHandler() {
+                    public Object invoke(Object proxy, Method method,
+                            Object[] args) throws Throwable {
+                        addMethodInvocationToQueue(rpcInterface.getName()
+                                .replaceAll("\\$", "."), method.getName(), args);
+                        // TODO no need to do full repaint if only RPC calls
+                        requestRepaint();
+                        return null;
+                    }
+                };
+                Class<?> proxyClass = Proxy.getProxyClass(
+                        rpcInterface.getClassLoader(),
+                        new Class[] { rpcInterface });
+                T rpcProxy = (T) proxyClass.getConstructor(
+                        new Class[] { InvocationHandler.class }).newInstance(
+                        new Object[] { handler });
+                // cache the proxy
+                rpcProxyMap.put(rpcInterface, rpcProxy);
+            }
+            return (T) rpcProxyMap.get(rpcInterface);
+        } catch (Exception e) {
+            // TODO exception handling?
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * For internal use: adds a method invocation to the pending RPC call queue.
+     * 
+     * @param interfaceName
+     *            RPC interface name
+     * @param methodName
+     *            RPC method name
+     * @param parameters
+     *            RPC vall parameters
+     * 
+     * @since 7.0
+     */
+    protected void addMethodInvocationToQueue(String interfaceName,
+            String methodName, Object[] parameters) {
+        // add to queue
+        pendingInvocations.add(new ClientMethodInvocation(this, interfaceName,
+                methodName, parameters));
+    }
+
+    /**
+     * @see RpcTarget#getRpcManager(Class)
+     * 
+     * @param rpcInterface
+     *            RPC interface for which a call was made
+     * @return RPC Manager handling calls for the interface
+     * 
+     * @since 7.0
+     */
     public RpcManager getRpcManager(Class<?> rpcInterface) {
         return rpcManagerMap.get(rpcInterface);
     }
 
+    public List<ClientMethodInvocation> retrievePendingRpcCalls() {
+        if (pendingInvocations.isEmpty()) {
+            return Collections.emptyList();
+        } else {
+            List<ClientMethodInvocation> result = pendingInvocations;
+            pendingInvocations = new ArrayList<ClientMethodInvocation>();
+            return Collections.unmodifiableList(result);
+        }
+    }
 }
index 3c02d73858e0864b9dfb68a03183678d68bad258..e164b087f3cd81cc5aadc48485907ad0b8804366 100644 (file)
@@ -13,6 +13,7 @@ import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.gwt.client.ui.MediaBaseConnector;
+import com.vaadin.terminal.gwt.client.ui.MediaBaseConnector.MediaControl;
 
 /**
  * Abstract base class for the HTML5 media components.
@@ -33,10 +34,6 @@ public class AbstractMedia extends AbstractComponent {
 
     private boolean muted;
 
-    private boolean pause;
-
-    private boolean play;
-
     /**
      * Sets a single media file as the source of the media component.
      * 
@@ -182,22 +179,14 @@ public class AbstractMedia extends AbstractComponent {
      * Pauses the media.
      */
     public void pause() {
-        // cancel any possible play command
-        play = false;
-
-        pause = true;
-        requestRepaint();
+        getRpcProxy(MediaControl.class).pause();
     }
 
     /**
      * Starts playback of the media.
      */
     public void play() {
-        // cancel any possible pause command.
-        pause = false;
-
-        play = true;
-        requestRepaint();
+        getRpcProxy(MediaControl.class).play();
     }
 
     @Override
@@ -218,13 +207,5 @@ public class AbstractMedia extends AbstractComponent {
             target.endTag(MediaBaseConnector.TAG_SOURCE);
         }
         target.addAttribute(MediaBaseConnector.ATTR_MUTED, isMuted());
-        if (play) {
-            target.addAttribute(MediaBaseConnector.ATTR_PLAY, true);
-            play = false;
-        }
-        if (pause) {
-            target.addAttribute(MediaBaseConnector.ATTR_PAUSE, true);
-            pause = false;
-        }
     }
 }