123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- /*
- * Copyright 2000-2014 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
- package com.vaadin.server.communication;
-
- import java.io.IOException;
- import java.io.Serializable;
- import java.io.Writer;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Set;
- import java.util.logging.Level;
- import java.util.logging.Logger;
-
- import com.vaadin.annotations.JavaScript;
- import com.vaadin.annotations.StyleSheet;
- import com.vaadin.server.ClientConnector;
- import com.vaadin.server.JsonPaintTarget;
- import com.vaadin.server.LegacyCommunicationManager;
- import com.vaadin.server.LegacyCommunicationManager.ClientCache;
- import com.vaadin.server.SystemMessages;
- import com.vaadin.server.VaadinService;
- import com.vaadin.server.VaadinSession;
- import com.vaadin.shared.ApplicationConstants;
- import com.vaadin.ui.ConnectorTracker;
- import com.vaadin.ui.UI;
-
- import elemental.json.Json;
- import elemental.json.JsonArray;
- import elemental.json.impl.JsonUtil;
-
- /**
- * Serializes pending server-side changes to UI state to JSON. This includes
- * shared state, client RPC invocations, connector hierarchy changes, connector
- * type information among others.
- *
- * @author Vaadin Ltd
- * @since 7.1
- */
- public class UidlWriter implements Serializable {
-
- /**
- * Writes a JSON object containing all pending changes to the given UI.
- *
- * @param ui
- * The {@link UI} whose changes to write
- * @param writer
- * The writer to use
- * @param analyzeLayouts
- * Whether detected layout problems should be logged.
- * @param async
- * True if this message is sent by the server asynchronously,
- * false if it is a response to a client message.
- *
- * @throws IOException
- * If the writing fails.
- */
- public void write(UI ui, Writer writer, boolean async) throws IOException {
- VaadinSession session = ui.getSession();
- VaadinService service = session.getService();
-
- // Purge pending access calls as they might produce additional changes
- // to write out
- service.runPendingAccessTasks(session);
-
- Set<ClientConnector> processedConnectors = new HashSet<ClientConnector>();
-
- LegacyCommunicationManager manager = session.getCommunicationManager();
- ClientCache clientCache = manager.getClientCache(ui);
- boolean repaintAll = clientCache.isEmpty();
- // Paints components
- ConnectorTracker uiConnectorTracker = ui.getConnectorTracker();
- getLogger().log(Level.FINE, "* Creating response to client");
-
- while (true) {
- ArrayList<ClientConnector> connectorsToProcess = new ArrayList<ClientConnector>();
- for (ClientConnector c : uiConnectorTracker.getDirtyConnectors()) {
- if (!processedConnectors.contains(c)
- && LegacyCommunicationManager
- .isConnectorVisibleToClient(c)) {
- connectorsToProcess.add(c);
- }
- }
-
- if (connectorsToProcess.isEmpty()) {
- break;
- }
-
- for (ClientConnector connector : connectorsToProcess) {
- boolean initialized = uiConnectorTracker
- .isClientSideInitialized(connector);
- processedConnectors.add(connector);
-
- try {
- connector.beforeClientResponse(!initialized);
- } catch (RuntimeException e) {
- manager.handleConnectorRelatedException(connector, e);
- }
- }
- }
-
- getLogger().log(
- Level.FINE,
- "Found " + processedConnectors.size()
- + " dirty connectors to paint");
-
- uiConnectorTracker.setWritingResponse(true);
- try {
-
- int syncId = service.getDeploymentConfiguration()
- .isSyncIdCheckEnabled() ? uiConnectorTracker
- .getCurrentSyncId() : -1;
- writer.write("\"" + ApplicationConstants.SERVER_SYNC_ID + "\": "
- + syncId + ", ");
- if (repaintAll) {
- writer.write("\"" + ApplicationConstants.RESYNCHRONIZE_ID
- + "\": true, ");
- }
- int nextClientToServerMessageId = ui
- .getLastProcessedClientToServerId() + 1;
- writer.write("\"" + ApplicationConstants.CLIENT_TO_SERVER_ID
- + "\": " + nextClientToServerMessageId + ", ");
- writer.write("\"changes\" : ");
-
- JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer,
- !repaintAll);
-
- new LegacyUidlWriter().write(ui, writer, paintTarget);
-
- paintTarget.close();
- writer.write(", "); // close changes
-
- // send shared state to client
-
- // for now, send the complete state of all modified and new
- // components
-
- // Ideally, all this would be sent before "changes", but that causes
- // complications with legacy components that create sub-components
- // in their paint phase. Nevertheless, this will be processed on the
- // client after component creation but before legacy UIDL
- // processing.
-
- writer.write("\"state\":");
- new SharedStateWriter().write(ui, writer);
- writer.write(", "); // close states
-
- // TODO This should be optimized. The type only needs to be
- // sent once for each connector id + on refresh. Use the same cache
- // as
- // widget mapping
-
- writer.write("\"types\":");
- new ConnectorTypeWriter().write(ui, writer, paintTarget);
- writer.write(", "); // close states
-
- // Send update hierarchy information to the client.
-
- // This could be optimized aswell to send only info if hierarchy has
- // actually changed. Much like with the shared state. Note though
- // that an empty hierarchy is information aswell (e.g. change from 1
- // child to 0 children)
-
- writer.write("\"hierarchy\":");
- new ConnectorHierarchyWriter().write(ui, writer);
- writer.write(", "); // close hierarchy
-
- // send server to client RPC calls for components in the UI, in call
- // order
-
- // collect RPC calls from components in the UI in the order in
- // which they were performed, remove the calls from components
-
- writer.write("\"rpc\" : ");
- new ClientRpcWriter().write(ui, writer);
- writer.write(", "); // close rpc
-
- uiConnectorTracker.markAllConnectorsClean();
-
- writer.write("\"meta\" : ");
-
- SystemMessages messages = ui.getSession().getService()
- .getSystemMessages(ui.getLocale(), null);
- // TODO hilightedConnector
- new MetadataWriter().write(ui, writer, repaintAll, async, messages);
- writer.write(", ");
-
- writer.write("\"resources\" : ");
- new ResourceWriter().write(ui, writer, paintTarget);
-
- Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
- .getUsedClientConnectors();
- boolean typeMappingsOpen = false;
-
- List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
-
- for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
- if (clientCache.cache(class1)) {
- // client does not know the mapping key for this type, send
- // mapping to client
- newConnectorTypes.add(class1);
-
- if (!typeMappingsOpen) {
- typeMappingsOpen = true;
- writer.write(", \"typeMappings\" : { ");
- } else {
- writer.write(" , ");
- }
- String canonicalName = class1.getCanonicalName();
- writer.write("\"");
- writer.write(canonicalName);
- writer.write("\" : ");
- writer.write(manager.getTagForType(class1));
- }
- }
- if (typeMappingsOpen) {
- writer.write(" }");
- }
-
- // TODO PUSH Refactor to TypeInheritanceWriter or something
- boolean typeInheritanceMapOpen = false;
- if (typeMappingsOpen) {
- // send the whole type inheritance map if any new mappings
- for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
- if (!ClientConnector.class.isAssignableFrom(class1
- .getSuperclass())) {
- continue;
- }
- if (!typeInheritanceMapOpen) {
- typeInheritanceMapOpen = true;
- writer.write(", \"typeInheritanceMap\" : { ");
- } else {
- writer.write(" , ");
- }
- writer.write("\"");
- writer.write(manager.getTagForType(class1));
- writer.write("\" : ");
- writer.write(manager
- .getTagForType((Class<? extends ClientConnector>) class1
- .getSuperclass()));
- }
- if (typeInheritanceMapOpen) {
- writer.write(" }");
- }
- }
-
- // TODO Refactor to DependencyWriter or something
- /*
- * Ensure super classes come before sub classes to get script
- * dependency order right. Sub class @JavaScript might assume that
- *
- * @JavaScript defined by super class is already loaded.
- */
- Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
- @Override
- public int compare(Class<?> o1, Class<?> o2) {
- // TODO optimize using Class.isAssignableFrom?
- return hierarchyDepth(o1) - hierarchyDepth(o2);
- }
-
- private int hierarchyDepth(Class<?> type) {
- if (type == Object.class) {
- return 0;
- } else {
- return hierarchyDepth(type.getSuperclass()) + 1;
- }
- }
- });
-
- List<String> scriptDependencies = new ArrayList<String>();
- List<String> styleDependencies = new ArrayList<String>();
-
- for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
- JavaScript jsAnnotation = class1
- .getAnnotation(JavaScript.class);
- if (jsAnnotation != null) {
- for (String uri : jsAnnotation.value()) {
- scriptDependencies.add(manager.registerDependency(uri,
- class1));
- }
- }
-
- StyleSheet styleAnnotation = class1
- .getAnnotation(StyleSheet.class);
- if (styleAnnotation != null) {
- for (String uri : styleAnnotation.value()) {
- styleDependencies.add(manager.registerDependency(uri,
- class1));
- }
- }
- }
-
- // Include script dependencies in output if there are any
- if (!scriptDependencies.isEmpty()) {
- writer.write(", \"scriptDependencies\": "
- + JsonUtil.stringify(toJsonArray(scriptDependencies)));
- }
-
- // Include style dependencies in output if there are any
- if (!styleDependencies.isEmpty()) {
- writer.write(", \"styleDependencies\": "
- + JsonUtil.stringify(toJsonArray(styleDependencies)));
- }
-
- session.getDragAndDropService().printJSONResponse(writer);
-
- for (ClientConnector connector : processedConnectors) {
- uiConnectorTracker.markClientSideInitialized(connector);
- }
-
- assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
-
- writePerformanceData(ui, writer);
- } finally {
- uiConnectorTracker.setWritingResponse(false);
- uiConnectorTracker.cleanConnectorMap();
- }
- }
-
- private JsonArray toJsonArray(List<String> list) {
- JsonArray result = Json.createArray();
- for (int i = 0; i < list.size(); i++) {
- result.set(i, list.get(i));
- }
-
- return result;
- }
-
- /**
- * Adds the performance timing data (used by TestBench 3) to the UIDL
- * response.
- *
- * @throws IOException
- */
- private void writePerformanceData(UI ui, Writer writer) throws IOException {
- writer.write(String.format(", \"timings\":[%d, %d]", ui.getSession()
- .getCumulativeRequestDuration(), ui.getSession()
- .getLastRequestDuration()));
- }
-
- private static final Logger getLogger() {
- return Logger.getLogger(UidlWriter.class.getName());
- }
- }
|