You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ConnectorTracker.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /*
  2. * Copyright 2011 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.Serializable;
  18. import java.util.Collection;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.Iterator;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import java.util.logging.Level;
  25. import java.util.logging.Logger;
  26. import com.vaadin.server.AbstractClientConnector;
  27. import com.vaadin.server.AbstractCommunicationManager;
  28. import com.vaadin.server.ClientConnector;
  29. /**
  30. * A class which takes care of book keeping of {@link ClientConnector}s for a
  31. * UI.
  32. * <p>
  33. * Provides {@link #getConnector(String)} which can be used to lookup a
  34. * connector from its id. This is for framework use only and should not be
  35. * needed in applications.
  36. * </p>
  37. * <p>
  38. * Tracks which {@link ClientConnector}s are dirty so they can be updated to the
  39. * client when the following response is sent. A connector is dirty when an
  40. * operation has been performed on it on the server and as a result of this
  41. * operation new information needs to be sent to its
  42. * {@link com.vaadin.client.ServerConnector}.
  43. * </p>
  44. *
  45. * @author Vaadin Ltd
  46. * @since 7.0.0
  47. *
  48. */
  49. public class ConnectorTracker implements Serializable {
  50. private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<String, ClientConnector>();
  51. private Set<ClientConnector> dirtyConnectors = new HashSet<ClientConnector>();
  52. private Set<ClientConnector> uninitializedConnectors = new HashSet<ClientConnector>();
  53. private UI uI;
  54. private Map<ClientConnector, Object> diffStates = new HashMap<ClientConnector, Object>();
  55. /**
  56. * Gets a logger for this class
  57. *
  58. * @return A logger instance for logging within this class
  59. *
  60. */
  61. public static Logger getLogger() {
  62. return Logger.getLogger(ConnectorTracker.class.getName());
  63. }
  64. /**
  65. * Creates a new ConnectorTracker for the given uI. A tracker is always
  66. * attached to a uI and the uI cannot be changed during the lifetime of a
  67. * {@link ConnectorTracker}.
  68. *
  69. * @param uI
  70. * The uI to attach to. Cannot be null.
  71. */
  72. public ConnectorTracker(UI uI) {
  73. this.uI = uI;
  74. }
  75. /**
  76. * Register the given connector.
  77. * <p>
  78. * The lookup method {@link #getConnector(String)} only returns registered
  79. * connectors.
  80. * </p>
  81. *
  82. * @param connector
  83. * The connector to register.
  84. */
  85. public void registerConnector(ClientConnector connector) {
  86. String connectorId = connector.getConnectorId();
  87. ClientConnector previouslyRegistered = connectorIdToConnector
  88. .get(connectorId);
  89. if (previouslyRegistered == null) {
  90. connectorIdToConnector.put(connectorId, connector);
  91. uninitializedConnectors.add(connector);
  92. getLogger().fine(
  93. "Registered " + connector.getClass().getSimpleName() + " ("
  94. + connectorId + ")");
  95. } else if (previouslyRegistered != connector) {
  96. throw new RuntimeException("A connector with id " + connectorId
  97. + " is already registered!");
  98. } else {
  99. getLogger().warning(
  100. "An already registered connector was registered again: "
  101. + connector.getClass().getSimpleName() + " ("
  102. + connectorId + ")");
  103. }
  104. }
  105. /**
  106. * Unregister the given connector.
  107. *
  108. * <p>
  109. * The lookup method {@link #getConnector(String)} only returns registered
  110. * connectors.
  111. * </p>
  112. *
  113. * @param connector
  114. * The connector to unregister
  115. */
  116. public void unregisterConnector(ClientConnector connector) {
  117. String connectorId = connector.getConnectorId();
  118. if (!connectorIdToConnector.containsKey(connectorId)) {
  119. getLogger().warning(
  120. "Tried to unregister "
  121. + connector.getClass().getSimpleName() + " ("
  122. + connectorId + ") which is not registered");
  123. return;
  124. }
  125. if (connectorIdToConnector.get(connectorId) != connector) {
  126. throw new RuntimeException("The given connector with id "
  127. + connectorId
  128. + " is not the one that was registered for that id");
  129. }
  130. getLogger().fine(
  131. "Unregistered " + connector.getClass().getSimpleName() + " ("
  132. + connectorId + ")");
  133. connectorIdToConnector.remove(connectorId);
  134. uninitializedConnectors.remove(connector);
  135. diffStates.remove(connector);
  136. }
  137. /**
  138. * Checks whether the given connector has already been initialized in the
  139. * browser. The given connector should be registered with this connector
  140. * tracker.
  141. *
  142. * @param connector
  143. * the client connector to check
  144. * @return <code>true</code> if the initial state has previously been sent
  145. * to the browser, <code>false</code> if the client-side doesn't
  146. * already know anything about the connector.
  147. */
  148. public boolean isClientSideInitialized(ClientConnector connector) {
  149. assert connectorIdToConnector.get(connector.getConnectorId()) == connector : "Connector should be registered with this ConnectorTracker";
  150. return !uninitializedConnectors.contains(connector);
  151. }
  152. /**
  153. * Marks the given connector as initialized, meaning that the client-side
  154. * state has been initialized for the connector.
  155. *
  156. * @see #isClientSideInitialized(ClientConnector)
  157. *
  158. * @param connector
  159. * the connector that should be marked as initialized
  160. */
  161. public void markClientSideInitialized(ClientConnector connector) {
  162. uninitializedConnectors.remove(connector);
  163. }
  164. /**
  165. * Marks all currently registered connectors as uninitialized. This should
  166. * be done when the client-side has been reset but the server-side state is
  167. * retained.
  168. *
  169. * @see #isClientSideInitialized(ClientConnector)
  170. */
  171. public void markAllClientSidesUninitialized() {
  172. uninitializedConnectors.addAll(connectorIdToConnector.values());
  173. diffStates.clear();
  174. }
  175. /**
  176. * Gets a connector by its id.
  177. *
  178. * @param connectorId
  179. * The connector id to look for
  180. * @return The connector with the given id or null if no connector has the
  181. * given id
  182. */
  183. public ClientConnector getConnector(String connectorId) {
  184. return connectorIdToConnector.get(connectorId);
  185. }
  186. /**
  187. * Cleans the connector map from all connectors that are no longer attached
  188. * to the application. This should only be called by the framework.
  189. */
  190. public void cleanConnectorMap() {
  191. // remove detached components from paintableIdMap so they
  192. // can be GC'ed
  193. Iterator<String> iterator = connectorIdToConnector.keySet().iterator();
  194. while (iterator.hasNext()) {
  195. String connectorId = iterator.next();
  196. ClientConnector connector = connectorIdToConnector.get(connectorId);
  197. if (getUIForConnector(connector) != uI) {
  198. // If connector is no longer part of this uI,
  199. // remove it from the map. If it is re-attached to the
  200. // application at some point it will be re-added through
  201. // registerConnector(connector)
  202. // This code should never be called as cleanup should take place
  203. // in detach()
  204. getLogger()
  205. .warning(
  206. "cleanConnectorMap unregistered connector "
  207. + getConnectorAndParentInfo(connector)
  208. + "). This should have been done when the connector was detached.");
  209. uninitializedConnectors.remove(connector);
  210. diffStates.remove(connector);
  211. iterator.remove();
  212. } else if (!AbstractCommunicationManager.isVisible(connector)
  213. && !uninitializedConnectors.contains(connector)) {
  214. uninitializedConnectors.add(connector);
  215. diffStates.remove(connector);
  216. getLogger().fine(
  217. "cleanConnectorMap removed state for "
  218. + getConnectorAndParentInfo(connector)
  219. + " as it is not visible");
  220. }
  221. }
  222. }
  223. /**
  224. * Finds the uI that the connector is attached to.
  225. *
  226. * @param connector
  227. * The connector to lookup
  228. * @return The uI the connector is attached to or null if it is not attached
  229. * to any uI.
  230. */
  231. private UI getUIForConnector(ClientConnector connector) {
  232. if (connector == null) {
  233. return null;
  234. }
  235. if (connector instanceof Component) {
  236. return ((Component) connector).getUI();
  237. }
  238. return getUIForConnector(connector.getParent());
  239. }
  240. /**
  241. * Mark the connector as dirty.
  242. *
  243. * @see #getDirtyConnectors()
  244. *
  245. * @param connector
  246. * The connector that should be marked clean.
  247. */
  248. public void markDirty(ClientConnector connector) {
  249. if (getLogger().isLoggable(Level.FINE)) {
  250. if (!dirtyConnectors.contains(connector)) {
  251. getLogger().fine(
  252. getConnectorAndParentInfo(connector) + " "
  253. + "is now dirty");
  254. }
  255. }
  256. dirtyConnectors.add(connector);
  257. }
  258. /**
  259. * Mark the connector as clean.
  260. *
  261. * @param connector
  262. * The connector that should be marked clean.
  263. */
  264. public void markClean(ClientConnector connector) {
  265. if (getLogger().isLoggable(Level.FINE)) {
  266. if (dirtyConnectors.contains(connector)) {
  267. getLogger().fine(
  268. getConnectorAndParentInfo(connector) + " "
  269. + "is no longer dirty");
  270. }
  271. }
  272. dirtyConnectors.remove(connector);
  273. }
  274. /**
  275. * Returns {@link #getConnectorString(ClientConnector)} for the connector
  276. * and its parent (if it has a parent).
  277. *
  278. * @param connector
  279. * The connector
  280. * @return A string describing the connector and its parent
  281. */
  282. private String getConnectorAndParentInfo(ClientConnector connector) {
  283. String message = getConnectorString(connector);
  284. if (connector.getParent() != null) {
  285. message += " (parent: " + getConnectorString(connector.getParent())
  286. + ")";
  287. }
  288. return message;
  289. }
  290. /**
  291. * Returns a string with the connector name and id. Useful mostly for
  292. * debugging and logging.
  293. *
  294. * @param connector
  295. * The connector
  296. * @return A string that describes the connector
  297. */
  298. private String getConnectorString(ClientConnector connector) {
  299. if (connector == null) {
  300. return "(null)";
  301. }
  302. String connectorId;
  303. try {
  304. connectorId = connector.getConnectorId();
  305. } catch (RuntimeException e) {
  306. // This happens if the connector is not attached to the application.
  307. // SHOULD not happen in this case but theoretically can.
  308. connectorId = "@" + Integer.toHexString(connector.hashCode());
  309. }
  310. return connector.getClass().getName() + "(" + connectorId + ")";
  311. }
  312. /**
  313. * Mark all connectors in this uI as dirty.
  314. */
  315. public void markAllConnectorsDirty() {
  316. markConnectorsDirtyRecursively(uI);
  317. getLogger().fine("All connectors are now dirty");
  318. }
  319. /**
  320. * Mark all connectors in this uI as clean.
  321. */
  322. public void markAllConnectorsClean() {
  323. dirtyConnectors.clear();
  324. getLogger().fine("All connectors are now clean");
  325. }
  326. /**
  327. * Marks all visible connectors dirty, starting from the given connector and
  328. * going downwards in the hierarchy.
  329. *
  330. * @param c
  331. * The component to start iterating downwards from
  332. */
  333. private void markConnectorsDirtyRecursively(ClientConnector c) {
  334. if (c instanceof Component && !((Component) c).isVisible()) {
  335. return;
  336. }
  337. markDirty(c);
  338. for (ClientConnector child : AbstractClientConnector
  339. .getAllChildrenIterable(c)) {
  340. markConnectorsDirtyRecursively(child);
  341. }
  342. }
  343. /**
  344. * Returns a collection of all connectors which have been marked as dirty.
  345. * <p>
  346. * The state and pending RPC calls for dirty connectors are sent to the
  347. * client in the following request.
  348. * </p>
  349. *
  350. * @return A collection of all dirty connectors for this uI. This list may
  351. * contain invisible connectors.
  352. */
  353. public Collection<ClientConnector> getDirtyConnectors() {
  354. return dirtyConnectors;
  355. }
  356. public Object getDiffState(ClientConnector connector) {
  357. return diffStates.get(connector);
  358. }
  359. public void setDiffState(ClientConnector connector, Object diffState) {
  360. diffStates.put(connector, diffState);
  361. }
  362. public boolean isDirty(ClientConnector connector) {
  363. return dirtyConnectors.contains(connector);
  364. }
  365. }