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

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