From 727dc542346f12704d327708a78b185cd06f0e1d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Leif=20=C3=85strand?= Date: Mon, 3 Sep 2012 16:25:31 +0300 Subject: [PATCH] Don't mark connector as dirty when writing response (#9444, #9452) --- .../server/AbstractClientConnector.java | 5 +- .../server/AbstractCommunicationManager.java | 688 +++++++++--------- .../src/com/vaadin/ui/ConnectorTracker.java | 47 +- 3 files changed, 403 insertions(+), 337 deletions(-) diff --git a/server/src/com/vaadin/server/AbstractClientConnector.java b/server/src/com/vaadin/server/AbstractClientConnector.java index 2f660a443d..09e366260b 100644 --- a/server/src/com/vaadin/server/AbstractClientConnector.java +++ b/server/src/com/vaadin/server/AbstractClientConnector.java @@ -150,8 +150,9 @@ public abstract class AbstractClientConnector implements ClientConnector { } UI uI = getUI(); - if (uI != null && !uI.getConnectorTracker().isDirty(this)) { - requestRepaint(); + if (uI != null && !uI.getConnectorTracker().isWritingResponse() + && !uI.getConnectorTracker().isDirty(this)) { + markAsDirty(); } return sharedState; diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java index 2e42f51249..02d0b0d791 100644 --- a/server/src/com/vaadin/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java @@ -802,407 +802,427 @@ public abstract class AbstractCommunicationManager implements Serializable { requireLocale(application.getLocale().toString()); } - dirtyVisibleConnectors - .addAll(getDirtyVisibleConnectors(uiConnectorTracker)); - - getLogger().log( - Level.FINE, - "Found " + dirtyVisibleConnectors.size() - + " dirty connectors to paint"); - for (ClientConnector connector : dirtyVisibleConnectors) { - boolean initialized = uiConnectorTracker - .isClientSideInitialized(connector); - connector.beforeClientResponse(!initialized); - } - - outWriter.print("\"changes\":["); - - List invalidComponentRelativeSizes = null; - - JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, - !repaintAll); - legacyPaint(paintTarget, dirtyVisibleConnectors); - - if (analyzeLayouts) { - invalidComponentRelativeSizes = ComponentSizeValidator - .validateComponentRelativeSizes(ui.getContent(), null, null); - - // Also check any existing subwindows - if (ui.getWindows() != null) { - for (Window subWindow : ui.getWindows()) { - invalidComponentRelativeSizes = ComponentSizeValidator - .validateComponentRelativeSizes( - subWindow.getContent(), - invalidComponentRelativeSizes, null); + uiConnectorTracker.setWritingResponse(true); + try { + + dirtyVisibleConnectors + .addAll(getDirtyVisibleConnectors(uiConnectorTracker)); + + getLogger().log( + Level.FINE, + "Found " + dirtyVisibleConnectors.size() + + " dirty connectors to paint"); + for (ClientConnector connector : dirtyVisibleConnectors) { + boolean initialized = uiConnectorTracker + .isClientSideInitialized(connector); + connector.beforeClientResponse(!initialized); + } + + outWriter.print("\"changes\":["); + + List invalidComponentRelativeSizes = null; + + JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, + !repaintAll); + legacyPaint(paintTarget, dirtyVisibleConnectors); + + if (analyzeLayouts) { + invalidComponentRelativeSizes = ComponentSizeValidator + .validateComponentRelativeSizes(ui.getContent(), null, + null); + + // Also check any existing subwindows + if (ui.getWindows() != null) { + for (Window subWindow : ui.getWindows()) { + invalidComponentRelativeSizes = ComponentSizeValidator + .validateComponentRelativeSizes( + subWindow.getContent(), + invalidComponentRelativeSizes, null); + } } } - } - paintTarget.close(); - outWriter.print("], "); // close changes + paintTarget.close(); + outWriter.print("], "); // close changes - // send shared state to client + // send shared state to client - // for now, send the complete state of all modified and new - // components + // for now, send the complete state of all modified and new + // components - // Ideally, all this would be sent before "changes", but that causes - // complications with legacy components that create sub-components - // in their paint phase. Nevertheless, this will be processed on the - // client after component creation but before legacy UIDL - // processing. - JSONObject sharedStates = new JSONObject(); - for (ClientConnector connector : dirtyVisibleConnectors) { - // encode and send shared state - try { - JSONObject stateJson = connector.encodeState(); + // Ideally, all this would be sent before "changes", but that causes + // complications with legacy components that create sub-components + // in their paint phase. Nevertheless, this will be processed on the + // client after component creation but before legacy UIDL + // processing. + JSONObject sharedStates = new JSONObject(); + for (ClientConnector connector : dirtyVisibleConnectors) { + // encode and send shared state + try { + JSONObject stateJson = connector.encodeState(); - if (stateJson != null && stateJson.length() != 0) { - sharedStates.put(connector.getConnectorId(), stateJson); + if (stateJson != null && stateJson.length() != 0) { + sharedStates.put(connector.getConnectorId(), stateJson); + } + } catch (JSONException e) { + throw new PaintException( + "Failed to serialize shared state for connector " + + connector.getClass().getName() + " (" + + connector.getConnectorId() + "): " + + e.getMessage(), e); } - } catch (JSONException e) { - throw new PaintException( - "Failed to serialize shared state for connector " - + connector.getClass().getName() + " (" - + connector.getConnectorId() + "): " - + e.getMessage(), e); } - } - outWriter.print("\"state\":"); - outWriter.append(sharedStates.toString()); - outWriter.print(", "); // close states + outWriter.print("\"state\":"); + outWriter.append(sharedStates.toString()); + outWriter.print(", "); // close states - // TODO This should be optimized. The type only needs to be - // sent once for each connector id + on refresh. Use the same cache as - // widget mapping + // TODO This should be optimized. The type only needs to be + // sent once for each connector id + on refresh. Use the same cache + // as + // widget mapping - JSONObject connectorTypes = new JSONObject(); - for (ClientConnector connector : dirtyVisibleConnectors) { - String connectorType = paintTarget.getTag(connector); - try { - connectorTypes.put(connector.getConnectorId(), connectorType); - } catch (JSONException e) { - throw new PaintException( - "Failed to send connector type for connector " - + connector.getConnectorId() + ": " - + e.getMessage(), e); + JSONObject connectorTypes = new JSONObject(); + for (ClientConnector connector : dirtyVisibleConnectors) { + String connectorType = paintTarget.getTag(connector); + try { + connectorTypes.put(connector.getConnectorId(), + connectorType); + } catch (JSONException e) { + throw new PaintException( + "Failed to send connector type for connector " + + connector.getConnectorId() + ": " + + e.getMessage(), e); + } } - } - outWriter.print("\"types\":"); - outWriter.append(connectorTypes.toString()); - outWriter.print(", "); // close states + outWriter.print("\"types\":"); + outWriter.append(connectorTypes.toString()); + outWriter.print(", "); // close states - // Send update hierarchy information to the client. + // Send update hierarchy information to the client. - // This could be optimized aswell to send only info if hierarchy has - // actually changed. Much like with the shared state. Note though - // that an empty hierarchy is information aswell (e.g. change from 1 - // child to 0 children) + // This could be optimized aswell to send only info if hierarchy has + // actually changed. Much like with the shared state. Note though + // that an empty hierarchy is information aswell (e.g. change from 1 + // child to 0 children) - outWriter.print("\"hierarchy\":"); + outWriter.print("\"hierarchy\":"); - JSONObject hierarchyInfo = new JSONObject(); - for (ClientConnector connector : dirtyVisibleConnectors) { - String connectorId = connector.getConnectorId(); - JSONArray children = new JSONArray(); + JSONObject hierarchyInfo = new JSONObject(); + for (ClientConnector connector : dirtyVisibleConnectors) { + String connectorId = connector.getConnectorId(); + JSONArray children = new JSONArray(); - for (ClientConnector child : AbstractClientConnector - .getAllChildrenIterable(connector)) { - if (isVisible(child)) { - children.put(child.getConnectorId()); + for (ClientConnector child : AbstractClientConnector + .getAllChildrenIterable(connector)) { + if (isVisible(child)) { + children.put(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); } } - try { - hierarchyInfo.put(connectorId, children); - } catch (JSONException e) { - throw new PaintException( - "Failed to send hierarchy information about " - + connectorId + " to the client: " - + e.getMessage(), e); - } - } - outWriter.append(hierarchyInfo.toString()); - outWriter.print(", "); // close hierarchy + outWriter.append(hierarchyInfo.toString()); + outWriter.print(", "); // close hierarchy - uiConnectorTracker.markAllConnectorsClean(); + uiConnectorTracker.markAllConnectorsClean(); - // send server to client RPC calls for components in the UI, in call - // order + // send server to client RPC calls for components in the UI, in call + // order - // collect RPC calls from components in the UI in the order in - // which they were performed, remove the calls from components + // collect RPC calls from components in the UI in the order in + // which they were performed, remove the calls from components - LinkedList rpcPendingQueue = new LinkedList( - dirtyVisibleConnectors); - List pendingInvocations = collectPendingRpcCalls(dirtyVisibleConnectors); + LinkedList rpcPendingQueue = new LinkedList( + dirtyVisibleConnectors); + List pendingInvocations = collectPendingRpcCalls(dirtyVisibleConnectors); - JSONArray rpcCalls = new JSONArray(); - for (ClientMethodInvocation invocation : pendingInvocations) { - // add invocation to rpcCalls - try { - JSONArray invocationJson = new JSONArray(); - invocationJson.put(invocation.getConnector().getConnectorId()); - invocationJson.put(invocation.getInterfaceName()); - invocationJson.put(invocation.getMethodName()); - JSONArray paramJson = new JSONArray(); - for (int i = 0; i < invocation.getParameterTypes().length; ++i) { - Type parameterType = invocation.getParameterTypes()[i]; - Object referenceParameter = null; - // TODO Use default values for RPC parameter types - // if (!JsonCodec.isInternalType(parameterType)) { - // try { - // referenceParameter = parameterType.newInstance(); - // } catch (Exception e) { - // logger.log(Level.WARNING, - // "Error creating reference object for parameter of type " - // + parameterType.getName()); - // } - // } - EncodeResult encodeResult = JsonCodec.encode( - invocation.getParameters()[i], referenceParameter, - parameterType, ui.getConnectorTracker()); - paramJson.put(encodeResult.getEncodedValue()); + JSONArray rpcCalls = new JSONArray(); + for (ClientMethodInvocation invocation : pendingInvocations) { + // add invocation to rpcCalls + try { + JSONArray invocationJson = new JSONArray(); + invocationJson.put(invocation.getConnector() + .getConnectorId()); + invocationJson.put(invocation.getInterfaceName()); + invocationJson.put(invocation.getMethodName()); + JSONArray paramJson = new JSONArray(); + for (int i = 0; i < invocation.getParameterTypes().length; ++i) { + Type parameterType = invocation.getParameterTypes()[i]; + Object referenceParameter = null; + // TODO Use default values for RPC parameter types + // if (!JsonCodec.isInternalType(parameterType)) { + // try { + // referenceParameter = parameterType.newInstance(); + // } catch (Exception e) { + // logger.log(Level.WARNING, + // "Error creating reference object for parameter of type " + // + parameterType.getName()); + // } + // } + EncodeResult encodeResult = JsonCodec.encode( + invocation.getParameters()[i], + referenceParameter, parameterType, + ui.getConnectorTracker()); + paramJson.put(encodeResult.getEncodedValue()); + } + invocationJson.put(paramJson); + rpcCalls.put(invocationJson); + } catch (JSONException e) { + throw new PaintException( + "Failed to serialize RPC method call parameters for connector " + + invocation.getConnector() + .getConnectorId() + " method " + + invocation.getInterfaceName() + "." + + invocation.getMethodName() + ": " + + e.getMessage(), e); } - invocationJson.put(paramJson); - rpcCalls.put(invocationJson); - } catch (JSONException e) { - throw new PaintException( - "Failed to serialize RPC method call parameters for connector " - + invocation.getConnector().getConnectorId() - + " method " + invocation.getInterfaceName() - + "." + invocation.getMethodName() + ": " - + e.getMessage(), e); - } - } + } - if (rpcCalls.length() > 0) { - outWriter.print("\"rpc\" : "); - outWriter.append(rpcCalls.toString()); - outWriter.print(", "); // close rpc - } + if (rpcCalls.length() > 0) { + outWriter.print("\"rpc\" : "); + outWriter.append(rpcCalls.toString()); + outWriter.print(", "); // close rpc + } - outWriter.print("\"meta\" : {"); - boolean metaOpen = false; + outWriter.print("\"meta\" : {"); + boolean metaOpen = false; - if (repaintAll) { - metaOpen = true; - outWriter.write("\"repaintAll\":true"); - if (analyzeLayouts) { - outWriter.write(", \"invalidLayouts\":"); - outWriter.write("["); - if (invalidComponentRelativeSizes != null) { - boolean first = true; - for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) { - if (!first) { - outWriter.write(","); - } else { - first = false; + if (repaintAll) { + metaOpen = true; + outWriter.write("\"repaintAll\":true"); + if (analyzeLayouts) { + outWriter.write(", \"invalidLayouts\":"); + outWriter.write("["); + if (invalidComponentRelativeSizes != null) { + boolean first = true; + for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) { + if (!first) { + outWriter.write(","); + } else { + first = false; + } + invalidLayout.reportErrors(outWriter, this, + System.err); } - invalidLayout.reportErrors(outWriter, this, System.err); } + outWriter.write("]"); } - outWriter.write("]"); - } - if (highlightedConnector != null) { - outWriter.write(", \"hl\":\""); - outWriter.write(highlightedConnector.getConnectorId()); - outWriter.write("\""); - highlightedConnector = null; - } - } - - SystemMessages ci = request.getDeploymentConfiguration() - .getSystemMessages(); - - // meta instruction for client to enable auto-forward to - // sessionExpiredURL after timer expires. - if (ci != null && ci.getSessionExpiredMessage() == null - && ci.getSessionExpiredCaption() == null - && ci.isSessionExpiredNotificationEnabled()) { - int newTimeoutInterval = getTimeoutInterval(); - if (repaintAll || (timeoutInterval != newTimeoutInterval)) { - String escapedURL = ci.getSessionExpiredURL() == null ? "" : ci - .getSessionExpiredURL().replace("/", "\\/"); - if (metaOpen) { - outWriter.write(","); + if (highlightedConnector != null) { + outWriter.write(", \"hl\":\""); + outWriter.write(highlightedConnector.getConnectorId()); + outWriter.write("\""); + highlightedConnector = null; } - outWriter.write("\"timedRedirect\":{\"interval\":" - + (newTimeoutInterval + 15) + ",\"url\":\"" - + escapedURL + "\"}"); - metaOpen = true; } - timeoutInterval = newTimeoutInterval; - } - - outWriter.print("}, \"resources\" : {"); - // Precache custom layouts - - // TODO We should only precache the layouts that are not - // cached already (plagiate from usedPaintableTypes) - int resourceIndex = 0; - for (final Iterator i = paintTarget.getUsedResources() - .iterator(); i.hasNext();) { - final String resource = (String) i.next(); - InputStream is = null; - try { - is = getThemeResourceAsStream(ui, getTheme(ui), resource); - } catch (final Exception e) { - // FIXME: Handle exception - getLogger().log(Level.FINER, - "Failed to get theme resource stream.", e); + SystemMessages ci = request.getDeploymentConfiguration() + .getSystemMessages(); + + // meta instruction for client to enable auto-forward to + // sessionExpiredURL after timer expires. + if (ci != null && ci.getSessionExpiredMessage() == null + && ci.getSessionExpiredCaption() == null + && ci.isSessionExpiredNotificationEnabled()) { + int newTimeoutInterval = getTimeoutInterval(); + if (repaintAll || (timeoutInterval != newTimeoutInterval)) { + String escapedURL = ci.getSessionExpiredURL() == null ? "" + : ci.getSessionExpiredURL().replace("/", "\\/"); + if (metaOpen) { + outWriter.write(","); + } + outWriter.write("\"timedRedirect\":{\"interval\":" + + (newTimeoutInterval + 15) + ",\"url\":\"" + + escapedURL + "\"}"); + metaOpen = true; + } + timeoutInterval = newTimeoutInterval; } - if (is != null) { - outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\"" - + resource + "\" : "); - final StringBuffer layout = new StringBuffer(); + outWriter.print("}, \"resources\" : {"); + + // Precache custom layouts + // TODO We should only precache the layouts that are not + // cached already (plagiate from usedPaintableTypes) + int resourceIndex = 0; + for (final Iterator i = paintTarget.getUsedResources() + .iterator(); i.hasNext();) { + final String resource = (String) i.next(); + InputStream is = null; try { - final InputStreamReader r = new InputStreamReader(is, - "UTF-8"); - final char[] buffer = new char[20000]; - int charsRead = 0; - while ((charsRead = r.read(buffer)) > 0) { - layout.append(buffer, 0, charsRead); - } - r.close(); - } catch (final java.io.IOException e) { + is = getThemeResourceAsStream(ui, getTheme(ui), resource); + } catch (final Exception e) { // FIXME: Handle exception - getLogger().log(Level.INFO, "Resource transfer failed", e); + getLogger().log(Level.FINER, + "Failed to get theme resource stream.", e); } - outWriter.print("\"" - + JsonPaintTarget.escapeJSON(layout.toString()) + "\""); - } else { - // FIXME: Handle exception - getLogger().severe("CustomLayout not found: " + resource); - } - } - outWriter.print("}"); + if (is != null) { - Collection> usedClientConnectors = paintTarget - .getUsedClientConnectors(); - boolean typeMappingsOpen = false; - ClientCache clientCache = getClientCache(ui); + outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\"" + + resource + "\" : "); + final StringBuffer layout = new StringBuffer(); - List> newConnectorTypes = new ArrayList>(); - - for (Class class1 : usedClientConnectors) { - if (clientCache.cache(class1)) { - // client does not know the mapping key for this type, send - // mapping to client - newConnectorTypes.add(class1); - - if (!typeMappingsOpen) { - typeMappingsOpen = true; - outWriter.print(", \"typeMappings\" : { "); + try { + final InputStreamReader r = new InputStreamReader(is, + "UTF-8"); + final char[] buffer = new char[20000]; + int charsRead = 0; + while ((charsRead = r.read(buffer)) > 0) { + layout.append(buffer, 0, charsRead); + } + r.close(); + } catch (final java.io.IOException e) { + // FIXME: Handle exception + getLogger().log(Level.INFO, "Resource transfer failed", + e); + } + outWriter.print("\"" + + JsonPaintTarget.escapeJSON(layout.toString()) + + "\""); } else { - outWriter.print(" , "); + // FIXME: Handle exception + getLogger().severe("CustomLayout not found: " + resource); } - String canonicalName = class1.getCanonicalName(); - outWriter.print("\""); - outWriter.print(canonicalName); - outWriter.print("\" : "); - outWriter.print(getTagForType(class1)); } - } - if (typeMappingsOpen) { - outWriter.print(" }"); - } + outWriter.print("}"); + + Collection> usedClientConnectors = paintTarget + .getUsedClientConnectors(); + boolean typeMappingsOpen = false; + ClientCache clientCache = getClientCache(ui); + + List> newConnectorTypes = new ArrayList>(); - boolean typeInheritanceMapOpen = false; - if (typeMappingsOpen) { - // send the whole type inheritance map if any new mappings for (Class class1 : usedClientConnectors) { - if (!ClientConnector.class.isAssignableFrom(class1 - .getSuperclass())) { - continue; - } - if (!typeInheritanceMapOpen) { - typeInheritanceMapOpen = true; - outWriter.print(", \"typeInheritanceMap\" : { "); - } else { - outWriter.print(" , "); + if (clientCache.cache(class1)) { + // client does not know the mapping key for this type, send + // mapping to client + newConnectorTypes.add(class1); + + if (!typeMappingsOpen) { + typeMappingsOpen = true; + outWriter.print(", \"typeMappings\" : { "); + } else { + outWriter.print(" , "); + } + String canonicalName = class1.getCanonicalName(); + outWriter.print("\""); + outWriter.print(canonicalName); + outWriter.print("\" : "); + outWriter.print(getTagForType(class1)); } - outWriter.print("\""); - outWriter.print(getTagForType(class1)); - outWriter.print("\" : "); - outWriter - .print(getTagForType((Class) class1 - .getSuperclass())); - } - if (typeInheritanceMapOpen) { - outWriter.print(" }"); } - } - - /* - * Ensure super classes come before sub classes to get script dependency - * order right. Sub class @JavaScript might assume that @JavaScript - * defined by super class is already loaded. - */ - Collections.sort(newConnectorTypes, new Comparator>() { - @Override - public int compare(Class o1, Class o2) { - // TODO optimize using Class.isAssignableFrom? - return hierarchyDepth(o1) - hierarchyDepth(o2); + if (typeMappingsOpen) { + outWriter.print(" }"); } - private int hierarchyDepth(Class type) { - if (type == Object.class) { - return 0; - } else { - return hierarchyDepth(type.getSuperclass()) + 1; + boolean typeInheritanceMapOpen = false; + if (typeMappingsOpen) { + // send the whole type inheritance map if any new mappings + for (Class class1 : usedClientConnectors) { + if (!ClientConnector.class.isAssignableFrom(class1 + .getSuperclass())) { + continue; + } + if (!typeInheritanceMapOpen) { + typeInheritanceMapOpen = true; + outWriter.print(", \"typeInheritanceMap\" : { "); + } else { + outWriter.print(" , "); + } + outWriter.print("\""); + outWriter.print(getTagForType(class1)); + outWriter.print("\" : "); + outWriter + .print(getTagForType((Class) class1 + .getSuperclass())); + } + if (typeInheritanceMapOpen) { + outWriter.print(" }"); } } - }); - List scriptDependencies = new ArrayList(); - List styleDependencies = new ArrayList(); + /* + * Ensure super classes come before sub classes to get script + * dependency order right. Sub class @JavaScript might assume that + * + * @JavaScript defined by super class is already loaded. + */ + Collections.sort(newConnectorTypes, new Comparator>() { + @Override + public int compare(Class o1, Class o2) { + // TODO optimize using Class.isAssignableFrom? + return hierarchyDepth(o1) - hierarchyDepth(o2); + } - for (Class class1 : newConnectorTypes) { - JavaScript jsAnnotation = class1.getAnnotation(JavaScript.class); - if (jsAnnotation != null) { - for (String resource : jsAnnotation.value()) { - scriptDependencies.add(registerResource(resource, class1)); + private int hierarchyDepth(Class type) { + if (type == Object.class) { + return 0; + } else { + return hierarchyDepth(type.getSuperclass()) + 1; + } + } + }); + + List scriptDependencies = new ArrayList(); + List styleDependencies = new ArrayList(); + + for (Class class1 : newConnectorTypes) { + JavaScript jsAnnotation = class1 + .getAnnotation(JavaScript.class); + if (jsAnnotation != null) { + for (String resource : jsAnnotation.value()) { + scriptDependencies.add(registerResource(resource, + class1)); + } } - } - StyleSheet styleAnnotation = class1.getAnnotation(StyleSheet.class); - if (styleAnnotation != null) { - for (String resource : styleAnnotation.value()) { - styleDependencies.add(registerResource(resource, class1)); + StyleSheet styleAnnotation = class1 + .getAnnotation(StyleSheet.class); + if (styleAnnotation != null) { + for (String resource : styleAnnotation.value()) { + styleDependencies + .add(registerResource(resource, class1)); + } } } - } - // Include script dependencies in output if there are any - if (!scriptDependencies.isEmpty()) { - outWriter.print(", \"scriptDependencies\": " - + new JSONArray(scriptDependencies).toString()); - } + // Include script dependencies in output if there are any + if (!scriptDependencies.isEmpty()) { + outWriter.print(", \"scriptDependencies\": " + + new JSONArray(scriptDependencies).toString()); + } - // Include style dependencies in output if there are any - if (!styleDependencies.isEmpty()) { - outWriter.print(", \"styleDependencies\": " - + new JSONArray(styleDependencies).toString()); - } + // Include style dependencies in output if there are any + if (!styleDependencies.isEmpty()) { + outWriter.print(", \"styleDependencies\": " + + new JSONArray(styleDependencies).toString()); + } - // add any pending locale definitions requested by the client - printLocaleDeclarations(outWriter); + // add any pending locale definitions requested by the client + printLocaleDeclarations(outWriter); - if (dragAndDropService != null) { - dragAndDropService.printJSONResponse(outWriter); - } + if (dragAndDropService != null) { + dragAndDropService.printJSONResponse(outWriter); + } - for (ClientConnector connector : dirtyVisibleConnectors) { - uiConnectorTracker.markClientSideInitialized(connector); - } + for (ClientConnector connector : dirtyVisibleConnectors) { + uiConnectorTracker.markClientSideInitialized(connector); + } - assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended."; + assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended."; - writePerformanceData(outWriter); + writePerformanceData(outWriter); + } finally { + uiConnectorTracker.setWritingResponse(false); + } } public static JSONObject encodeState(ClientConnector connector, diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java index 3140c26525..c84b75ca51 100644 --- a/server/src/com/vaadin/ui/ConnectorTracker.java +++ b/server/src/com/vaadin/ui/ConnectorTracker.java @@ -56,6 +56,8 @@ public class ConnectorTracker implements Serializable { private Set dirtyConnectors = new HashSet(); private Set uninitializedConnectors = new HashSet(); + private boolean writingResponse = false; + private UI uI; private Map diffStates = new HashMap(); @@ -274,14 +276,21 @@ public class ConnectorTracker implements Serializable { } /** - * Mark the connector as dirty. + * Mark the connector as dirty. This should not be done while the response + * is being written. * * @see #getDirtyConnectors() + * @see #isWritingResponse() * * @param connector * The connector that should be marked clean. */ public void markDirty(ClientConnector connector) { + if (isWritingResponse()) { + throw new IllegalStateException( + "A connector should not be marked as dirty while a response is being written."); + } + if (getLogger().isLoggable(Level.FINE)) { if (!dirtyConnectors.contains(connector)) { getLogger().fine( @@ -412,4 +421,40 @@ public class ConnectorTracker implements Serializable { return dirtyConnectors.contains(connector); } + /** + * Checks whether the response is currently being written. Connectors can + * not be marked as dirty when a response is being written. + * + * @see #setWritingResponse(boolean) + * @see #markDirty(ClientConnector) + * + * @return true if the response is currently being written, + * false if outside the response writing phase. + */ + public boolean isWritingResponse() { + return writingResponse; + } + + /** + * Sets the current response write status. Connectors can not be marked as + * dirty when the response is written. + * + * @param writingResponse + * the new response status. + * + * @see #markDirty(ClientConnector) + * @see #isWritingResponse() + * + * @throws IllegalArgumentException + * if the new response status is the same as the previous value. + * This is done to help detecting problems caused by missed + * invocations of this method. + */ + public void setWritingResponse(boolean writingResponse) { + if (this.writingResponse == writingResponse) { + throw new IllegalArgumentException( + "The old value is same as the new value"); + } + this.writingResponse = writingResponse; + } } -- 2.39.5