From a75c42b0641059aca186a26cf648e303f34f6a82 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 21 Jun 2012 10:35:38 +0300 Subject: [PATCH] Moved connector tracking from Application to Root This should fix issues with connectors being detached and reattached and also make the connector map cleanup method unnecessary as long as AbstractConnect.attach and detach always are called (#8943) --- src/com/vaadin/Application.java | 53 +--- .../terminal/AbstractClientConnector.java | 7 +- .../server/AbstractApplicationPortlet.java | 2 +- .../server/AbstractApplicationServlet.java | 15 +- .../server/AbstractCommunicationManager.java | 69 +++--- .../gwt/server/CommunicationManager.java | 18 +- .../vaadin/terminal/gwt/server/Constants.java | 2 +- .../vaadin/terminal/gwt/server/JsonCodec.java | 142 ++++++----- .../server/PortletCommunicationManager.java | 6 +- src/com/vaadin/ui/ConnectorTracker.java | 229 ++++++++++++++++++ src/com/vaadin/ui/DirtyConnectorTracker.java | 113 --------- src/com/vaadin/ui/Root.java | 7 +- 12 files changed, 378 insertions(+), 285 deletions(-) create mode 100644 src/com/vaadin/ui/ConnectorTracker.java delete mode 100644 src/com/vaadin/ui/DirtyConnectorTracker.java diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java index dbf71408a9..3da314a11e 100644 --- a/src/com/vaadin/Application.java +++ b/src/com/vaadin/Application.java @@ -18,7 +18,6 @@ import java.util.EventObject; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; -import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; @@ -48,14 +47,12 @@ import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; -import com.vaadin.ui.Component; import com.vaadin.ui.Root; import com.vaadin.ui.Table; import com.vaadin.ui.Window; @@ -2359,8 +2356,6 @@ public class Application implements Terminal.ErrorListener, Serializable { return Collections.unmodifiableCollection(roots.values()); } - private final HashMap connectorIdToConnector = new HashMap(); - private int connectorIdSequence = 0; /** @@ -2372,53 +2367,7 @@ public class Application implements Terminal.ErrorListener, Serializable { * @return A new id for the connector */ public String createConnectorId(ClientConnector connector) { - String connectorId = String.valueOf(connectorIdSequence++); - Connector oldReference = connectorIdToConnector.put(connectorId, - connector); - if (oldReference != null) { - throw new RuntimeException( - "An error occured while generating connector ids. A connector with id " - + connectorId + " was already found!"); - } - return connectorId; - } - - /** - * Gets a connector by its id. - * - * @param connectorId - * The connector id to look for - * @return The connector with the given id or null if no connector has the - * given id - */ - public ClientConnector getConnector(String connectorId) { - return connectorIdToConnector.get(connectorId); - } - - /** - * Cleans the connector map from all connectors that are no longer attached - * to the application. This should only be called by the framework. - */ - public void cleanConnectorMap() { - // remove detached components from paintableIdMap so they - // can be GC'ed - Iterator iterator = connectorIdToConnector.keySet().iterator(); - - while (iterator.hasNext()) { - String connectorId = iterator.next(); - Connector connector = connectorIdToConnector.get(connectorId); - if (connector instanceof Component) { - Component component = (Component) connector; - if (component.getApplication() != this) { - // If component is no longer part of this application, - // remove it from the map. If it is re-attached to the - // application at some point it will be re-added to this - // collection when sent to the client. - iterator.remove(); - } - } - } - + return String.valueOf(connectorIdSequence++); } private static final Logger getLogger() { diff --git a/src/com/vaadin/terminal/AbstractClientConnector.java b/src/com/vaadin/terminal/AbstractClientConnector.java index 6a87f58c71..9de444d70e 100644 --- a/src/com/vaadin/terminal/AbstractClientConnector.java +++ b/src/com/vaadin/terminal/AbstractClientConnector.java @@ -72,7 +72,7 @@ public abstract class AbstractClientConnector implements ClientConnector { public void requestRepaint() { Root root = getRoot(); if (root != null) { - root.getDirtyConnectorTracker().markDirty(this); + root.getConnectorTracker().markDirty(this); } } @@ -455,9 +455,12 @@ public abstract class AbstractClientConnector implements ClientConnector { public void attach() { requestRepaint(); + getRoot().getConnectorTracker().registerConnector(this); + for (ClientConnector connector : getAllChildrenIterable(this)) { connector.attach(); } + } /** @@ -472,6 +475,8 @@ public abstract class AbstractClientConnector implements ClientConnector { for (ClientConnector connector : getAllChildrenIterable(this)) { connector.detach(); } + + getRoot().getConnectorTracker().unregisterConnector(this); } public boolean isConnectorEnabled() { diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index bf29144cc1..b7be6fd394 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -638,7 +638,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(wrappedRequest, + applicationManager.handleFileUpload(root, wrappedRequest, wrappedResponse); return; } else if (requestType == RequestType.BROWSER_DETAILS) { diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 2179761d31..0e548f61c8 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -421,25 +421,28 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(application, request, - response); + Root root = application.getRootForRequest(request); + if (root == null) { + throw new ServletException(ERROR_NO_ROOT_FOUND); + } + applicationManager.handleFileUpload(root, request, response); return; } else if (requestType == RequestType.UIDL) { - // Handles AJAX UIDL requests Root root = application.getRootForRequest(request); if (root == null) { - throw new ServletException(ERROR_NO_WINDOW_FOUND); - } + throw new ServletException(ERROR_NO_ROOT_FOUND); + }// Handles AJAX UIDL requests applicationManager.handleUidlRequest(request, response, servletWrapper, root); return; } else if (requestType == RequestType.BROWSER_DETAILS) { + // Browser details - not related to a specific root applicationManager.handleBrowserDetailsRequest(request, response, application); return; } - // Removes application if it has stopped (mayby by thread or + // Removes application if it has stopped (maybe by thread or // transactionlistener) if (!application.isRunning()) { endApplication(request, response, application); diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index ecd4fdd1ad..797f8f756c 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -74,7 +74,7 @@ import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.Component; -import com.vaadin.ui.DirtyConnectorTracker; +import com.vaadin.ui.ConnectorTracker; import com.vaadin.ui.HasComponents; import com.vaadin.ui.Root; import com.vaadin.ui.Window; @@ -134,6 +134,9 @@ public abstract class AbstractCommunicationManager implements Serializable { private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts"; + /** + * The application this communication manager is used for + */ private final Application application; private List locales; @@ -517,7 +520,8 @@ public abstract class AbstractCommunicationManager implements Serializable { if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) { String pid = request .getParameter(GET_PARAM_HIGHLIGHT_COMPONENT); - highlightedConnector = root.getApplication().getConnector(pid); + highlightedConnector = root.getConnectorTracker().getConnector( + pid); highlightConnector(highlightedConnector); } } @@ -617,7 +621,7 @@ public abstract class AbstractCommunicationManager implements Serializable { protected void postPaint(Root root) { // Remove connectors that have been detached from the application during // handling of the request - root.getApplication().cleanConnectorMap(); + root.getConnectorTracker().cleanConnectorMap(); } protected void highlightConnector(Connector highlightedConnector) { @@ -763,8 +767,7 @@ public abstract class AbstractCommunicationManager implements Serializable { ArrayList dirtyVisibleConnectors = new ArrayList(); Application application = root.getApplication(); // Paints components - DirtyConnectorTracker rootConnectorTracker = root - .getDirtyConnectorTracker(); + ConnectorTracker rootConnectorTracker = root.getConnectorTracker(); getLogger().log(Level.FINE, "* Creating response to client"); if (repaintAll) { getClientCache(root).clear(); @@ -848,7 +851,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } } Object stateJson = JsonCodec.encode(state, referenceState, - stateType, application); + stateType, root.getConnectorTracker()); sharedStates.put(connector.getConnectorId(), stateJson); } catch (JSONException e) { @@ -950,7 +953,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // } paramJson.put(JsonCodec.encode( invocation.getParameters()[i], referenceParameter, - parameterType, application)); + parameterType, root.getConnectorTracker())); } invocationJson.put(paramJson); rpcCalls.put(invocationJson); @@ -1411,7 +1414,7 @@ public abstract class AbstractCommunicationManager implements Serializable { for (int bi = 1; bi < bursts.length; bi++) { // unescape any encoded separator characters in the burst final String burst = unescapeBurst(bursts[bi]); - success &= handleBurst(request, application2, burst); + success &= handleBurst(request, root, burst); // In case that there were multiple bursts, we know that this is // a special synchronous case for closing window. Thus we are @@ -1453,22 +1456,23 @@ public abstract class AbstractCommunicationManager implements Serializable { * directly. * * @param source - * @param app - * application receiving the burst + * @param root + * the root receiving the burst * @param burst * the content of the burst as a String to be parsed * @return true if the processing of the burst was successful and there were * no messages to non-existent components */ - public boolean handleBurst(Object source, Application app, + public boolean handleBurst(WrappedRequest source, Root root, final String burst) { boolean success = true; try { Set enabledConnectors = new HashSet(); - List invocations = parseInvocations(burst); + List invocations = parseInvocations( + root.getConnectorTracker(), burst); for (MethodInvocation invocation : invocations) { - final ClientConnector connector = getConnector(app, + final ClientConnector connector = getConnector(root, invocation.getConnectorId()); if (connector != null && connector.isConnectorEnabled()) { @@ -1479,7 +1483,7 @@ public abstract class AbstractCommunicationManager implements Serializable { for (int i = 0; i < invocations.size(); i++) { MethodInvocation invocation = invocations.get(i); - final ClientConnector connector = getConnector(app, + final ClientConnector connector = getConnector(root, invocation.getConnectorId()); if (connector == null) { @@ -1557,8 +1561,8 @@ public abstract class AbstractCommunicationManager implements Serializable { errorComponent = (Component) dropHandlerOwner; } } - handleChangeVariablesError(app, errorComponent, e, - changes); + handleChangeVariablesError(root.getApplication(), + errorComponent, e, changes); } } } @@ -1577,12 +1581,15 @@ public abstract class AbstractCommunicationManager implements Serializable { * Parse a message burst from the client into a list of MethodInvocation * instances. * + * @param root + * The root for this request * @param burst * message string (JSON) * @return list of MethodInvocation to perform * @throws JSONException */ - private List parseInvocations(final String burst) + private List parseInvocations( + ConnectorTracker connectorTracker, final String burst) throws JSONException { JSONArray invocationsJson = new JSONArray(burst); @@ -1595,7 +1602,7 @@ public abstract class AbstractCommunicationManager implements Serializable { JSONArray invocationJson = invocationsJson.getJSONArray(i); MethodInvocation invocation = parseInvocation(invocationJson, - previousInvocation); + previousInvocation, connectorTracker); if (invocation != null) { // Can be null iff the invocation was a legacy invocation and it // was merged with the previous one @@ -1607,7 +1614,8 @@ public abstract class AbstractCommunicationManager implements Serializable { } private MethodInvocation parseInvocation(JSONArray invocationJson, - MethodInvocation previousInvocation) throws JSONException { + MethodInvocation previousInvocation, + ConnectorTracker connectorTracker) throws JSONException { String connectorId = invocationJson.getString(0); String interfaceName = invocationJson.getString(1); String methodName = invocationJson.getString(2); @@ -1623,10 +1631,10 @@ public abstract class AbstractCommunicationManager implements Serializable { return parseLegacyChangeVariablesInvocation(connectorId, interfaceName, methodName, (LegacyChangeVariablesInvocation) previousInvocation, - parametersJson); + parametersJson, connectorTracker); } else { return parseServerRpcInvocation(connectorId, interfaceName, - methodName, parametersJson); + methodName, parametersJson, connectorTracker); } } @@ -1634,7 +1642,8 @@ public abstract class AbstractCommunicationManager implements Serializable { private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation( String connectorId, String interfaceName, String methodName, LegacyChangeVariablesInvocation previousInvocation, - JSONArray parametersJson) throws JSONException { + JSONArray parametersJson, ConnectorTracker connectorTracker) + throws JSONException { if (parametersJson.length() != 2) { throw new JSONException( "Invalid parameters in legacy change variables call. Expected 2, was " @@ -1642,7 +1651,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } String variableName = parametersJson.getString(0); UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType( - UidlValue.class, true, parametersJson.get(1), application); + UidlValue.class, true, parametersJson.get(1), connectorTracker); Object value = uidlValue.getValue(); @@ -1658,7 +1667,8 @@ public abstract class AbstractCommunicationManager implements Serializable { private ServerRpcMethodInvocation parseServerRpcInvocation( String connectorId, String interfaceName, String methodName, - JSONArray parametersJson) throws JSONException { + JSONArray parametersJson, ConnectorTracker connectorTracker) + throws JSONException { ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation( connectorId, interfaceName, methodName, parametersJson.length()); @@ -1670,7 +1680,7 @@ public abstract class AbstractCommunicationManager implements Serializable { Object parameterValue = parametersJson.get(j); Type parameterType = declaredRpcMethodParameterTypes[j]; parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType, - parameterValue, application); + parameterValue, connectorTracker); } invocation.setParameters(parameters); return invocation; @@ -1681,8 +1691,9 @@ public abstract class AbstractCommunicationManager implements Serializable { owner.changeVariables(source, m); } - protected ClientConnector getConnector(Application app, String connectorId) { - ClientConnector c = app.getConnector(connectorId); + protected ClientConnector getConnector(Root root, String connectorId) { + ClientConnector c = root.getConnectorTracker() + .getConnector(connectorId); if (c == null && connectorId.equals(getDragAndDropService().getConnectorId())) { return getDragAndDropService(); @@ -2037,9 +2048,9 @@ public abstract class AbstractCommunicationManager implements Serializable { * @return */ private ArrayList getDirtyVisibleConnectors( - DirtyConnectorTracker dirtyConnectorTracker) { + ConnectorTracker connectorTracker) { ArrayList dirtyConnectors = new ArrayList(); - for (ClientConnector c : dirtyConnectorTracker.getDirtyConnectors()) { + for (ClientConnector c : connectorTracker.getDirtyConnectors()) { if (isVisible(c)) { dirtyConnectors.add(c); } diff --git a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java index 3dd2eb97fd..cc2981dc45 100644 --- a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java @@ -61,7 +61,8 @@ public class CommunicationManager extends AbstractCommunicationManager { /** * Handles file upload request submitted via Upload component. * - * @param application + * @param root + * The root for this request * * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) * @@ -70,9 +71,9 @@ public class CommunicationManager extends AbstractCommunicationManager { * @throws IOException * @throws InvalidUIDLSecurityKeyException */ - public void handleFileUpload(Application application, - WrappedRequest request, WrappedResponse response) - throws IOException, InvalidUIDLSecurityKeyException { + public void handleFileUpload(Root root, WrappedRequest request, + WrappedResponse response) throws IOException, + InvalidUIDLSecurityKeyException { /* * URI pattern: APP/UPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl @@ -86,14 +87,14 @@ public class CommunicationManager extends AbstractCommunicationManager { String uppUri = pathInfo.substring(startOfData); String[] parts = uppUri.split("/", 3); // 0 = pid, 1= name, 2 = sec key String variableName = parts[1]; - String paintableId = parts[0]; + String connectorId = parts[0]; StreamVariable streamVariable = pidToNameToStreamVariable.get( - paintableId).get(variableName); + connectorId).get(variableName); String secKey = streamVariableToSeckey.get(streamVariable); if (secKey.equals(parts[2])) { - Connector source = getConnector(application, paintableId); + Connector source = getConnector(root, connectorId); String contentType = request.getContentType(); if (contentType.contains("boundary")) { // Multipart requests contain boundary string @@ -117,13 +118,12 @@ public class CommunicationManager extends AbstractCommunicationManager { protected void postPaint(Root root) { super.postPaint(root); - Application application = root.getApplication(); if (pidToNameToStreamVariable != null) { Iterator iterator = pidToNameToStreamVariable.keySet() .iterator(); while (iterator.hasNext()) { String connectorId = iterator.next(); - if (application.getConnector(connectorId) == null) { + if (root.getConnectorTracker().getConnector(connectorId) == null) { // Owner is no longer attached to the application Map removed = pidToNameToStreamVariable .get(connectorId); diff --git a/src/com/vaadin/terminal/gwt/server/Constants.java b/src/com/vaadin/terminal/gwt/server/Constants.java index 7c467aa7f4..9e6b2c775b 100644 --- a/src/com/vaadin/terminal/gwt/server/Constants.java +++ b/src/com/vaadin/terminal/gwt/server/Constants.java @@ -68,7 +68,7 @@ public interface Constants { // Widget set parameter name static final String PARAMETER_WIDGETSET = "widgetset"; - static final String ERROR_NO_WINDOW_FOUND = "No window found. Did you remember to setMainWindow()?"; + static final String ERROR_NO_ROOT_FOUND = "Application did not return a root for the request and did not request extra information either. Something is wrong."; static final String DEFAULT_THEME_NAME = "reindeer"; diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index 71e4727164..4b470f7215 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -23,7 +23,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import com.vaadin.Application; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; @@ -31,6 +30,7 @@ import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; import com.vaadin.terminal.gwt.client.communication.UidlValue; import com.vaadin.ui.Component; +import com.vaadin.ui.ConnectorTracker; /** * Decoder for converting RPC parameters and other values from JSON in transfer @@ -105,16 +105,18 @@ public class JsonCodec implements Serializable { } public static Object decodeInternalOrCustomType(Type targetType, - Object value, Application application) throws JSONException { + Object value, ConnectorTracker connectorTracker) + throws JSONException { if (isInternalType(targetType)) { - return decodeInternalType(targetType, false, value, application); + return decodeInternalType(targetType, false, value, + connectorTracker); } else { - return decodeCustomType(targetType, value, application); + return decodeCustomType(targetType, value, connectorTracker); } } public static Object decodeCustomType(Type targetType, Object value, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (isInternalType(targetType)) { throw new JSONException("decodeCustomType cannot be used for " + targetType + ", which is an internal type"); @@ -127,7 +129,8 @@ public class JsonCodec implements Serializable { || targetType == JSONArray.class) { return value; } else { - return decodeObject(targetType, (JSONObject) value, application); + return decodeObject(targetType, (JSONObject) value, + connectorTracker); } } @@ -157,7 +160,7 @@ public class JsonCodec implements Serializable { */ public static Object decodeInternalType(Type targetType, boolean restrictToInternalTypes, Object encodedJsonValue, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (!isInternalType(targetType)) { throw new JSONException("Type " + targetType + " is not a supported internal type."); @@ -170,26 +173,27 @@ public class JsonCodec implements Serializable { // UidlValue if (targetType == UidlValue.class) { - return decodeUidlValue((JSONArray) encodedJsonValue, application); + return decodeUidlValue((JSONArray) encodedJsonValue, + connectorTracker); } // Collections if (JsonEncoder.VTYPE_LIST.equals(transportType)) { return decodeList(targetType, restrictToInternalTypes, - (JSONArray) encodedJsonValue, application); + (JSONArray) encodedJsonValue, connectorTracker); } else if (JsonEncoder.VTYPE_SET.equals(transportType)) { return decodeSet(targetType, restrictToInternalTypes, - (JSONArray) encodedJsonValue, application); + (JSONArray) encodedJsonValue, connectorTracker); } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) { return decodeMap(targetType, restrictToInternalTypes, - encodedJsonValue, application); + encodedJsonValue, connectorTracker); } // Arrays if (JsonEncoder.VTYPE_ARRAY.equals(transportType)) { return decodeObjectArray(targetType, (JSONArray) encodedJsonValue, - application); + connectorTracker); } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(transportType)) { return decodeStringArray((JSONArray) encodedJsonValue); @@ -200,7 +204,7 @@ public class JsonCodec implements Serializable { String stringValue = String.valueOf(encodedJsonValue); if (JsonEncoder.VTYPE_CONNECTOR.equals(transportType)) { - return application.getConnector(stringValue); + return connectorTracker.getConnector(stringValue); } // Standard Java types @@ -223,11 +227,11 @@ public class JsonCodec implements Serializable { } private static UidlValue decodeUidlValue(JSONArray encodedJsonValue, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { String type = encodedJsonValue.getString(0); Object decodedValue = decodeInternalType(getType(type), true, - encodedJsonValue.get(1), application); + encodedJsonValue.get(1), connectorTracker); return new UidlValue(decodedValue); } @@ -248,7 +252,7 @@ public class JsonCodec implements Serializable { private static Map decodeMap(Type targetType, boolean restrictToInternalTypes, Object jsonMap, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (jsonMap instanceof JSONArray) { // Client-side has no declared type information to determine // encoding method for empty maps, so these are handled separately. @@ -266,22 +270,22 @@ public class JsonCodec implements Serializable { .getActualTypeArguments()[1]; if (keyType == String.class) { return decodeStringMap(valueType, (JSONObject) jsonMap, - application); + connectorTracker); } else if (keyType == Connector.class) { return decodeConnectorMap(valueType, (JSONObject) jsonMap, - application); + connectorTracker); } else { return decodeObjectMap(keyType, valueType, (JSONArray) jsonMap, - application); + connectorTracker); } } else { return decodeStringMap(UidlValue.class, (JSONObject) jsonMap, - application); + connectorTracker); } } private static Map decodeObjectMap(Type keyType, - Type valueType, JSONArray jsonMap, Application application) + Type valueType, JSONArray jsonMap, ConnectorTracker connectorTracker) throws JSONException { Map map = new HashMap(); @@ -292,9 +296,9 @@ public class JsonCodec implements Serializable { for (int i = 0; i < keys.length(); i++) { Object key = decodeInternalOrCustomType(keyType, keys.get(i), - application); + connectorTracker); Object value = decodeInternalOrCustomType(valueType, values.get(i), - application); + connectorTracker); map.put(key, value); } @@ -303,30 +307,32 @@ public class JsonCodec implements Serializable { } private static Map decodeConnectorMap(Type valueType, - JSONObject jsonMap, Application application) throws JSONException { + JSONObject jsonMap, ConnectorTracker connectorTracker) + throws JSONException { Map map = new HashMap(); for (Iterator iter = jsonMap.keys(); iter.hasNext();) { String key = (String) iter.next(); Object value = decodeInternalOrCustomType(valueType, - jsonMap.get(key), application); + jsonMap.get(key), connectorTracker); if (valueType == UidlValue.class) { value = ((UidlValue) value).getValue(); } - map.put(application.getConnector(key), value); + map.put(connectorTracker.getConnector(key), value); } return map; } private static Map decodeStringMap(Type valueType, - JSONObject jsonMap, Application application) throws JSONException { + JSONObject jsonMap, ConnectorTracker connectorTracker) + throws JSONException { Map map = new HashMap(); for (Iterator iter = jsonMap.keys(); iter.hasNext();) { String key = (String) iter.next(); Object value = decodeInternalOrCustomType(valueType, - jsonMap.get(key), application); + jsonMap.get(key), connectorTracker); if (valueType == UidlValue.class) { value = ((UidlValue) value).getValue(); } @@ -349,17 +355,18 @@ public class JsonCodec implements Serializable { */ private static Object decodeParametrizedType(Type targetType, boolean restrictToInternalTypes, int typeIndex, Object value, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (!restrictToInternalTypes && targetType instanceof ParameterizedType) { Type childType = ((ParameterizedType) targetType) .getActualTypeArguments()[typeIndex]; // Only decode the given type - return decodeInternalOrCustomType(childType, value, application); + return decodeInternalOrCustomType(childType, value, + connectorTracker); } else { // Only UidlValue when not enforcing a given type to avoid security // issues UidlValue decodeInternalType = (UidlValue) decodeInternalType( - UidlValue.class, true, value, application); + UidlValue.class, true, value, connectorTracker); return decodeInternalType.getValue(); } } @@ -380,20 +387,21 @@ public class JsonCodec implements Serializable { } private static Object[] decodeObjectArray(Type targetType, - JSONArray jsonArray, Application application) throws JSONException { - List list = decodeList(List.class, true, jsonArray, application); + JSONArray jsonArray, ConnectorTracker connectorTracker) + throws JSONException { + List list = decodeList(List.class, true, jsonArray, connectorTracker); return list.toArray(new Object[list.size()]); } private static List decodeList(Type targetType, boolean restrictToInternalTypes, JSONArray jsonArray, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { List list = new ArrayList(); for (int i = 0; i < jsonArray.length(); ++i) { // each entry always has two elements: type and value Object encodedValue = jsonArray.get(i); Object decodedChild = decodeParametrizedType(targetType, - restrictToInternalTypes, 0, encodedValue, application); + restrictToInternalTypes, 0, encodedValue, connectorTracker); list.add(decodedChild); } return list; @@ -401,10 +409,10 @@ public class JsonCodec implements Serializable { private static Set decodeSet(Type targetType, boolean restrictToInternalTypes, JSONArray jsonArray, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { HashSet set = new HashSet(); set.addAll(decodeList(targetType, restrictToInternalTypes, jsonArray, - application)); + connectorTracker)); return set; } @@ -430,7 +438,7 @@ public class JsonCodec implements Serializable { } private static Object decodeObject(Type targetType, - JSONObject serializedObject, Application application) + JSONObject serializedObject, ConnectorTracker connectorTracker) throws JSONException { Class targetClass = getClassForType(targetType); @@ -451,7 +459,7 @@ public class JsonCodec implements Serializable { Object encodedFieldValue = serializedObject.get(fieldName); Type fieldType = pd.getReadMethod().getGenericReturnType(); Object decodedFieldValue = decodeInternalOrCustomType( - fieldType, encodedFieldValue, application); + fieldType, encodedFieldValue, connectorTracker); pd.getWriteMethod().invoke(decodedObject, decodedFieldValue); } @@ -471,7 +479,8 @@ public class JsonCodec implements Serializable { } public static Object encode(Object value, Object referenceValue, - Type valueType, Application application) throws JSONException { + Type valueType, ConnectorTracker connectorTracker) + throws JSONException { if (valueType == null) { throw new IllegalArgumentException("type must be defined"); @@ -497,15 +506,15 @@ public class JsonCodec implements Serializable { } else if (value instanceof Collection) { Collection collection = (Collection) value; JSONArray jsonArray = encodeCollection(valueType, collection, - application); + connectorTracker); return jsonArray; } else if (value instanceof Object[]) { Object[] array = (Object[]) value; - JSONArray jsonArray = encodeArrayContents(array, application); + JSONArray jsonArray = encodeArrayContents(array, connectorTracker); return jsonArray; } else if (value instanceof Map) { Object jsonMap = encodeMap(valueType, (Map) value, - application); + connectorTracker); return jsonMap; } else if (value instanceof Connector) { Connector connector = (Connector) value; @@ -516,13 +525,13 @@ public class JsonCodec implements Serializable { } return connector.getConnectorId(); } else if (value instanceof Enum) { - return encodeEnum((Enum) value, application); + return encodeEnum((Enum) value, connectorTracker); } else if (value instanceof JSONArray || value instanceof JSONObject) { return value; } else { // Any object that we do not know how to encode we encode by looping // through fields - return encodeObject(value, referenceValue, application); + return encodeObject(value, referenceValue, connectorTracker); } } @@ -531,7 +540,7 @@ public class JsonCodec implements Serializable { } private static Object encodeObject(Object value, Object referenceValue, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); try { @@ -565,7 +574,7 @@ public class JsonCodec implements Serializable { jsonMap.put( fieldName, encode(fieldValue, referenceFieldValue, fieldType, - application)); + connectorTracker)); // } else { // System.out.println("Skipping field " + fieldName // + " of type " + fieldType.getName() @@ -599,44 +608,44 @@ public class JsonCodec implements Serializable { return false; } - private static String encodeEnum(Enum e, Application application) - throws JSONException { + private static String encodeEnum(Enum e, + ConnectorTracker connectorTracker) throws JSONException { return e.name(); } private static JSONArray encodeArrayContents(Object[] array, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : array) { - jsonArray.put(encode(o, null, null, application)); + jsonArray.put(encode(o, null, null, connectorTracker)); } return jsonArray; } private static JSONArray encodeCollection(Type targetType, - Collection collection, Application application) + Collection collection, ConnectorTracker connectorTracker) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : collection) { - jsonArray.put(encodeChild(targetType, 0, o, application)); + jsonArray.put(encodeChild(targetType, 0, o, connectorTracker)); } return jsonArray; } private static Object encodeChild(Type targetType, int typeIndex, Object o, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (targetType instanceof ParameterizedType) { Type childType = ((ParameterizedType) targetType) .getActualTypeArguments()[typeIndex]; // Encode using the given type - return encode(o, null, childType, application); + return encode(o, null, childType, connectorTracker); } else { throw new JSONException("Collection is missing generics"); } } private static Object encodeMap(Type mapType, Map map, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { Type keyType, valueType; if (mapType instanceof ParameterizedType) { @@ -653,24 +662,25 @@ public class JsonCodec implements Serializable { } if (keyType == String.class) { - return encodeStringMap(valueType, map, application); + return encodeStringMap(valueType, map, connectorTracker); } else if (keyType == Connector.class) { - return encodeConnectorMap(valueType, map, application); + return encodeConnectorMap(valueType, map, connectorTracker); } else { - return encodeObjectMap(keyType, valueType, map, application); + return encodeObjectMap(keyType, valueType, map, connectorTracker); } } private static JSONArray encodeObjectMap(Type keyType, Type valueType, - Map map, Application application) throws JSONException { + Map map, ConnectorTracker connectorTracker) + throws JSONException { JSONArray keys = new JSONArray(); JSONArray values = new JSONArray(); for (Entry entry : map.entrySet()) { Object encodedKey = encode(entry.getKey(), null, keyType, - application); + connectorTracker); Object encodedValue = encode(entry.getValue(), null, valueType, - application); + connectorTracker); keys.put(encodedKey); values.put(encodedValue); @@ -680,13 +690,13 @@ public class JsonCodec implements Serializable { } private static JSONObject encodeConnectorMap(Type valueType, Map map, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); for (Entry entry : map.entrySet()) { Connector key = (Connector) entry.getKey(); Object encodedValue = encode(entry.getValue(), null, valueType, - application); + connectorTracker); jsonMap.put(key.getConnectorId(), encodedValue); } @@ -694,13 +704,13 @@ public class JsonCodec implements Serializable { } private static JSONObject encodeStringMap(Type valueType, Map map, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); for (Entry entry : map.entrySet()) { String key = (String) entry.getKey(); Object encodedValue = encode(entry.getValue(), null, valueType, - application); + connectorTracker); jsonMap.put(key, encodedValue); } diff --git a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java index 55f15da3d9..d3fbf4d988 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java @@ -43,12 +43,12 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { super(application); } - public void handleFileUpload(WrappedRequest request, + public void handleFileUpload(Root root, WrappedRequest request, WrappedResponse response) throws IOException { String contentType = request.getContentType(); String name = request.getParameter("name"); String ownerId = request.getParameter("rec-owner"); - Connector owner = getConnector(getApplication(), ownerId); + Connector owner = getConnector(root, ownerId); StreamVariable streamVariable = ownerToNameToStreamVariable.get(owner) .get(name); @@ -73,7 +73,7 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { .iterator(); while (iterator.hasNext()) { Connector owner = iterator.next(); - if (application.getConnector(owner.getConnectorId()) == null) { + if (getConnector(root, owner.getConnectorId()) == null) { // Owner is no longer attached to the application iterator.remove(); } diff --git a/src/com/vaadin/ui/ConnectorTracker.java b/src/com/vaadin/ui/ConnectorTracker.java new file mode 100644 index 0000000000..75a75ad22a --- /dev/null +++ b/src/com/vaadin/ui/ConnectorTracker.java @@ -0,0 +1,229 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.vaadin.terminal.AbstractClientConnector; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.server.ClientConnector; + +/** + * A class which takes care of book keeping of {@link ClientConnector}s for one + * Root. + *

+ * Provides {@link #getConnector(String)} which can be used to lookup a + * connector from its id. This is for framework use only and should not be + * needed in applications. + *

+ *

+ * Tracks which {@link ClientConnector}s are dirty so they can be updated to the + * client when the following response is sent. A connector is dirty when an + * operation has been performed on it on the server and as a result of this + * operation new information needs to be sent to its {@link ServerConnector}. + *

+ * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class ConnectorTracker implements Serializable { + + private final HashMap connectorIdToConnector = new HashMap(); + private Set dirtyConnectors = new HashSet(); + + private Root root; + + /** + * Gets a logger for this class + * + * @return A logger instance for logging within this class + * + */ + public static Logger getLogger() { + return Logger.getLogger(ConnectorTracker.class.getName()); + } + + public ConnectorTracker(Root root) { + this.root = root; + } + + /** + * Register the given connector. + *

+ * The lookup method {@link #getConnector(String)} only returns registered + * connectors. + *

+ * + * @param connector + * The connector to register. + */ + public void registerConnector(ClientConnector connector) { + String connectorId = connector.getConnectorId(); + ClientConnector previouslyRegistered = connectorIdToConnector + .get(connectorId); + if (previouslyRegistered == null) { + connectorIdToConnector.put(connectorId, connector); + getLogger().fine( + "Registered " + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + } else if (previouslyRegistered != connector) { + throw new RuntimeException("A connector with id " + connectorId + + " is already registered!"); + } else { + getLogger().warning( + "An already registered connector was registered again: " + + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + } + + } + + /** + * Unregister the given connector. + * + *

+ * The lookup method {@link #getConnector(String)} only returns registered + * connectors. + *

+ * + * @param connector + * The connector to unregister + */ + public void unregisterConnector(ClientConnector connector) { + String connectorId = connector.getConnectorId(); + if (!connectorIdToConnector.containsKey(connectorId)) { + getLogger().warning( + "Tried to unregister " + + connector.getClass().getSimpleName() + " (" + + connectorId + ") which is not registered"); + return; + } + if (connectorIdToConnector.get(connectorId) != connector) { + throw new RuntimeException("The given connector with id " + + connectorId + + " is not the one that was registered for that id"); + } + + getLogger().fine( + "Unregistered " + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + connectorIdToConnector.remove(connectorId); + } + + /** + * Gets a connector by its id. + * + * @param connectorId + * The connector id to look for + * @return The connector with the given id or null if no connector has the + * given id + */ + public ClientConnector getConnector(String connectorId) { + return connectorIdToConnector.get(connectorId); + } + + /** + * Cleans the connector map from all connectors that are no longer attached + * to the application. This should only be called by the framework. + */ + public void cleanConnectorMap() { + // remove detached components from paintableIdMap so they + // can be GC'ed + Iterator iterator = connectorIdToConnector.keySet().iterator(); + + while (iterator.hasNext()) { + String connectorId = iterator.next(); + ClientConnector connector = connectorIdToConnector.get(connectorId); + if (connector instanceof Component) { + Component component = (Component) connector; + if (component.getRoot() != root) { + // If component is no longer part of this application, + // remove it from the map. If it is re-attached to the + // application at some point it will be re-added through + // registerConnector(connector) + iterator.remove(); + } + } + } + + } + + public void markDirty(ClientConnector connector) { + if (getLogger().isLoggable(Level.FINE)) { + if (!dirtyConnectors.contains(connector)) { + getLogger() + .fine(getDebugInfo(connector) + " " + "is now dirty"); + } + } + + dirtyConnectors.add(connector); + } + + public void markClean(ClientConnector connector) { + if (getLogger().isLoggable(Level.FINE)) { + if (dirtyConnectors.contains(connector)) { + getLogger().fine( + getDebugInfo(connector) + " " + "is no longer dirty"); + } + } + + dirtyConnectors.remove(connector); + } + + private String getDebugInfo(ClientConnector connector) { + String message = getObjectString(connector); + if (connector.getParent() != null) { + message += " (parent: " + getObjectString(connector.getParent()) + + ")"; + } + return message; + } + + private String getObjectString(Object connector) { + return connector.getClass().getName() + "@" + + Integer.toHexString(connector.hashCode()); + } + + public void markAllConnectorsDirty() { + markConnectorsDirtyRecursively(root); + getLogger().fine("All connectors are now dirty"); + } + + public void markAllConnectorsClean() { + dirtyConnectors.clear(); + getLogger().fine("All connectors are now clean"); + } + + /** + * Marks all visible connectors dirty, starting from the given connector and + * going downwards in the hierarchy. + * + * @param c + * The component to start iterating downwards from + */ + private void markConnectorsDirtyRecursively(ClientConnector c) { + if (c instanceof Component && !((Component) c).isVisible()) { + return; + } + markDirty(c); + for (ClientConnector child : AbstractClientConnector + .getAllChildrenIterable(c)) { + markConnectorsDirtyRecursively(child); + } + } + + public Collection getDirtyConnectors() { + return dirtyConnectors; + } + +} diff --git a/src/com/vaadin/ui/DirtyConnectorTracker.java b/src/com/vaadin/ui/DirtyConnectorTracker.java deleted file mode 100644 index ae47d87eae..0000000000 --- a/src/com/vaadin/ui/DirtyConnectorTracker.java +++ /dev/null @@ -1,113 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.ui; - -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.vaadin.terminal.AbstractClientConnector; -import com.vaadin.terminal.gwt.server.ClientConnector; - -/** - * A class that tracks dirty {@link ClientConnector}s. A {@link ClientConnector} - * is dirty when an operation has been performed on it on the server and as a - * result of this operation new information needs to be sent to its client side - * counterpart. - * - * @author Vaadin Ltd - * @version @VERSION@ - * @since 7.0.0 - * - */ -public class DirtyConnectorTracker implements Serializable { - private Set dirtyConnectors = new HashSet(); - private Root root; - - /** - * Gets a logger for this class - * - * @return A logger instance for logging within this class - * - */ - public static Logger getLogger() { - return Logger.getLogger(DirtyConnectorTracker.class.getName()); - } - - public DirtyConnectorTracker(Root root) { - this.root = root; - } - - public void markDirty(ClientConnector connector) { - if (getLogger().isLoggable(Level.FINE)) { - if (!dirtyConnectors.contains(connector)) { - getLogger() - .fine(getDebugInfo(connector) + " " + "is now dirty"); - } - } - - dirtyConnectors.add(connector); - } - - public void markClean(ClientConnector connector) { - if (getLogger().isLoggable(Level.FINE)) { - if (dirtyConnectors.contains(connector)) { - getLogger().fine( - getDebugInfo(connector) + " " + "is no longer dirty"); - } - } - - dirtyConnectors.remove(connector); - } - - private String getDebugInfo(ClientConnector connector) { - String message = getObjectString(connector); - if (connector.getParent() != null) { - message += " (parent: " + getObjectString(connector.getParent()) - + ")"; - } - return message; - } - - private String getObjectString(Object connector) { - return connector.getClass().getName() + "@" - + Integer.toHexString(connector.hashCode()); - } - - public void markAllConnectorsDirty() { - markConnectorsDirtyRecursively(root); - getLogger().fine("All connectors are now dirty"); - } - - public void markAllConnectorsClean() { - dirtyConnectors.clear(); - getLogger().fine("All connectors are now clean"); - } - - /** - * Marks all visible connectors dirty, starting from the given connector and - * going downwards in the hierarchy. - * - * @param c - * The component to start iterating downwards from - */ - private void markConnectorsDirtyRecursively(ClientConnector c) { - if (c instanceof Component && !((Component) c).isVisible()) { - return; - } - markDirty(c); - for (ClientConnector child : AbstractClientConnector - .getAllChildrenIterable(c)) { - markConnectorsDirtyRecursively(child); - } - } - - public Collection getDirtyConnectors() { - return dirtyConnectors; - } - -} diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java index 50ad99571e..3727d6f251 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -560,8 +560,7 @@ public abstract class Root extends AbstractComponentContainer implements /** Identifies the click event */ private static final String CLICK_EVENT_ID = VRoot.CLICK_EVENT_ID; - private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( - this); + private ConnectorTracker connectorTracker = new ConnectorTracker(this); private Page page = new Page(this); @@ -1208,8 +1207,8 @@ public abstract class Root extends AbstractComponentContainer implements return isVisible() && isEnabled(); } - public DirtyConnectorTracker getDirtyConnectorTracker() { - return dirtyConnectorTracker; + public ConnectorTracker getConnectorTracker() { + return connectorTracker; } public Page getPage() { -- 2.39.5