Browse Source

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
tags/7.6.0.beta2
Leif Åstrand 8 years ago
parent
commit
39c7d9f68a

+ 166
- 123
client/src/com/vaadin/client/communication/MessageHandler.java View 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) {

+ 17
- 8
server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java View 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));

+ 11
- 4
server/src/com/vaadin/server/communication/SharedStateWriter.java View 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;
}
}

+ 4
- 2
server/src/com/vaadin/server/communication/UidlWriter.java View 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

Loading…
Cancel
Save