Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

ConnectorTracker.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  1. /*
  2. * Copyright 2000-2016 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.IOException;
  18. import java.io.Serializable;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.LinkedList;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.UUID;
  28. import java.util.logging.Level;
  29. import java.util.logging.Logger;
  30. import com.vaadin.server.AbstractClientConnector;
  31. import com.vaadin.server.ClientConnector;
  32. import com.vaadin.server.DragAndDropService;
  33. import com.vaadin.server.GlobalResourceHandler;
  34. import com.vaadin.server.LegacyCommunicationManager;
  35. import com.vaadin.server.StreamVariable;
  36. import com.vaadin.server.VaadinRequest;
  37. import com.vaadin.server.VaadinService;
  38. import com.vaadin.server.communication.ConnectorHierarchyWriter;
  39. import elemental.json.Json;
  40. import elemental.json.JsonException;
  41. import elemental.json.JsonObject;
  42. /**
  43. * A class which takes care of book keeping of {@link ClientConnector}s for a
  44. * UI.
  45. * <p>
  46. * Provides {@link #getConnector(String)} which can be used to lookup a
  47. * connector from its id. This is for framework use only and should not be
  48. * needed in applications.
  49. * </p>
  50. * <p>
  51. * Tracks which {@link ClientConnector}s are dirty so they can be updated to the
  52. * client when the following response is sent. A connector is dirty when an
  53. * operation has been performed on it on the server and as a result of this
  54. * operation new information needs to be sent to its
  55. * {@link com.vaadin.client.ServerConnector}.
  56. * </p>
  57. *
  58. * @author Vaadin Ltd
  59. * @since 7.0.0
  60. *
  61. */
  62. public class ConnectorTracker implements Serializable {
  63. private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<>();
  64. private final Set<ClientConnector> dirtyConnectors = new HashSet<>();
  65. private final Set<ClientConnector> uninitializedConnectors = new HashSet<>();
  66. /**
  67. * Connectors that have been unregistered and should be cleaned up the next
  68. * time {@link #cleanConnectorMap(boolean)} is invoked unless they have been
  69. * registered again.
  70. */
  71. private final Set<ClientConnector> unregisteredConnectors = new HashSet<>();
  72. private boolean writingResponse = false;
  73. private final UI uI;
  74. private transient Map<ClientConnector, JsonObject> diffStates = new HashMap<>();
  75. /** Maps connectorIds to a map of named StreamVariables */
  76. private Map<String, Map<String, StreamVariable>> pidToNameToStreamVariable;
  77. private Map<StreamVariable, String> streamVariableToSeckey;
  78. private int currentSyncId = 0;
  79. /**
  80. * Gets a logger for this class
  81. *
  82. * @return A logger instance for logging within this class
  83. *
  84. */
  85. private static Logger getLogger() {
  86. return Logger.getLogger(ConnectorTracker.class.getName());
  87. }
  88. /**
  89. * Creates a new ConnectorTracker for the given uI. A tracker is always
  90. * attached to a uI and the uI cannot be changed during the lifetime of a
  91. * {@link ConnectorTracker}.
  92. *
  93. * @param uI
  94. * The uI to attach to. Cannot be null.
  95. */
  96. public ConnectorTracker(UI uI) {
  97. this.uI = uI;
  98. }
  99. /**
  100. * Register the given connector.
  101. * <p>
  102. * The lookup method {@link #getConnector(String)} only returns registered
  103. * connectors.
  104. * </p>
  105. *
  106. * @param connector
  107. * The connector to register.
  108. */
  109. public void registerConnector(ClientConnector connector) {
  110. boolean wasUnregistered = unregisteredConnectors.remove(connector);
  111. String connectorId = connector.getConnectorId();
  112. ClientConnector previouslyRegistered = connectorIdToConnector
  113. .get(connectorId);
  114. if (previouslyRegistered == null) {
  115. connectorIdToConnector.put(connectorId, connector);
  116. uninitializedConnectors.add(connector);
  117. if (getLogger().isLoggable(Level.FINE)) {
  118. getLogger().log(Level.FINE, "Registered {0} ({1})",
  119. new Object[] { connector.getClass().getSimpleName(),
  120. connectorId });
  121. }
  122. } else if (previouslyRegistered != connector) {
  123. throw new RuntimeException("A connector with id " + connectorId
  124. + " is already registered!");
  125. } else if (!wasUnregistered) {
  126. getLogger().log(Level.WARNING,
  127. "An already registered connector was registered again: {0} ({1})",
  128. new Object[] { connector.getClass().getSimpleName(),
  129. connectorId });
  130. }
  131. dirtyConnectors.add(connector);
  132. }
  133. /**
  134. * Unregister the given connector.
  135. *
  136. * <p>
  137. * The lookup method {@link #getConnector(String)} only returns registered
  138. * connectors.
  139. * </p>
  140. *
  141. * @param connector
  142. * The connector to unregister
  143. */
  144. public void unregisterConnector(ClientConnector connector) {
  145. String connectorId = connector.getConnectorId();
  146. if (!connectorIdToConnector.containsKey(connectorId)) {
  147. getLogger().log(Level.WARNING,
  148. "Tried to unregister {0} ({1}) which is not registered",
  149. new Object[] { connector.getClass().getSimpleName(),
  150. connectorId });
  151. return;
  152. }
  153. if (connectorIdToConnector.get(connectorId) != connector) {
  154. throw new RuntimeException("The given connector with id "
  155. + connectorId
  156. + " is not the one that was registered for that id");
  157. }
  158. dirtyConnectors.remove(connector);
  159. if (!isClientSideInitialized(connector)) {
  160. // Client side has never known about this connector so there is no
  161. // point in tracking it
  162. removeUnregisteredConnector(connector,
  163. uI.getSession().getGlobalResourceHandler(false));
  164. } else if (unregisteredConnectors.add(connector)) {
  165. // Client side knows about the connector, track it for a while if it
  166. // becomes reattached
  167. if (getLogger().isLoggable(Level.FINE)) {
  168. getLogger().log(Level.FINE, "Unregistered {0} ({1})",
  169. new Object[] { connector.getClass().getSimpleName(),
  170. connectorId });
  171. }
  172. } else {
  173. getLogger().log(Level.WARNING,
  174. "Unregistered {0} ({1}) that was already unregistered.",
  175. new Object[] { connector.getClass().getSimpleName(),
  176. connectorId });
  177. }
  178. }
  179. /**
  180. * Checks whether the given connector has already been initialized in the
  181. * browser. The given connector should be registered with this connector
  182. * tracker.
  183. *
  184. * @param connector
  185. * the client connector to check
  186. * @return <code>true</code> if the initial state has previously been sent
  187. * to the browser, <code>false</code> if the client-side doesn't
  188. * already know anything about the connector.
  189. */
  190. public boolean isClientSideInitialized(ClientConnector connector) {
  191. assert connectorIdToConnector.get(connector
  192. .getConnectorId()) == connector : "Connector should be registered with this ConnectorTracker";
  193. return !uninitializedConnectors.contains(connector);
  194. }
  195. /**
  196. * Marks the given connector as initialized, meaning that the client-side
  197. * state has been initialized for the connector.
  198. *
  199. * @see #isClientSideInitialized(ClientConnector)
  200. *
  201. * @param connector
  202. * the connector that should be marked as initialized
  203. */
  204. public void markClientSideInitialized(ClientConnector connector) {
  205. uninitializedConnectors.remove(connector);
  206. }
  207. /**
  208. * Marks all currently registered connectors as uninitialized. This should
  209. * be done when the client-side has been reset but the server-side state is
  210. * retained.
  211. *
  212. * @see #isClientSideInitialized(ClientConnector)
  213. */
  214. public void markAllClientSidesUninitialized() {
  215. uninitializedConnectors.addAll(connectorIdToConnector.values());
  216. diffStates.clear();
  217. }
  218. /**
  219. * Gets a connector by its id.
  220. *
  221. * @param connectorId
  222. * The connector id to look for
  223. * @return The connector with the given id or null if no connector has the
  224. * given id
  225. */
  226. public ClientConnector getConnector(String connectorId) {
  227. ClientConnector connector = connectorIdToConnector.get(connectorId);
  228. // Ignore connectors that have been unregistered but not yet cleaned up
  229. if (unregisteredConnectors.contains(connector)) {
  230. return null;
  231. } else if (connector != null) {
  232. return connector;
  233. } else {
  234. DragAndDropService service = uI.getSession()
  235. .getDragAndDropService();
  236. if (connectorId.equals(service.getConnectorId())) {
  237. return service;
  238. }
  239. }
  240. return null;
  241. }
  242. /**
  243. * Cleans the connector map from all connectors that are no longer attached
  244. * to the application if there are dirty connectors or the force flag is
  245. * true. This should only be called by the framework.
  246. *
  247. * @param force
  248. * {@code true} to force cleaning
  249. * @since
  250. */
  251. public void cleanConnectorMap(boolean force) {
  252. if (force || !dirtyConnectors.isEmpty()) {
  253. cleanConnectorMap();
  254. }
  255. }
  256. /**
  257. * Cleans the connector map from all connectors that are no longer attached
  258. * to the application. This should only be called by the framework.
  259. *
  260. * @deprecated use {@link #cleanConnectorMap(boolean)} instead
  261. */
  262. @Deprecated
  263. public void cleanConnectorMap() {
  264. removeUnregisteredConnectors();
  265. cleanStreamVariables();
  266. // Do this expensive check only with assertions enabled
  267. assert isHierarchyComplete() : "The connector hierarchy is corrupted. "
  268. + "Check for missing calls to super.setParent(), super.attach() and super.detach() "
  269. + "and that all custom component containers call child.setParent(this) when a child is added and child.setParent(null) when the child is no longer used. "
  270. + "See previous log messages for details.";
  271. Iterator<ClientConnector> iterator = connectorIdToConnector.values()
  272. .iterator();
  273. GlobalResourceHandler globalResourceHandler = uI.getSession()
  274. .getGlobalResourceHandler(false);
  275. while (iterator.hasNext()) {
  276. ClientConnector connector = iterator.next();
  277. assert connector != null;
  278. if (connector.getUI() != uI) {
  279. // If connector is no longer part of this uI,
  280. // remove it from the map. If it is re-attached to the
  281. // application at some point it will be re-added through
  282. // registerConnector(connector)
  283. // This code should never be called as cleanup should take place
  284. // in detach()
  285. getLogger().log(Level.WARNING,
  286. "cleanConnectorMap unregistered connector {0}. This should have been done when the connector was detached.",
  287. getConnectorAndParentInfo(connector));
  288. if (globalResourceHandler != null) {
  289. globalResourceHandler.unregisterConnector(connector);
  290. }
  291. uninitializedConnectors.remove(connector);
  292. diffStates.remove(connector);
  293. iterator.remove();
  294. } else if (!uninitializedConnectors.contains(connector)
  295. && !LegacyCommunicationManager
  296. .isConnectorVisibleToClient(connector)) {
  297. // Connector was visible to the client but is no longer (e.g.
  298. // setVisible(false) has been called or SelectiveRenderer tells
  299. // it's no longer shown) -> make sure that the full state is
  300. // sent again when/if made visible
  301. uninitializedConnectors.add(connector);
  302. diffStates.remove(connector);
  303. assert isRemovalSentToClient(connector) : "Connector "
  304. + connector + " (id = " + connector.getConnectorId()
  305. + ") is no longer visible to the client, but no corresponding hierarchy change was sent.";
  306. if (getLogger().isLoggable(Level.FINE)) {
  307. getLogger().log(Level.FINE,
  308. "cleanConnectorMap removed state for {0} as it is not visible",
  309. getConnectorAndParentInfo(connector));
  310. }
  311. }
  312. }
  313. }
  314. private boolean isRemovalSentToClient(ClientConnector connector) {
  315. VaadinRequest request = VaadinService.getCurrentRequest();
  316. if (request == null) {
  317. // Probably run from a unit test without normal request handling
  318. return true;
  319. }
  320. String attributeName = ConnectorHierarchyWriter.class.getName()
  321. + ".hierarchyInfo";
  322. Object hierarchyInfoObj = request.getAttribute(attributeName);
  323. if (hierarchyInfoObj instanceof JsonObject) {
  324. JsonObject hierachyInfo = (JsonObject) hierarchyInfoObj;
  325. ClientConnector firstVisibleParent = findFirstVisibleParent(
  326. connector);
  327. if (firstVisibleParent == null) {
  328. // Connector is detached, not our business
  329. return true;
  330. }
  331. if (!hierachyInfo.hasKey(firstVisibleParent.getConnectorId())) {
  332. /*
  333. * No hierarchy change about to be sent, but this might be
  334. * because of an optimization that omits explicit hierarchy
  335. * changes for empty connectors that have state changes.
  336. */
  337. if (hasVisibleChild(firstVisibleParent)) {
  338. // Not the optimization case if the parent has visible
  339. // children
  340. return false;
  341. }
  342. attributeName = ConnectorHierarchyWriter.class.getName()
  343. + ".stateUpdateConnectors";
  344. Object stateUpdateConnectorsObj = request
  345. .getAttribute(attributeName);
  346. if (stateUpdateConnectorsObj instanceof Set<?>) {
  347. Set<?> stateUpdateConnectors = (Set<?>) stateUpdateConnectorsObj;
  348. if (!stateUpdateConnectors
  349. .contains(firstVisibleParent.getConnectorId())) {
  350. // Not the optimization case if the parent is not marked
  351. // as dirty
  352. return false;
  353. }
  354. } else {
  355. getLogger().warning("Request attribute " + attributeName
  356. + " is not a Set");
  357. }
  358. }
  359. } else {
  360. getLogger().warning("Request attribute " + attributeName
  361. + " is not a JsonObject");
  362. }
  363. return true;
  364. }
  365. private static boolean hasVisibleChild(ClientConnector parent) {
  366. Iterator<? extends ClientConnector> iterator = AbstractClientConnector
  367. .getAllChildrenIterable(parent).iterator();
  368. while (iterator.hasNext()) {
  369. ClientConnector child = iterator.next();
  370. if (LegacyCommunicationManager.isConnectorVisibleToClient(child)) {
  371. return true;
  372. }
  373. }
  374. return false;
  375. }
  376. private ClientConnector findFirstVisibleParent(ClientConnector connector) {
  377. while (connector != null) {
  378. connector = connector.getParent();
  379. if (LegacyCommunicationManager
  380. .isConnectorVisibleToClient(connector)) {
  381. return connector;
  382. }
  383. }
  384. return null;
  385. }
  386. /**
  387. * Removes all references and information about connectors marked as
  388. * unregistered.
  389. *
  390. */
  391. private void removeUnregisteredConnectors() {
  392. GlobalResourceHandler globalResourceHandler = uI.getSession()
  393. .getGlobalResourceHandler(false);
  394. for (ClientConnector connector : unregisteredConnectors) {
  395. removeUnregisteredConnector(connector, globalResourceHandler);
  396. }
  397. unregisteredConnectors.clear();
  398. }
  399. /**
  400. * Removes all references and information about the given connector, which
  401. * must not be registered.
  402. *
  403. * @param connector
  404. * @param globalResourceHandler
  405. */
  406. private void removeUnregisteredConnector(ClientConnector connector,
  407. GlobalResourceHandler globalResourceHandler) {
  408. ClientConnector removedConnector = connectorIdToConnector
  409. .remove(connector.getConnectorId());
  410. assert removedConnector == connector;
  411. if (globalResourceHandler != null) {
  412. globalResourceHandler.unregisterConnector(connector);
  413. }
  414. uninitializedConnectors.remove(connector);
  415. diffStates.remove(connector);
  416. }
  417. /**
  418. * Checks that the connector hierarchy is consistent.
  419. *
  420. * @return <code>true</code> if the hierarchy is consistent,
  421. * <code>false</code> otherwise
  422. * @since 8.1
  423. */
  424. private boolean isHierarchyComplete() {
  425. boolean noErrors = true;
  426. Set<ClientConnector> danglingConnectors = new HashSet<>(
  427. connectorIdToConnector.values());
  428. LinkedList<ClientConnector> stack = new LinkedList<>();
  429. stack.add(uI);
  430. while (!stack.isEmpty()) {
  431. ClientConnector connector = stack.pop();
  432. danglingConnectors.remove(connector);
  433. Iterable<? extends ClientConnector> children = AbstractClientConnector
  434. .getAllChildrenIterable(connector);
  435. for (ClientConnector child : children) {
  436. stack.add(child);
  437. if (!connector.equals(child.getParent())) {
  438. noErrors = false;
  439. getLogger().log(Level.WARNING,
  440. "{0} claims that {1} is its child, but the child claims {2} is its parent.",
  441. new Object[] { getConnectorString(connector),
  442. getConnectorString(child),
  443. getConnectorString(child.getParent()) });
  444. }
  445. }
  446. }
  447. for (ClientConnector dangling : danglingConnectors) {
  448. noErrors = false;
  449. getLogger().log(Level.WARNING,
  450. "{0} claims that {1} is its parent, but the parent does not acknowledge the parenthood.",
  451. new Object[] { getConnectorString(dangling),
  452. getConnectorString(dangling.getParent()) });
  453. }
  454. return noErrors;
  455. }
  456. /**
  457. * Mark the connector as dirty. This should not be done while the response
  458. * is being written.
  459. *
  460. * @see #getDirtyConnectors()
  461. * @see #isWritingResponse()
  462. *
  463. * @param connector
  464. * The connector that should be marked clean.
  465. */
  466. public void markDirty(ClientConnector connector) {
  467. if (isWritingResponse()) {
  468. throw new IllegalStateException(
  469. "A connector should not be marked as dirty while a response is being written.");
  470. }
  471. if (getLogger().isLoggable(Level.FINE)) {
  472. if (!dirtyConnectors.contains(connector)) {
  473. getLogger().log(Level.FINE, "{0} is now dirty",
  474. getConnectorAndParentInfo(connector));
  475. }
  476. }
  477. dirtyConnectors.add(connector);
  478. }
  479. /**
  480. * Mark the connector as clean.
  481. *
  482. * @param connector
  483. * The connector that should be marked clean.
  484. */
  485. public void markClean(ClientConnector connector) {
  486. if (getLogger().isLoggable(Level.FINE)) {
  487. if (dirtyConnectors.contains(connector)) {
  488. getLogger().log(Level.FINE, "{0} is no longer dirty",
  489. getConnectorAndParentInfo(connector));
  490. }
  491. }
  492. dirtyConnectors.remove(connector);
  493. }
  494. /**
  495. * Returns {@link #getConnectorString(ClientConnector)} for the connector
  496. * and its parent (if it has a parent).
  497. *
  498. * @param connector
  499. * The connector
  500. * @return A string describing the connector and its parent
  501. */
  502. private String getConnectorAndParentInfo(ClientConnector connector) {
  503. String message = getConnectorString(connector);
  504. if (connector.getParent() != null) {
  505. message += " (parent: " + getConnectorString(connector.getParent())
  506. + ")";
  507. }
  508. return message;
  509. }
  510. /**
  511. * Returns a string with the connector name and id. Useful mostly for
  512. * debugging and logging.
  513. *
  514. * @param connector
  515. * The connector
  516. * @return A string that describes the connector
  517. */
  518. private String getConnectorString(ClientConnector connector) {
  519. if (connector == null) {
  520. return "(null)";
  521. }
  522. String connectorId;
  523. try {
  524. connectorId = connector.getConnectorId();
  525. } catch (RuntimeException e) {
  526. // This happens if the connector is not attached to the application.
  527. // SHOULD not happen in this case but theoretically can.
  528. connectorId = "@" + Integer.toHexString(connector.hashCode());
  529. }
  530. return connector.getClass().getName() + "(" + connectorId + ")";
  531. }
  532. /**
  533. * Mark all connectors in this uI as dirty.
  534. */
  535. public void markAllConnectorsDirty() {
  536. markConnectorsDirtyRecursively(uI);
  537. getLogger().fine("All connectors are now dirty");
  538. }
  539. /**
  540. * Mark all connectors in this uI as clean.
  541. */
  542. public void markAllConnectorsClean() {
  543. dirtyConnectors.clear();
  544. getLogger().fine("All connectors are now clean");
  545. }
  546. /**
  547. * Marks all visible connectors dirty, starting from the given connector and
  548. * going downwards in the hierarchy.
  549. *
  550. * @param c
  551. * The component to start iterating downwards from
  552. */
  553. private void markConnectorsDirtyRecursively(ClientConnector c) {
  554. if (c instanceof Component && !((Component) c).isVisible()) {
  555. return;
  556. }
  557. markDirty(c);
  558. for (ClientConnector child : AbstractClientConnector
  559. .getAllChildrenIterable(c)) {
  560. markConnectorsDirtyRecursively(child);
  561. }
  562. }
  563. /**
  564. * Returns a collection of all connectors which have been marked as dirty.
  565. * <p>
  566. * The state and pending RPC calls for dirty connectors are sent to the
  567. * client in the following request.
  568. * </p>
  569. *
  570. * @return A collection of all dirty connectors for this uI. This list may
  571. * contain invisible connectors.
  572. */
  573. public Collection<ClientConnector> getDirtyConnectors() {
  574. return dirtyConnectors;
  575. }
  576. /**
  577. * Checks if there a dirty connectors.
  578. *
  579. * @return true if there are dirty connectors, false otherwise
  580. */
  581. public boolean hasDirtyConnectors() {
  582. return !getDirtyConnectors().isEmpty();
  583. }
  584. /**
  585. * Returns a collection of those {@link #getDirtyConnectors() dirty
  586. * connectors} that are actually visible to the client.
  587. *
  588. * @return A list of dirty and visible connectors.
  589. */
  590. public ArrayList<ClientConnector> getDirtyVisibleConnectors() {
  591. Collection<ClientConnector> dirtyConnectors = getDirtyConnectors();
  592. ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<>(
  593. dirtyConnectors.size());
  594. for (ClientConnector c : dirtyConnectors) {
  595. if (LegacyCommunicationManager.isConnectorVisibleToClient(c)) {
  596. dirtyVisibleConnectors.add(c);
  597. }
  598. }
  599. return dirtyVisibleConnectors;
  600. }
  601. public JsonObject getDiffState(ClientConnector connector) {
  602. assert getConnector(connector.getConnectorId()) == connector;
  603. return diffStates.get(connector);
  604. }
  605. public void setDiffState(ClientConnector connector, JsonObject diffState) {
  606. assert getConnector(connector.getConnectorId()) == connector;
  607. diffStates.put(connector, diffState);
  608. }
  609. public boolean isDirty(ClientConnector connector) {
  610. return dirtyConnectors.contains(connector);
  611. }
  612. /**
  613. * Checks whether the response is currently being written. Connectors can
  614. * not be marked as dirty when a response is being written.
  615. *
  616. * @see #setWritingResponse(boolean)
  617. * @see #markDirty(ClientConnector)
  618. *
  619. * @return <code>true</code> if the response is currently being written,
  620. * <code>false</code> if outside the response writing phase.
  621. */
  622. public boolean isWritingResponse() {
  623. return writingResponse;
  624. }
  625. /**
  626. * Sets the current response write status. Connectors can not be marked as
  627. * dirty when the response is written.
  628. * <p>
  629. * This method has a side-effect of incrementing the sync id by one (see
  630. * {@link #getCurrentSyncId()}), if {@link #isWritingResponse()} returns
  631. * <code>true</code> and <code>writingResponse</code> is set to
  632. * <code>false</code>.
  633. *
  634. * @param writingResponse
  635. * the new response status.
  636. *
  637. * @see #markDirty(ClientConnector)
  638. * @see #isWritingResponse()
  639. * @see #getCurrentSyncId()
  640. *
  641. * @throws IllegalArgumentException
  642. * if the new response status is the same as the previous value.
  643. * This is done to help detecting problems caused by missed
  644. * invocations of this method.
  645. */
  646. public void setWritingResponse(boolean writingResponse) {
  647. if (this.writingResponse == writingResponse) {
  648. throw new IllegalArgumentException(
  649. "The old value is same as the new value");
  650. }
  651. /*
  652. * the right hand side of the && is unnecessary here because of the
  653. * if-clause above, but rigorous coding is always rigorous coding.
  654. */
  655. if (!writingResponse && this.writingResponse) {
  656. // Bump sync id when done writing - the client is not expected to
  657. // know about anything happening after this moment.
  658. currentSyncId++;
  659. }
  660. this.writingResponse = writingResponse;
  661. }
  662. /* Special serialization to JsonObjects which are not serializable */
  663. private void writeObject(java.io.ObjectOutputStream out)
  664. throws IOException {
  665. out.defaultWriteObject();
  666. // Convert JsonObjects in diff state to String representation as
  667. // JsonObject is not serializable
  668. HashMap<ClientConnector, String> stringDiffStates = new HashMap<>(
  669. diffStates.size() * 2);
  670. for (ClientConnector key : diffStates.keySet()) {
  671. stringDiffStates.put(key, diffStates.get(key).toString());
  672. }
  673. out.writeObject(stringDiffStates);
  674. }
  675. /* Special serialization to JsonObjects which are not serializable */
  676. private void readObject(java.io.ObjectInputStream in)
  677. throws IOException, ClassNotFoundException {
  678. in.defaultReadObject();
  679. // Read String versions of JsonObjects and parse into JsonObjects as
  680. // JsonObject is not serializable
  681. diffStates = new HashMap<>();
  682. @SuppressWarnings("unchecked")
  683. HashMap<ClientConnector, String> stringDiffStates = (HashMap<ClientConnector, String>) in
  684. .readObject();
  685. diffStates = new HashMap<>(stringDiffStates.size() * 2);
  686. for (ClientConnector key : stringDiffStates.keySet()) {
  687. try {
  688. diffStates.put(key, Json.parse(stringDiffStates.get(key)));
  689. } catch (JsonException e) {
  690. throw new IOException(e);
  691. }
  692. }
  693. }
  694. /**
  695. * Checks if the indicated connector has a StreamVariable of the given name
  696. * and returns the variable if one is found.
  697. *
  698. * @param connectorId
  699. * @param variableName
  700. * @return variable if a matching one exists, otherwise null
  701. */
  702. public StreamVariable getStreamVariable(String connectorId,
  703. String variableName) {
  704. if (pidToNameToStreamVariable == null) {
  705. return null;
  706. }
  707. Map<String, StreamVariable> map = pidToNameToStreamVariable
  708. .get(connectorId);
  709. if (map == null) {
  710. return null;
  711. }
  712. StreamVariable streamVariable = map.get(variableName);
  713. return streamVariable;
  714. }
  715. /**
  716. * Adds a StreamVariable of the given name to the indicated connector.
  717. *
  718. * @param connectorId
  719. * @param variableName
  720. * @param variable
  721. */
  722. public void addStreamVariable(String connectorId, String variableName,
  723. StreamVariable variable) {
  724. assert getConnector(connectorId) != null;
  725. if (pidToNameToStreamVariable == null) {
  726. pidToNameToStreamVariable = new HashMap<>();
  727. }
  728. Map<String, StreamVariable> nameToStreamVariable = pidToNameToStreamVariable
  729. .get(connectorId);
  730. if (nameToStreamVariable == null) {
  731. nameToStreamVariable = new HashMap<>();
  732. pidToNameToStreamVariable.put(connectorId, nameToStreamVariable);
  733. }
  734. nameToStreamVariable.put(variableName, variable);
  735. if (streamVariableToSeckey == null) {
  736. streamVariableToSeckey = new HashMap<>();
  737. }
  738. String seckey = streamVariableToSeckey.get(variable);
  739. if (seckey == null) {
  740. /*
  741. * Despite section 6 of RFC 4122, this particular use of UUID *is*
  742. * adequate for security capabilities. Type 4 UUIDs contain 122 bits
  743. * of random data, and UUID.randomUUID() is defined to use a
  744. * cryptographically secure random generator.
  745. */
  746. seckey = UUID.randomUUID().toString();
  747. streamVariableToSeckey.put(variable, seckey);
  748. }
  749. }
  750. /**
  751. * Removes StreamVariables that belong to connectors that are no longer
  752. * attached to the session.
  753. */
  754. private void cleanStreamVariables() {
  755. if (pidToNameToStreamVariable != null) {
  756. ConnectorTracker connectorTracker = uI.getConnectorTracker();
  757. Iterator<String> iterator = pidToNameToStreamVariable.keySet()
  758. .iterator();
  759. while (iterator.hasNext()) {
  760. String connectorId = iterator.next();
  761. if (connectorTracker.getConnector(connectorId) == null) {
  762. // Owner is no longer attached to the session
  763. Map<String, StreamVariable> removed = pidToNameToStreamVariable
  764. .get(connectorId);
  765. for (String key : removed.keySet()) {
  766. streamVariableToSeckey.remove(removed.get(key));
  767. }
  768. iterator.remove();
  769. }
  770. }
  771. }
  772. }
  773. /**
  774. * Removes any StreamVariable of the given name from the indicated
  775. * connector.
  776. *
  777. * @param connectorId
  778. * @param variableName
  779. */
  780. public void cleanStreamVariable(String connectorId, String variableName) {
  781. if (pidToNameToStreamVariable == null) {
  782. return;
  783. }
  784. Map<String, StreamVariable> nameToStreamVar = pidToNameToStreamVariable
  785. .get(connectorId);
  786. nameToStreamVar.remove(variableName);
  787. if (nameToStreamVar.isEmpty()) {
  788. pidToNameToStreamVariable.remove(connectorId);
  789. }
  790. }
  791. /**
  792. * Returns the security key associated with the given StreamVariable.
  793. *
  794. * @param variable
  795. * @return matching security key if one exists, null otherwise
  796. */
  797. public String getSeckey(StreamVariable variable) {
  798. if (streamVariableToSeckey == null) {
  799. return null;
  800. }
  801. return streamVariableToSeckey.get(variable);
  802. }
  803. /**
  804. * Gets the most recently generated server sync id.
  805. * <p>
  806. * The sync id is incremented by one whenever a new response is being
  807. * written. This id is then sent over to the client. The client then adds
  808. * the most recent sync id to each communication packet it sends back to the
  809. * server. This way, the server knows at what state the client is when the
  810. * packet is sent. If the state has changed on the server side since that,
  811. * the server can try to adjust the way it handles the actions from the
  812. * client side.
  813. * <p>
  814. * The sync id value <code>-1</code> is ignored to facilitate testing with
  815. * pre-recorded requests.
  816. *
  817. * @see #setWritingResponse(boolean)
  818. * @see #connectorWasPresentAsRequestWasSent(String, long)
  819. * @since 7.2
  820. * @return the current sync id
  821. */
  822. public int getCurrentSyncId() {
  823. return currentSyncId;
  824. }
  825. }