From: Leif Åstrand Date: Sat, 25 Jul 2015 09:14:30 +0000 (+0300) Subject: Omit empty hierarchy data from the response (#18510) X-Git-Tag: 7.6.0.beta2~26 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=39c7d9f68a3723feeebf3b05646aaef2fe3ce070;p=vaadin-framework.git Omit empty hierarchy data from the response (#18510) 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 --- diff --git a/client/src/com/vaadin/client/communication/MessageHandler.java b/client/src/com/vaadin/client/communication/MessageHandler.java index e87928fc57..7ff8820563 100644 --- a/client/src/com/vaadin/client/communication/MessageHandler.java +++ b/client/src/com/vaadin/client/communication/MessageHandler.java @@ -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 newChildren = new ArrayList(); - List newComponents = new ArrayList(); - 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 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 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 newChildren = new ArrayList(); + List newComponents = new ArrayList(); + 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 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 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 events, FastStringSet detachedConnectors) { diff --git a/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java index 1c1a220b5d..503bf8c0ae 100644 --- a/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java +++ b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java @@ -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 stateUpdateConnectors) + throws IOException { Collection 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)); diff --git a/server/src/com/vaadin/server/communication/SharedStateWriter.java b/server/src/com/vaadin/server/communication/SharedStateWriter.java index 6ef02955f7..06b59ad4cc 100644 --- a/server/src/com/vaadin/server/communication/SharedStateWriter.java +++ b/server/src/com/vaadin/server/communication/SharedStateWriter.java @@ -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 write(UI ui, Writer writer) throws IOException { Collection dirtyVisibleConnectors = ui .getConnectorTracker().getDirtyVisibleConnectors(); + Set writtenConnectors = new HashSet(); 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; } } diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java index 25b1bdaaf9..b117cb4b4d 100644 --- a/server/src/com/vaadin/server/communication/UidlWriter.java +++ b/server/src/com/vaadin/server/communication/UidlWriter.java @@ -159,7 +159,8 @@ public class UidlWriter implements Serializable { // processing. writer.write("\"state\":"); - new SharedStateWriter().write(ui, writer); + Set 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