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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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. Set<String> stateUpdateConnectors = new SharedStateWriter().write(
  140. ui, writer);
  141. writer.write(", "); // close states
  142. // TODO This should be optimized. The type only needs to be
  143. // sent once for each connector id + on refresh. Use the same cache
  144. // as
  145. // widget mapping
  146. writer.write("\"types\":");
  147. new ConnectorTypeWriter().write(ui, writer, paintTarget);
  148. writer.write(", "); // close states
  149. // Send update hierarchy information to the client.
  150. // This could be optimized aswell to send only info if hierarchy has
  151. // actually changed. Much like with the shared state. Note though
  152. // that an empty hierarchy is information aswell (e.g. change from 1
  153. // child to 0 children)
  154. writer.write("\"hierarchy\":");
  155. new ConnectorHierarchyWriter().write(ui, writer,
  156. stateUpdateConnectors);
  157. writer.write(", "); // close hierarchy
  158. // send server to client RPC calls for components in the UI, in call
  159. // order
  160. // collect RPC calls from components in the UI in the order in
  161. // which they were performed, remove the calls from components
  162. writer.write("\"rpc\" : ");
  163. new ClientRpcWriter().write(ui, writer);
  164. writer.write(", "); // close rpc
  165. uiConnectorTracker.markAllConnectorsClean();
  166. writer.write("\"meta\" : ");
  167. SystemMessages messages = ui.getSession().getService()
  168. .getSystemMessages(ui.getLocale(), null);
  169. // TODO hilightedConnector
  170. new MetadataWriter().write(ui, writer, repaintAll, async, messages);
  171. writer.write(", ");
  172. writer.write("\"resources\" : ");
  173. new ResourceWriter().write(ui, writer, paintTarget);
  174. Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
  175. .getUsedClientConnectors();
  176. boolean typeMappingsOpen = false;
  177. List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
  178. for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
  179. if (clientCache.cache(class1)) {
  180. // client does not know the mapping key for this type, send
  181. // mapping to client
  182. newConnectorTypes.add(class1);
  183. if (!typeMappingsOpen) {
  184. typeMappingsOpen = true;
  185. writer.write(", \"typeMappings\" : { ");
  186. } else {
  187. writer.write(" , ");
  188. }
  189. String canonicalName = class1.getCanonicalName();
  190. writer.write("\"");
  191. writer.write(canonicalName);
  192. writer.write("\" : ");
  193. writer.write(manager.getTagForType(class1));
  194. }
  195. }
  196. if (typeMappingsOpen) {
  197. writer.write(" }");
  198. }
  199. // TODO PUSH Refactor to TypeInheritanceWriter or something
  200. boolean typeInheritanceMapOpen = false;
  201. if (typeMappingsOpen) {
  202. // send the whole type inheritance map if any new mappings
  203. for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
  204. if (!ClientConnector.class.isAssignableFrom(class1
  205. .getSuperclass())) {
  206. continue;
  207. }
  208. if (!typeInheritanceMapOpen) {
  209. typeInheritanceMapOpen = true;
  210. writer.write(", \"typeInheritanceMap\" : { ");
  211. } else {
  212. writer.write(" , ");
  213. }
  214. writer.write("\"");
  215. writer.write(manager.getTagForType(class1));
  216. writer.write("\" : ");
  217. writer.write(manager
  218. .getTagForType((Class<? extends ClientConnector>) class1
  219. .getSuperclass()));
  220. }
  221. if (typeInheritanceMapOpen) {
  222. writer.write(" }");
  223. }
  224. }
  225. // TODO Refactor to DependencyWriter or something
  226. /*
  227. * Ensure super classes come before sub classes to get script
  228. * dependency order right. Sub class @JavaScript might assume that
  229. *
  230. * @JavaScript defined by super class is already loaded.
  231. */
  232. Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
  233. @Override
  234. public int compare(Class<?> o1, Class<?> o2) {
  235. // TODO optimize using Class.isAssignableFrom?
  236. return hierarchyDepth(o1) - hierarchyDepth(o2);
  237. }
  238. private int hierarchyDepth(Class<?> type) {
  239. if (type == Object.class) {
  240. return 0;
  241. } else {
  242. return hierarchyDepth(type.getSuperclass()) + 1;
  243. }
  244. }
  245. });
  246. List<String> scriptDependencies = new ArrayList<String>();
  247. List<String> styleDependencies = new ArrayList<String>();
  248. for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
  249. JavaScript jsAnnotation = class1
  250. .getAnnotation(JavaScript.class);
  251. if (jsAnnotation != null) {
  252. for (String uri : jsAnnotation.value()) {
  253. scriptDependencies.add(manager.registerDependency(uri,
  254. class1));
  255. }
  256. }
  257. StyleSheet styleAnnotation = class1
  258. .getAnnotation(StyleSheet.class);
  259. if (styleAnnotation != null) {
  260. for (String uri : styleAnnotation.value()) {
  261. styleDependencies.add(manager.registerDependency(uri,
  262. class1));
  263. }
  264. }
  265. }
  266. // Include script dependencies in output if there are any
  267. if (!scriptDependencies.isEmpty()) {
  268. writer.write(", \"scriptDependencies\": "
  269. + JsonUtil.stringify(toJsonArray(scriptDependencies)));
  270. }
  271. // Include style dependencies in output if there are any
  272. if (!styleDependencies.isEmpty()) {
  273. writer.write(", \"styleDependencies\": "
  274. + JsonUtil.stringify(toJsonArray(styleDependencies)));
  275. }
  276. session.getDragAndDropService().printJSONResponse(writer);
  277. for (ClientConnector connector : processedConnectors) {
  278. uiConnectorTracker.markClientSideInitialized(connector);
  279. }
  280. assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
  281. writePerformanceData(ui, writer);
  282. } finally {
  283. uiConnectorTracker.setWritingResponse(false);
  284. uiConnectorTracker.cleanConnectorMap();
  285. }
  286. }
  287. private JsonArray toJsonArray(List<String> list) {
  288. JsonArray result = Json.createArray();
  289. for (int i = 0; i < list.size(); i++) {
  290. result.set(i, list.get(i));
  291. }
  292. return result;
  293. }
  294. /**
  295. * Adds the performance timing data (used by TestBench 3) to the UIDL
  296. * response.
  297. *
  298. * @throws IOException
  299. */
  300. private void writePerformanceData(UI ui, Writer writer) throws IOException {
  301. writer.write(String.format(", \"timings\":[%d, %d]", ui.getSession()
  302. .getCumulativeRequestDuration(), ui.getSession()
  303. .getLastRequestDuration()));
  304. }
  305. private static final Logger getLogger() {
  306. return Logger.getLogger(UidlWriter.class.getName());
  307. }
  308. }