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.

UidlWriter.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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.server.communication;
  17. import java.io.IOException;
  18. import java.io.Serializable;
  19. import java.io.Writer;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.Comparator;
  24. import java.util.HashSet;
  25. import java.util.List;
  26. import java.util.Set;
  27. import java.util.logging.Level;
  28. import java.util.logging.Logger;
  29. import com.vaadin.server.ClientConnector;
  30. import com.vaadin.server.DependencyFilter.FilterContext;
  31. import com.vaadin.server.JsonPaintTarget;
  32. import com.vaadin.server.LegacyCommunicationManager;
  33. import com.vaadin.server.LegacyCommunicationManager.ClientCache;
  34. import com.vaadin.server.SystemMessages;
  35. import com.vaadin.server.VaadinService;
  36. import com.vaadin.server.VaadinSession;
  37. import com.vaadin.shared.ApplicationConstants;
  38. import com.vaadin.ui.ConnectorTracker;
  39. import com.vaadin.ui.Dependency;
  40. import com.vaadin.ui.UI;
  41. import elemental.json.Json;
  42. import elemental.json.JsonArray;
  43. import elemental.json.JsonObject;
  44. import elemental.json.impl.JsonUtil;
  45. /**
  46. * Serializes pending server-side changes to UI state to JSON. This includes
  47. * shared state, client RPC invocations, connector hierarchy changes, connector
  48. * type information among others.
  49. *
  50. * @author Vaadin Ltd
  51. * @since 7.1
  52. */
  53. public class UidlWriter implements Serializable {
  54. /**
  55. * Writes a JSON object containing all pending changes to the given UI.
  56. *
  57. * @param ui
  58. * The {@link UI} whose changes to write
  59. * @param writer
  60. * The writer to use
  61. * @param async
  62. * True if this message is sent by the server asynchronously,
  63. * false if it is a response to a client message.
  64. *
  65. * @throws IOException
  66. * If the writing fails.
  67. */
  68. public void write(UI ui, Writer writer, boolean async) throws IOException {
  69. VaadinSession session = ui.getSession();
  70. VaadinService service = session.getService();
  71. // Purge pending access calls as they might produce additional changes
  72. // to write out
  73. service.runPendingAccessTasks(session);
  74. Set<ClientConnector> processedConnectors = new HashSet<>();
  75. LegacyCommunicationManager manager = session.getCommunicationManager();
  76. ClientCache clientCache = manager.getClientCache(ui);
  77. boolean repaintAll = clientCache.isEmpty();
  78. // Paints components
  79. ConnectorTracker uiConnectorTracker = ui.getConnectorTracker();
  80. getLogger().log(Level.FINE, "* Creating response to client");
  81. while (true) {
  82. ArrayList<ClientConnector> connectorsToProcess = new ArrayList<>();
  83. for (ClientConnector c : uiConnectorTracker
  84. .getDirtyVisibleConnectors()) {
  85. if (!processedConnectors.contains(c)) {
  86. connectorsToProcess.add(c);
  87. }
  88. }
  89. if (connectorsToProcess.isEmpty()) {
  90. break;
  91. }
  92. // process parents before children
  93. Collections.sort(connectorsToProcess,
  94. Comparator.comparingInt(conn -> {
  95. int depth = 0;
  96. ClientConnector connector = conn;
  97. // this is a very fast operation, even for 100+ levels
  98. while (connector.getParent() != null) {
  99. ++depth;
  100. connector = connector.getParent();
  101. }
  102. return depth;
  103. }));
  104. for (ClientConnector connector : connectorsToProcess) {
  105. // call isDirty() to find out if ConnectorTracker knows the
  106. // connector
  107. boolean initialized = uiConnectorTracker.isDirty(connector)
  108. && uiConnectorTracker
  109. .isClientSideInitialized(connector);
  110. processedConnectors.add(connector);
  111. try {
  112. connector.beforeClientResponse(!initialized);
  113. } catch (RuntimeException e) {
  114. manager.handleConnectorRelatedException(connector, e);
  115. }
  116. }
  117. }
  118. getLogger().log(Level.FINE, "Found " + processedConnectors.size()
  119. + " dirty connectors to paint");
  120. uiConnectorTracker.setWritingResponse(true);
  121. try {
  122. int syncId = service.getDeploymentConfiguration()
  123. .isSyncIdCheckEnabled()
  124. ? uiConnectorTracker.getCurrentSyncId() : -1;
  125. writer.write("\"" + ApplicationConstants.SERVER_SYNC_ID + "\": "
  126. + syncId + ", ");
  127. if (repaintAll) {
  128. writer.write("\"" + ApplicationConstants.RESYNCHRONIZE_ID
  129. + "\": true, ");
  130. }
  131. int nextClientToServerMessageId = ui
  132. .getLastProcessedClientToServerId() + 1;
  133. writer.write("\"" + ApplicationConstants.CLIENT_TO_SERVER_ID
  134. + "\": " + nextClientToServerMessageId + ", ");
  135. writer.write("\"changes\" : ");
  136. JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer,
  137. !repaintAll);
  138. new LegacyUidlWriter().write(ui, writer, paintTarget);
  139. paintTarget.close();
  140. writer.write(", "); // close changes
  141. // send shared state to client
  142. // for now, send the complete state of all modified and new
  143. // components
  144. // Ideally, all this would be sent before "changes", but that causes
  145. // complications with legacy components that create sub-components
  146. // in their paint phase. Nevertheless, this will be processed on the
  147. // client after component creation but before legacy UIDL
  148. // processing.
  149. writer.write("\"state\":");
  150. Set<String> stateUpdateConnectors = new SharedStateWriter()
  151. .write(ui, writer);
  152. writer.write(", "); // close states
  153. // TODO This should be optimized. The type only needs to be
  154. // sent once for each connector id + on refresh. Use the same cache
  155. // as
  156. // widget mapping
  157. writer.write("\"types\":");
  158. new ConnectorTypeWriter().write(ui, writer, paintTarget);
  159. writer.write(", "); // close states
  160. // Send update hierarchy information to the client.
  161. // This could be optimized aswell to send only info if hierarchy has
  162. // actually changed. Much like with the shared state. Note though
  163. // that an empty hierarchy is information aswell (e.g. change from 1
  164. // child to 0 children)
  165. writer.write("\"hierarchy\":");
  166. new ConnectorHierarchyWriter().write(ui, writer,
  167. stateUpdateConnectors);
  168. writer.write(", "); // close hierarchy
  169. // send server to client RPC calls for components in the UI, in call
  170. // order
  171. // collect RPC calls from components in the UI in the order in
  172. // which they were performed, remove the calls from components
  173. writer.write("\"rpc\" : ");
  174. new ClientRpcWriter().write(ui, writer);
  175. writer.write(", "); // close rpc
  176. uiConnectorTracker.markAllConnectorsClean();
  177. writer.write("\"meta\" : ");
  178. SystemMessages messages = ui.getSession().getService()
  179. .getSystemMessages(ui.getLocale(), null);
  180. // TODO hilightedConnector
  181. new MetadataWriter().write(ui, writer, repaintAll, async, messages);
  182. writer.write(", ");
  183. writer.write("\"resources\" : ");
  184. new ResourceWriter().write(ui, writer, paintTarget);
  185. Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
  186. .getUsedClientConnectors();
  187. boolean typeMappingsOpen = false;
  188. List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<>();
  189. for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
  190. if (clientCache.cache(class1)) {
  191. // client does not know the mapping key for this type, send
  192. // mapping to client
  193. newConnectorTypes.add(class1);
  194. if (!typeMappingsOpen) {
  195. typeMappingsOpen = true;
  196. writer.write(", \"typeMappings\" : { ");
  197. } else {
  198. writer.write(" , ");
  199. }
  200. String canonicalName = class1.getCanonicalName();
  201. writer.write("\"");
  202. writer.write(canonicalName);
  203. writer.write("\" : ");
  204. writer.write(manager.getTagForType(class1));
  205. }
  206. }
  207. if (typeMappingsOpen) {
  208. writer.write(" }");
  209. }
  210. // TODO PUSH Refactor to TypeInheritanceWriter or something
  211. boolean typeInheritanceMapOpen = false;
  212. if (typeMappingsOpen) {
  213. // send the whole type inheritance map if any new mappings
  214. for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
  215. if (!ClientConnector.class
  216. .isAssignableFrom(class1.getSuperclass())) {
  217. continue;
  218. }
  219. if (!typeInheritanceMapOpen) {
  220. typeInheritanceMapOpen = true;
  221. writer.write(", \"typeInheritanceMap\" : { ");
  222. } else {
  223. writer.write(" , ");
  224. }
  225. writer.write("\"");
  226. writer.write(manager.getTagForType(class1));
  227. writer.write("\" : ");
  228. writer.write(manager.getTagForType(
  229. (Class<? extends ClientConnector>) class1
  230. .getSuperclass()));
  231. }
  232. if (typeInheritanceMapOpen) {
  233. writer.write(" }");
  234. }
  235. }
  236. // TODO Refactor to DependencyWriter or something
  237. /*
  238. * Ensure super classes come before sub classes to get script
  239. * dependency order right. Sub class @JavaScript might assume that
  240. *
  241. * @JavaScript defined by super class is already loaded.
  242. */
  243. Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
  244. @Override
  245. public int compare(Class<?> o1, Class<?> o2) {
  246. // TODO optimize using Class.isAssignableFrom?
  247. return hierarchyDepth(o1) - hierarchyDepth(o2);
  248. }
  249. private int hierarchyDepth(Class<?> type) {
  250. if (type == Object.class) {
  251. return 0;
  252. } else {
  253. return hierarchyDepth(type.getSuperclass()) + 1;
  254. }
  255. }
  256. });
  257. List<Dependency> dependencies = new ArrayList<>();
  258. dependencies.addAll(ui.getPage().getPendingDependencies());
  259. dependencies.addAll(Dependency.findDependencies(newConnectorTypes,
  260. manager, new FilterContext(session)));
  261. // Include dependencies in output if there are any
  262. if (!dependencies.isEmpty()) {
  263. writer.write(", \"dependencies\": "
  264. + JsonUtil.stringify(toJsonArray(dependencies)));
  265. }
  266. session.getDragAndDropService().printJSONResponse(writer);
  267. for (ClientConnector connector : processedConnectors) {
  268. uiConnectorTracker.markClientSideInitialized(connector);
  269. }
  270. assert (uiConnectorTracker.getDirtyConnectors()
  271. .isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
  272. writePerformanceData(ui, writer);
  273. } finally {
  274. uiConnectorTracker.setWritingResponse(false);
  275. uiConnectorTracker.cleanConnectorMap(true);
  276. }
  277. }
  278. private JsonArray toJsonArray(List<Dependency> list) {
  279. JsonArray result = Json.createArray();
  280. for (int i = 0; i < list.size(); i++) {
  281. JsonObject dep = Json.createObject();
  282. Dependency dependency = list.get(i);
  283. dep.put("type", dependency.getType().name());
  284. dep.put("url", dependency.getUrl());
  285. result.set(i, dep);
  286. }
  287. return result;
  288. }
  289. /**
  290. * Adds the performance timing data (used by TestBench 3) to the UIDL
  291. * response.
  292. *
  293. * @throws IOException
  294. */
  295. private void writePerformanceData(UI ui, Writer writer) throws IOException {
  296. if (!ui.getSession().getService().getDeploymentConfiguration()
  297. .isProductionMode()) {
  298. writer.write(String.format(", \"timings\":[%d, %d]",
  299. ui.getSession().getCumulativeRequestDuration(),
  300. ui.getSession().getLastRequestDuration()));
  301. }
  302. }
  303. private static final Logger getLogger() {
  304. return Logger.getLogger(UidlWriter.class.getName());
  305. }
  306. }