]> source.dussan.org Git - vaadin-framework.git/commitdiff
Don't mark connector as dirty when writing response (#9444, #9452)
authorLeif Åstrand <leif@vaadin.com>
Mon, 3 Sep 2012 13:25:31 +0000 (16:25 +0300)
committerLeif Åstrand <leif@vaadin.com>
Mon, 3 Sep 2012 13:27:46 +0000 (16:27 +0300)
server/src/com/vaadin/server/AbstractClientConnector.java
server/src/com/vaadin/server/AbstractCommunicationManager.java
server/src/com/vaadin/ui/ConnectorTracker.java

index 2f660a443d83375e3a29e93b624623cfe0face5e..09e366260ba20351d2f3faae7e94c478de6f9919 100644 (file)
@@ -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;
index 2e42f51249021d09853f5aea364e2c5069b31dcd..02d0b0d791c2c01c7b8e6d5c71adaaed20e72ded 100644 (file)
@@ -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<InvalidLayout> 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<InvalidLayout> 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<ClientConnector> rpcPendingQueue = new LinkedList<ClientConnector>(
-                dirtyVisibleConnectors);
-        List<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(dirtyVisibleConnectors);
+            LinkedList<ClientConnector> rpcPendingQueue = new LinkedList<ClientConnector>(
+                    dirtyVisibleConnectors);
+            List<ClientMethodInvocation> 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<Object> 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<Object> 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<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
-                .getUsedClientConnectors();
-        boolean typeMappingsOpen = false;
-        ClientCache clientCache = getClientCache(ui);
+                    outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
+                            + resource + "\" : ");
+                    final StringBuffer layout = new StringBuffer();
 
-        List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
-
-        for (Class<? extends ClientConnector> 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<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
+                    .getUsedClientConnectors();
+            boolean typeMappingsOpen = false;
+            ClientCache clientCache = getClientCache(ui);
+
+            List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
 
-        boolean typeInheritanceMapOpen = false;
-        if (typeMappingsOpen) {
-            // send the whole type inheritance map if any new mappings
             for (Class<? extends ClientConnector> 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<? extends ClientConnector>) 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<Class<?>>() {
-            @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<? extends ClientConnector> 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<? extends ClientConnector>) class1
+                                    .getSuperclass()));
+                }
+                if (typeInheritanceMapOpen) {
+                    outWriter.print(" }");
                 }
             }
-        });
 
-        List<String> scriptDependencies = new ArrayList<String>();
-        List<String> styleDependencies = new ArrayList<String>();
+            /*
+             * 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<Class<?>>() {
+                @Override
+                public int compare(Class<?> o1, Class<?> o2) {
+                    // TODO optimize using Class.isAssignableFrom?
+                    return hierarchyDepth(o1) - hierarchyDepth(o2);
+                }
 
-        for (Class<? extends ClientConnector> 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<String> scriptDependencies = new ArrayList<String>();
+            List<String> styleDependencies = new ArrayList<String>();
+
+            for (Class<? extends ClientConnector> 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,
index 3140c26525ce5a42f8946cce3f140c16728543f8..c84b75ca514e846c188cf5337711c7c70aba9985 100644 (file)
@@ -56,6 +56,8 @@ public class ConnectorTracker implements Serializable {
     private Set<ClientConnector> dirtyConnectors = new HashSet<ClientConnector>();
     private Set<ClientConnector> uninitializedConnectors = new HashSet<ClientConnector>();
 
+    private boolean writingResponse = false;
+
     private UI uI;
     private Map<ClientConnector, Object> diffStates = new HashMap<ClientConnector, Object>();
 
@@ -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 <code>true</code> if the response is currently being written,
+     *         <code>false</code> 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;
+    }
 }