/*
@VaadinApache2LicenseForJavaFiles@
*/
package com.vaadin.terminal.gwt.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage;
import com.vaadin.terminal.gwt.client.ResourceLoader.ResourceLoadEvent;
import com.vaadin.terminal.gwt.client.ResourceLoader.ResourceLoadListener;
import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper;
import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
import com.vaadin.terminal.gwt.client.communication.RpcManager;
import com.vaadin.terminal.gwt.client.communication.SerializerMap;
import com.vaadin.terminal.gwt.client.communication.SharedState;
import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
import com.vaadin.terminal.gwt.client.communication.Type;
import com.vaadin.terminal.gwt.client.communication.UidlValue;
import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector;
import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector;
import com.vaadin.terminal.gwt.client.ui.VContextMenu;
import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager;
import com.vaadin.terminal.gwt.client.ui.notification.VNotification;
import com.vaadin.terminal.gwt.client.ui.notification.VNotification.HideEvent;
import com.vaadin.terminal.gwt.client.ui.root.RootConnector;
import com.vaadin.terminal.gwt.client.ui.window.WindowConnector;
import com.vaadin.terminal.gwt.server.AbstractCommunicationManager;
/**
* This is the client side communication "engine", managing client-server
* communication with its server side counterpart
* {@link AbstractCommunicationManager}.
*
* Client-side connectors receive updates from the corresponding server-side
* connector (typically component) as state updates or RPC calls. The connector
* has the possibility to communicate back with its server side counter part
* through RPC calls.
*
* TODO document better
*
* Entry point classes (widgetsets) define onModuleLoad()
.
*/
public class ApplicationConnection {
public static final String V_RESOURCE_PATH = "v-resourcePath";
private static final String CONNECTOR_PROTOCOL_PREFIX = "connector://";
public static final String CONNECTOR_RESOURCE_PREFIX = "APP/CONNECTOR";
// This indicates the whole page is generated by us (not embedded)
public static final String GENERATED_BODY_CLASSNAME = "v-generated-body";
public static final String MODIFIED_CLASSNAME = "v-modified";
public static final String DISABLED_CLASSNAME = "v-disabled";
public static final String REQUIRED_CLASSNAME_EXT = "-required";
public static final String ERROR_CLASSNAME_EXT = "-error";
public static final String UPDATE_VARIABLE_INTERFACE = "v";
public static final String UPDATE_VARIABLE_METHOD = "v";
public static final char VAR_BURST_SEPARATOR = '\u001d';
public static final char VAR_ESCAPE_CHARACTER = '\u001b';
public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key";
/**
* Name of the parameter used to transmit root ids back and forth
*/
public static final String ROOT_ID_PARAMETER = "rootId";
/**
* @deprecated use UIDL_SECURITY_TOKEN_ID instead
*/
@Deprecated
public static final String UIDL_SECURITY_HEADER = UIDL_SECURITY_TOKEN_ID;
public static final String PARAM_UNLOADBURST = "onunloadburst";
private static SerializerMap serializerMap;
/**
* A string that, if found in a non-JSON response to a UIDL request, will
* cause the browser to refresh the page. If followed by a colon, optional
* whitespace, and a URI, causes the browser to synchronously load the URI.
*
*
* This allows, for instance, a servlet filter to redirect the application * to a custom login page when the session expires. For example: *
* ** if (sessionExpired) { * response.setHeader("Content-Type", "text/html"); * response.getWriter().write( * myLoginPageHtml + "<!-- Vaadin-Refresh: " * + request.getContextPath() + " -->"); * } **/ public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh"; private final boolean debugLogging = true || false; // will hold the UIDL security key (for XSS protection) once received private String uidlSecurityKey = "init"; private final HashMap
vaadin.forceSync()
sends pending variable changes, in
* effect synchronizing the server and client state. This is done for all
* applications on host page.vaadin.postRequestHooks
is a map of functions which gets
* called after each XHR made by vaadin application. Note, that it is
* attaching js functions responsibility to create the variable like this:
*
*
* if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
* postRequestHooks.myHook = function(appId) {
* if(appId == "MyAppOfInterest") {
* // do the staff you need on xhr activity
* }
* }
*
First parameter passed to these functions is the identifier
* of Vaadin application that made the request.
* "); html.append(message); html.append("
"); } if (html.length() > 0) { // Add error description html.append(""); html.append(details); html.append("
"); VNotification n = VNotification.createNotification(1000 * 60 * 45); n.addEventListener(new NotificationRedirect(url)); n.show(html.toString(), VNotification.CENTERED_TOP, VNotification.STYLE_SYSTEM); } else { redirect(url); } } protected void startRequest() { if (hasActiveRequest) { VConsole.error("Trying to start a new request while another is active"); } hasActiveRequest = true; requestStartTime = new Date(); // show initial throbber if (loadTimer == null) { loadTimer = new Timer() { @Override public void run() { /* * IE7 does not properly cancel the event with * loadTimer.cancel() so we have to check that we really * should make it visible */ if (loadTimer != null) { showLoadingIndicator(); } } }; // First one kicks in at 300ms } loadTimer.schedule(300); } protected void endRequest() { if (!hasActiveRequest) { VConsole.error("No active request"); } // After checkForPendingVariableBursts() there may be a new active // request, so we must set hasActiveRequest to false before, not after, // the call. Active requests used to be tracked with an integer counter, // so setting it after used to work but not with the #8505 changes. hasActiveRequest = false; if (applicationRunning) { checkForPendingVariableBursts(); runPostRequestHooks(configuration.getRootPanelId()); } // deferring to avoid flickering Scheduler.get().scheduleDeferred(new Command() { @Override public void execute() { if (!hasActiveRequest()) { hideLoadingIndicator(); // If on Liferay and session expiration management is in // use, extend session duration on each request. // Doing it here rather than before the request to improve // responsiveness. // Postponed until the end of the next request if other // requests still pending. extendLiferaySession(); } } }); } /** * This method is called after applying uidl change set to application. * * It will clean current and queued variable change sets. And send next * change set if it exists. */ private void checkForPendingVariableBursts() { cleanVariableBurst(pendingInvocations); if (pendingBursts.size() > 0) { for (Iterator* Used by the native "client.isActive" function. *
* * @return true if deferred commands are (potentially) being executed, false * otherwise */ private boolean isExecutingDeferredCommands() { Scheduler s = Scheduler.get(); if (s instanceof VSchedulerImpl) { return ((VSchedulerImpl) s).hasWorkQueued(); } else { return false; } } /** * Determines whether or not the loading indicator is showing. * * @return true if the loading indicator is visible */ public boolean isLoadingIndicatorVisible() { if (loadElement == null) { return false; } if (loadElement.getStyle().getProperty("display").equals("none")) { return false; } return true; } private static native ValueMap parseJSONResponse(String jsonText) /*-{ try { return JSON.parse(jsonText); } catch (ignored) { return eval('(' + jsonText + ')'); } }-*/; private void handleReceivedJSONMessage(Date start, String jsonText, ValueMap json) { handleUIDLMessage(start, jsonText, json); } protected void handleUIDLMessage(final Date start, final String jsonText, final ValueMap json) { VConsole.log("Handling message from server"); // Handle redirect if (json.containsKey("redirect")) { String url = json.getValueMap("redirect").getString("url"); VConsole.log("redirecting to " + url); redirect(url); return; } final MultiStepDuration handleUIDLDuration = new MultiStepDuration(); // Get security key if (json.containsKey(UIDL_SECURITY_TOKEN_ID)) { uidlSecurityKey = json.getString(UIDL_SECURITY_TOKEN_ID); } VConsole.log(" * Handling resources from server"); if (json.containsKey("resources")) { ValueMap resources = json.getValueMap("resources"); JsArrayString keyArray = resources.getKeyArray(); int l = keyArray.length(); for (int i = 0; i < l; i++) { String key = keyArray.get(i); resourcesMap.put(key, resources.getAsString(key)); } } handleUIDLDuration.logDuration( " * Handling resources from server completed", 10); VConsole.log(" * Handling type inheritance map from server"); if (json.containsKey("typeInheritanceMap")) { configuration.addComponentInheritanceInfo(json .getValueMap("typeInheritanceMap")); } handleUIDLDuration.logDuration( " * Handling type inheritance map from server completed", 10); VConsole.log("Handling type mappings from server"); if (json.containsKey("typeMappings")) { configuration.addComponentMappings( json.getValueMap("typeMappings"), widgetSet); } VConsole.log("Handling resource dependencies"); if (json.containsKey("scriptDependencies")) { loadScriptDependencies(json.getJSStringArray("scriptDependencies")); } if (json.containsKey("styleDependencies")) { loadStyleDependencies(json.getJSStringArray("styleDependencies")); } handleUIDLDuration.logDuration( " * Handling type mappings from server completed", 10); /* * Hook for e.g. TestBench to get details about server peformance */ if (json.containsKey("timings")) { serverTimingInfo = json.getValueMap("timings"); } Command c = new Command() { @Override public void execute() { handleUIDLDuration.logDuration(" * Loading widgets completed", 10); MultiStepDuration updateDuration = new MultiStepDuration(); if (debugLogging) { VConsole.log(" * Dumping UIDL to the console"); VConsole.dirUIDL(json, configuration); updateDuration.logDuration( " * Dumping UIDL to the console completed", 10); } if (json.containsKey("locales")) { VConsole.log(" * Handling locales"); // Store locale data JsArray" + error.getAsString("message") + "
"; } String url = null; if (error.containsKey("url")) { url = error.getString("url"); } if (html.length() != 0) { /* 45 min */ VNotification n = VNotification .createNotification(1000 * 60 * 45); n.addEventListener(new NotificationRedirect(url)); n.show(html, VNotification.CENTERED_TOP, VNotification.STYLE_SYSTEM); } else { redirect(url); } applicationRunning = false; } if (validatingLayouts) { VConsole.printLayoutProblems(meta, ApplicationConnection.this, zeroHeightComponents, zeroWidthComponents); zeroHeightComponents = null; zeroWidthComponents = null; validatingLayouts = false; } } updateDuration.logDuration(" * Error handling completed", 10); // TODO build profiling for widget impl loading time lastProcessingTime = (int) ((new Date().getTime()) - start .getTime()); totalProcessingTime += lastProcessingTime; VConsole.log(" Processing time was " + String.valueOf(lastProcessingTime) + "ms for " + jsonText.length() + " characters of JSON"); VConsole.log("Referenced paintables: " + connectorMap.size()); endRequest(); } /** * Sends the state change events created while updating the state * information. * * This must be called after hierarchy change listeners have been * called. At least caption updates for the parent are strange if * fired from state change listeners and thus calls the parent * BEFORE the parent is aware of the child (through a * ConnectorHierarchyChangedEvent) * * @param pendingStateChangeEvents * The events to send */ private void sendStateChangeEvents( Collection* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, ServerConnector newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate); } /** * Sends a new value for the given paintables given variable to the server. ** The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, String newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate); } /** * Sends a new value for the given paintables given variable to the server. ** The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, int newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate); } /** * Sends a new value for the given paintables given variable to the server. ** The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, long newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate); } /** * Sends a new value for the given paintables given variable to the server. ** The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, float newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate); } /** * Sends a new value for the given paintables given variable to the server. ** The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, double newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate); } /** * Sends a new value for the given paintables given variable to the server. ** The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, boolean newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate); } /** * Sends a new value for the given paintables given variable to the server. ** The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *
* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param map * the new values to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, Map