]> source.dussan.org Git - vaadin-framework.git/commitdiff
Omit empty hierarchy data from the response (#18510)
authorLeif Åstrand <leif@vaadin.com>
Sat, 25 Jul 2015 09:14:30 +0000 (12:14 +0300)
committerVaadin Code Review <review@vaadin.com>
Wed, 25 Nov 2015 07:55:33 +0000 (07:55 +0000)
Reduces the payload size for the "Update all labels" action with 40
layouts in BasicPerformanceTest by 16% (from 11087 to 9270 bytes). The
reduction is improved to 22% (1855 to 1455) if the response is gzipped
since the omited data doesn't compress very well.

Change-Id: I1d2837c93222fffa59b14836f162e3e87349e086

client/src/com/vaadin/client/communication/MessageHandler.java
server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java
server/src/com/vaadin/server/communication/SharedStateWriter.java
server/src/com/vaadin/server/communication/UidlWriter.java

index e87928fc577581a5a333b2f762639d601762157b..7ff8820563cc7689f82e271c8113e330e34bb6db 100644 (file)
@@ -1122,142 +1122,42 @@ public class MessageHandler {
                 ConnectorHierarchyUpdateResult result = new ConnectorHierarchyUpdateResult();
 
                 getLogger().info(" * Updating connector hierarchy");
-                if (!json.containsKey("hierarchy")) {
-                    return result;
-                }
 
                 Profiler.enter("updateConnectorHierarchy");
 
                 FastStringSet maybeDetached = FastStringSet.create();
+                FastStringSet hasHierarchy = FastStringSet.create();
 
-                ValueMap hierarchies = json.getValueMap("hierarchy");
-                JsArrayString hierarchyKeys = hierarchies.getKeyArray();
-                for (int i = 0; i < hierarchyKeys.length(); i++) {
-                    try {
-                        Profiler.enter("updateConnectorHierarchy hierarchy entry");
-
+                // Process regular hierarchy data
+                if (json.containsKey("hierarchy")) {
+                    ValueMap hierarchies = json.getValueMap("hierarchy");
+                    JsArrayString hierarchyKeys = hierarchies.getKeyArray();
+                    for (int i = 0; i < hierarchyKeys.length(); i++) {
                         String connectorId = hierarchyKeys.get(i);
-                        ServerConnector parentConnector = getConnectorMap()
-                                .getConnector(connectorId);
                         JsArrayString childConnectorIds = hierarchies
                                 .getJSStringArray(connectorId);
-                        int childConnectorSize = childConnectorIds.length();
-
-                        Profiler.enter("updateConnectorHierarchy find new connectors");
-
-                        List<ServerConnector> newChildren = new ArrayList<ServerConnector>();
-                        List<ComponentConnector> newComponents = new ArrayList<ComponentConnector>();
-                        for (int connectorIndex = 0; connectorIndex < childConnectorSize; connectorIndex++) {
-                            String childConnectorId = childConnectorIds
-                                    .get(connectorIndex);
-                            ServerConnector childConnector = getConnectorMap()
-                                    .getConnector(childConnectorId);
-                            if (childConnector == null) {
-                                getLogger()
-                                        .severe("Hierarchy claims that "
-                                                + childConnectorId
-                                                + " is a child for "
-                                                + connectorId
-                                                + " ("
-                                                + parentConnector.getClass()
-                                                        .getName()
-                                                + ") but no connector with id "
-                                                + childConnectorId
-                                                + " has been registered. "
-                                                + "More information might be available in the server-side log if assertions are enabled");
-                                continue;
-                            }
-                            newChildren.add(childConnector);
-                            if (childConnector instanceof ComponentConnector) {
-                                newComponents
-                                        .add((ComponentConnector) childConnector);
-                            } else if (!(childConnector instanceof AbstractExtensionConnector)) {
-                                throw new IllegalStateException(
-                                        Util.getConnectorString(childConnector)
-                                                + " is not a ComponentConnector nor an AbstractExtensionConnector");
-                            }
-                            if (childConnector.getParent() != parentConnector) {
-                                childConnector.setParent(parentConnector);
-                                result.parentChangedIds.add(childConnectorId);
-                                // Not detached even if previously removed from
-                                // parent
-                                maybeDetached.remove(childConnectorId);
-                            }
-                        }
-
-                        Profiler.leave("updateConnectorHierarchy find new connectors");
-
-                        // TODO This check should be done on the server side in
-                        // the future so the hierarchy update is only sent when
-                        // something actually has changed
-                        List<ServerConnector> oldChildren = parentConnector
-                                .getChildren();
-                        boolean actuallyChanged = !Util.collectionsEquals(
-                                oldChildren, newChildren);
-
-                        if (!actuallyChanged) {
-                            continue;
-                        }
-
-                        Profiler.enter("updateConnectorHierarchy handle HasComponentsConnector");
-
-                        if (parentConnector instanceof HasComponentsConnector) {
-                            HasComponentsConnector ccc = (HasComponentsConnector) parentConnector;
-                            List<ComponentConnector> oldComponents = ccc
-                                    .getChildComponents();
-                            if (!Util.collectionsEquals(oldComponents,
-                                    newComponents)) {
-                                // Fire change event if the hierarchy has
-                                // changed
-                                ConnectorHierarchyChangeEvent event = GWT
-                                        .create(ConnectorHierarchyChangeEvent.class);
-                                event.setOldChildren(oldComponents);
-                                event.setConnector(parentConnector);
-                                ccc.setChildComponents(newComponents);
-                                result.events.add(event);
-                            }
-                        } else if (!newComponents.isEmpty()) {
-                            getLogger()
-                                    .severe("Hierachy claims "
-                                            + Util.getConnectorString(parentConnector)
-                                            + " has component children even though it isn't a HasComponentsConnector");
-                        }
+                        hasHierarchy.add(connectorId);
 
-                        Profiler.leave("updateConnectorHierarchy handle HasComponentsConnector");
-
-                        Profiler.enter("updateConnectorHierarchy setChildren");
-                        parentConnector.setChildren(newChildren);
-                        Profiler.leave("updateConnectorHierarchy setChildren");
+                        updateConnectorHierarchy(connectorId,
+                                childConnectorIds, maybeDetached, result);
+                    }
+                }
 
-                        Profiler.enter("updateConnectorHierarchy find removed children");
+                // Assume empty hierarchy for connectors with state updates but
+                // no hierarchy data
+                if (json.containsKey("state")) {
+                    JsArrayString stateKeys = json.getValueMap("state")
+                            .getKeyArray();
 
-                        /*
-                         * Find children removed from this parent and mark for
-                         * removal unless they are already attached to some
-                         * other parent.
-                         */
-                        for (ServerConnector oldChild : oldChildren) {
-                            if (oldChild.getParent() != parentConnector) {
-                                // Ignore if moved to some other connector
-                                continue;
-                            }
+                    JsArrayString emptyArray = JavaScriptObject.createArray()
+                            .cast();
 
-                            if (!newChildren.contains(oldChild)) {
-                                /*
-                                 * Consider child detached for now, will be
-                                 * cleared if it is later on added to some other
-                                 * parent.
-                                 */
-                                maybeDetached.add(oldChild.getConnectorId());
-                            }
+                    for (int i = 0; i < stateKeys.length(); i++) {
+                        String connectorId = stateKeys.get(i);
+                        if (!hasHierarchy.contains(connectorId)) {
+                            updateConnectorHierarchy(connectorId, emptyArray,
+                                    maybeDetached, result);
                         }
-
-                        Profiler.leave("updateConnectorHierarchy find removed children");
-                    } catch (final Throwable e) {
-                        getLogger().log(Level.SEVERE,
-                                "Error updating connector hierarchy", e);
-                    } finally {
-                        Profiler.leave("updateConnectorHierarchy hierarchy entry");
                     }
                 }
 
@@ -1287,6 +1187,149 @@ public class MessageHandler {
 
             }
 
+            /**
+             * Updates the hierarchy for a connector
+             * 
+             * @since
+             * @param connectorId
+             *            the id of the connector to update
+             * @param childConnectorIds
+             *            array of child connector ids
+             * @param maybeDetached
+             *            set of connectors that are maybe detached
+             * @param result
+             *            the hierarchy update result
+             */
+            private void updateConnectorHierarchy(String connectorId,
+                    JsArrayString childConnectorIds,
+                    FastStringSet maybeDetached,
+                    ConnectorHierarchyUpdateResult result) {
+                try {
+                    Profiler.enter("updateConnectorHierarchy hierarchy entry");
+
+                    ConnectorMap connectorMap = getConnectorMap();
+
+                    ServerConnector parentConnector = connectorMap
+                            .getConnector(connectorId);
+                    int childConnectorSize = childConnectorIds.length();
+
+                    Profiler.enter("updateConnectorHierarchy find new connectors");
+
+                    List<ServerConnector> newChildren = new ArrayList<ServerConnector>();
+                    List<ComponentConnector> newComponents = new ArrayList<ComponentConnector>();
+                    for (int connectorIndex = 0; connectorIndex < childConnectorSize; connectorIndex++) {
+                        String childConnectorId = childConnectorIds
+                                .get(connectorIndex);
+                        ServerConnector childConnector = connectorMap
+                                .getConnector(childConnectorId);
+                        if (childConnector == null) {
+                            getLogger()
+                                    .severe("Hierarchy claims that "
+                                            + childConnectorId
+                                            + " is a child for "
+                                            + connectorId
+                                            + " ("
+                                            + parentConnector.getClass()
+                                                    .getName()
+                                            + ") but no connector with id "
+                                            + childConnectorId
+                                            + " has been registered. "
+                                            + "More information might be available in the server-side log if assertions are enabled");
+                            continue;
+                        }
+                        newChildren.add(childConnector);
+                        if (childConnector instanceof ComponentConnector) {
+                            newComponents
+                                    .add((ComponentConnector) childConnector);
+                        } else if (!(childConnector instanceof AbstractExtensionConnector)) {
+                            throw new IllegalStateException(
+                                    Util.getConnectorString(childConnector)
+                                            + " is not a ComponentConnector nor an AbstractExtensionConnector");
+                        }
+                        if (childConnector.getParent() != parentConnector) {
+                            childConnector.setParent(parentConnector);
+                            result.parentChangedIds.add(childConnectorId);
+                            // Not detached even if previously removed from
+                            // parent
+                            maybeDetached.remove(childConnectorId);
+                        }
+                    }
+
+                    Profiler.leave("updateConnectorHierarchy find new connectors");
+
+                    // TODO This check should be done on the server side in
+                    // the future so the hierarchy update is only sent when
+                    // something actually has changed
+                    List<ServerConnector> oldChildren = parentConnector
+                            .getChildren();
+                    boolean actuallyChanged = !Util.collectionsEquals(
+                            oldChildren, newChildren);
+
+                    if (!actuallyChanged) {
+                        return;
+                    }
+
+                    Profiler.enter("updateConnectorHierarchy handle HasComponentsConnector");
+
+                    if (parentConnector instanceof HasComponentsConnector) {
+                        HasComponentsConnector ccc = (HasComponentsConnector) parentConnector;
+                        List<ComponentConnector> oldComponents = ccc
+                                .getChildComponents();
+                        if (!Util.collectionsEquals(oldComponents,
+                                newComponents)) {
+                            // Fire change event if the hierarchy has
+                            // changed
+                            ConnectorHierarchyChangeEvent event = GWT
+                                    .create(ConnectorHierarchyChangeEvent.class);
+                            event.setOldChildren(oldComponents);
+                            event.setConnector(parentConnector);
+                            ccc.setChildComponents(newComponents);
+                            result.events.add(event);
+                        }
+                    } else if (!newComponents.isEmpty()) {
+                        getLogger()
+                                .severe("Hierachy claims "
+                                        + Util.getConnectorString(parentConnector)
+                                        + " has component children even though it isn't a HasComponentsConnector");
+                    }
+
+                    Profiler.leave("updateConnectorHierarchy handle HasComponentsConnector");
+
+                    Profiler.enter("updateConnectorHierarchy setChildren");
+                    parentConnector.setChildren(newChildren);
+                    Profiler.leave("updateConnectorHierarchy setChildren");
+
+                    Profiler.enter("updateConnectorHierarchy find removed children");
+
+                    /*
+                     * Find children removed from this parent and mark for
+                     * removal unless they are already attached to some other
+                     * parent.
+                     */
+                    for (ServerConnector oldChild : oldChildren) {
+                        if (oldChild.getParent() != parentConnector) {
+                            // Ignore if moved to some other connector
+                            continue;
+                        }
+
+                        if (!newChildren.contains(oldChild)) {
+                            /*
+                             * Consider child detached for now, will be cleared
+                             * if it is later on added to some other parent.
+                             */
+                            maybeDetached.add(oldChild.getConnectorId());
+                        }
+                    }
+
+                    Profiler.leave("updateConnectorHierarchy find removed children");
+                } catch (final Throwable e) {
+                    getLogger().log(Level.SEVERE,
+                            "Error updating connector hierarchy", e);
+                } finally {
+                    Profiler.leave("updateConnectorHierarchy hierarchy entry");
+                }
+            }
+
             private void recursivelyDetach(ServerConnector connector,
                     JsArrayObject<ConnectorHierarchyChangeEvent> events,
                     FastStringSet detachedConnectors) {
index 1c1a220b5de2fc8bc065f77f37a874b7f1615a75..503bf8c0ae253e23757ef4f29854abdc383cf486 100644 (file)
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.io.Writer;
 import java.util.Collection;
+import java.util.Set;
 
 import com.vaadin.server.AbstractClientConnector;
 import com.vaadin.server.ClientConnector;
@@ -49,10 +50,13 @@ public class ConnectorHierarchyWriter implements Serializable {
      *            The {@link UI} whose hierarchy to write.
      * @param writer
      *            The {@link Writer} used to write the JSON.
+     * @param stateUpdateConnectors
+     *            connector ids with state changes
      * @throws IOException
      *             If the serialization fails.
      */
-    public void write(UI ui, Writer writer) throws IOException {
+    public void write(UI ui, Writer writer, Set<String> stateUpdateConnectors)
+            throws IOException {
 
         Collection<ClientConnector> dirtyVisibleConnectors = ui
                 .getConnectorTracker().getDirtyVisibleConnectors();
@@ -69,13 +73,18 @@ public class ConnectorHierarchyWriter implements Serializable {
                     children.set(children.length(), child.getConnectorId());
                 }
             }
-            try {
-                hierarchyInfo.put(connectorId, children);
-            } catch (JsonException e) {
-                throw new PaintException(
-                        "Failed to send hierarchy information about "
-                                + connectorId + " to the client: "
-                                + e.getMessage(), e);
+
+            // Omit for leaf nodes with state changes
+            if (children.length() > 0
+                    || !stateUpdateConnectors.contains(connectorId)) {
+                try {
+                    hierarchyInfo.put(connectorId, children);
+                } catch (JsonException e) {
+                    throw new PaintException(
+                            "Failed to send hierarchy information about "
+                                    + connectorId + " to the client: "
+                                    + e.getMessage(), e);
+                }
             }
         }
         writer.write(JsonUtil.stringify(hierarchyInfo));
index 6ef02955f7892791eefe93931e431c8d87e12bd7..06b59ad4cc4e85cd75c5901653a3f9703ce9f0a1 100644 (file)
@@ -20,6 +20,8 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.io.Writer;
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
 
 import com.vaadin.server.ClientConnector;
 import com.vaadin.server.PaintException;
@@ -47,31 +49,36 @@ public class SharedStateWriter implements Serializable {
      *            The UI whose state changes should be written.
      * @param writer
      *            The writer to use.
+     * @return a set of connector ids with state changes
      * @throws IOException
      *             If the serialization fails.
      */
-    public void write(UI ui, Writer writer) throws IOException {
+    public Set<String> write(UI ui, Writer writer) throws IOException {
 
         Collection<ClientConnector> dirtyVisibleConnectors = ui
                 .getConnectorTracker().getDirtyVisibleConnectors();
 
+        Set<String> writtenConnectors = new HashSet<String>();
         JsonObject sharedStates = Json.createObject();
         for (ClientConnector connector : dirtyVisibleConnectors) {
             // encode and send shared state
+            String connectorId = connector.getConnectorId();
             try {
                 JsonObject stateJson = connector.encodeState();
 
                 if (stateJson != null && stateJson.keys().length != 0) {
-                    sharedStates.put(connector.getConnectorId(), stateJson);
+                    sharedStates.put(connectorId, stateJson);
+                    writtenConnectors.add(connectorId);
                 }
             } catch (JsonException e) {
                 throw new PaintException(
                         "Failed to serialize shared state for connector "
                                 + connector.getClass().getName() + " ("
-                                + connector.getConnectorId() + "): "
-                                + e.getMessage(), e);
+                                + connectorId + "): " + e.getMessage(), e);
             }
         }
         writer.write(JsonUtil.stringify(sharedStates));
+
+        return writtenConnectors;
     }
 }
index 25b1bdaaf91c7bc643e266fb9878266ce45113e4..b117cb4b4d7cbd3873eed1e5edb80bbeab99382e 100644 (file)
@@ -159,7 +159,8 @@ public class UidlWriter implements Serializable {
             // processing.
 
             writer.write("\"state\":");
-            new SharedStateWriter().write(ui, writer);
+            Set<String> stateUpdateConnectors = new SharedStateWriter().write(
+                    ui, writer);
             writer.write(", "); // close states
 
             // TODO This should be optimized. The type only needs to be
@@ -179,7 +180,8 @@ public class UidlWriter implements Serializable {
             // child to 0 children)
 
             writer.write("\"hierarchy\":");
-            new ConnectorHierarchyWriter().write(ui, writer);
+            new ConnectorHierarchyWriter().write(ui, writer,
+                    stateUpdateConnectors);
             writer.write(", "); // close hierarchy
 
             // send server to client RPC calls for components in the UI, in call