/*
@VaadinApache2LicenseForJavaFiles@
*/
package com.vaadin.terminal.gwt.client;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.dom.client.Style;
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.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.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Focusable;
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.ui.Field;
import com.vaadin.terminal.gwt.client.ui.VContextMenu;
import com.vaadin.terminal.gwt.client.ui.VNotification;
import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent;
import com.vaadin.terminal.gwt.client.ui.VViewPaintable;
import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager;
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 widgets receive updates from the corresponding server-side
* components as calls to
* {@link VPaintableWidget#updateFromUIDL(UIDL, ApplicationConnection)} (not to
* be confused with the server side interface
* {@link com.vaadin.terminal.Paintable} ). Any client-side changes (typically
* resulting from user actions) are sent back to the server as variable changes
* (see {@link #updateVariable()}).
*
* TODO document better
*
* Entry point classes (widgetsets) define onModuleLoad()
.
*/
public class ApplicationConnection {
// This indicates the whole page is generated by us (not embedded)
public static final String GENERATED_BODY_CLASSNAME = "v-generated-body";
private static final String MODIFIED_CLASSNAME = "v-modified";
public static final String DISABLED_CLASSNAME = "v-disabled";
private static final String REQUIRED_CLASSNAME_EXT = "-required";
private static final String ERROR_CLASSNAME_EXT = "-error";
public static final char VAR_RECORD_SEPARATOR = '\u001e';
public static final char VAR_FIELD_SEPARATOR = '\u001f';
public static final char VAR_BURST_SEPARATOR = '\u001d';
public static final char VAR_ARRAYITEM_SEPARATOR = '\u001c';
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";
public static final String ATTRIBUTE_DESCRIPTION = "description";
public static final String ATTRIBUTE_ERROR = "error";
/**
* 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"; // 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() { incrementActiveRequests(); 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 (applicationRunning) { checkForPendingVariableBursts(); runPostRequestHooks(configuration.getRootPanelId()); } decrementActiveRequests(); // deferring to avoid flickering Scheduler.get().scheduleDeferred(new Command() { public void execute() { if (!hasActiveRequest()) { hideLoadingIndicator(); } } }); } /** * 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(pendingVariables); if (pendingVariableBursts.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) { // Handle redirect if (json.containsKey("redirect")) { String url = json.getValueMap("redirect").getString("url"); VConsole.log("redirecting to " + url); redirect(url); return; } // Get security key if (json.containsKey(UIDL_SECURITY_TOKEN_ID)) { uidlSecurityKey = json.getString(UIDL_SECURITY_TOKEN_ID); } 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)); } } if (json.containsKey("typeMappings")) { configuration.addComponentMappings( json.getValueMap("typeMappings"), widgetSet); } Command c = new Command() { public void execute() { VConsole.dirUIDL(json, configuration); if (json.containsKey("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; } } if (repaintAll) { /* * idToPaintableDetail is already cleanded at the start of * the changeset handling, bypass cleanup. */ paintableMap.purgeUnregistryBag(false); } else { paintableMap.purgeUnregistryBag(true); } // TODO build profiling for widget impl loading time final long prosessingTime = (new Date().getTime()) - start.getTime(); VConsole.log(" Processing time was " + String.valueOf(prosessingTime) + "ms for " + jsonText.length() + " characters of JSON"); VConsole.log("Referenced paintables: " + paintableMap.size()); endRequest(); } }; ApplicationConfiguration.runWhenWidgetsLoaded(c); } // Redirect browser, null reloads current page private static native void redirect(String url) /*-{ if (url) { $wnd.location = url; } else { $wnd.location.reload(false); } }-*/; private void addVariableToQueue(String paintableId, String variableName, String encodedValue, boolean immediate, char type) { final String id = paintableId + VAR_FIELD_SEPARATOR + variableName + VAR_FIELD_SEPARATOR + type; for (int i = 1; i < pendingVariables.size(); i += 2) { if ((pendingVariables.get(i)).equals(id)) { pendingVariables.remove(i - 1); pendingVariables.remove(i - 1); break; } } pendingVariables.add(encodedValue); pendingVariables.add(id); if (immediate) { sendPendingVariableChanges(); } } /** * This method sends currently queued variable changes to server. It is * called when immediate variable update must happen. * * To ensure correct order for variable changes (due servers multithreading * or network), we always wait for active request to be handler before * sending a new one. If there is an active request, we will put varible * "burst" to queue that will be purged after current request is handled. * */ @SuppressWarnings("unchecked") public void sendPendingVariableChanges() { if (applicationRunning) { if (hasActiveRequest()) { // skip empty queues if there are pending bursts to be sent if (pendingVariables.size() > 0 || pendingVariableBursts.size() == 0) { ArrayList* 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, VPaintable newValue, boolean immediate) { String pid = paintableMap.getPid(newValue); addVariableToQueue(paintableId, variableName, pid, immediate, 'p'); } /** * 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, escapeVariableValue(newValue), immediate, 's'); } /** * 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, 'i'); } /** * 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, 'l'); } /** * 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, 'f'); } /** * 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, 'd'); } /** * 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 ? "true" : "false", immediate, 'b'); } /** * 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 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, Map* The implementation of a component depends on many properties, including * styles, component features, etc. Sometimes the user changes those * properties after the component has been created. Calling this method in * the beginning of your updateFromUIDL -method automatically replaces your * component with more appropriate if the requested implementation changes. *
* ** Component can delegate management of caption, icon, error messages and * description to parent layout. This is optional an should be decided by * component author *
* ** Component must also pipe events to * {@link #handleTooltipEvent(Event, VPaintableWidget, Object)} method. *
* This method can also be used to deregister tooltips by using null as * tooltip * * @param paintable * Paintable "owning" this tooltip * @param key * key assosiated with given tooltip. Can be any object. For * example a related dom element. Same key must be given for * {@link #handleTooltipEvent(Event, VPaintableWidget, Object)} * method. * * @param tooltip * the TooltipInfo object containing details shown in tooltip, * null if deregistering tooltip */ public void registerTooltip(VPaintableWidget paintable, Object key, TooltipInfo tooltip) { paintableMap.registerTooltip(paintable, key, tooltip); } /** * Gets the {@link ApplicationConfiguration} for the current application. * * @see ApplicationConfiguration * @return the configuration for this application */ public ApplicationConfiguration getConfiguration() { return configuration; } /** * Checks if there is a registered server side listener for the event. The * list of events which has server side listeners is updated automatically * before the component is updated so the value is correct if called from * updatedFromUIDL. * * @param eventIdentifier * The identifier for the event * @return true if at least one listener has been registered on server side * for the event identified by eventIdentifier. */ public boolean hasEventListeners(VPaintableWidget paintable, String eventIdentifier) { return paintableMap.hasEventListeners(paintable, eventIdentifier); } /** * Adds the get parameters to the uri and returns the new uri that contains * the parameters. * * @param uri * The uri to which the parameters should be added. * @param extraParams * One or more parameters in the format "a=b" or "c=d&e=f". An * empty string is allowed but will not modify the url. * @return The modified URI with the get parameters in extraParams added. */ public static String addGetParameters(String uri, String extraParams) { if (extraParams == null || extraParams.length() == 0) { return uri; } // RFC 3986: The query component is indicated by the first question // mark ("?") character and terminated by a number sign ("#") character // or by the end of the URI. String fragment = null; int hashPosition = uri.indexOf('#'); if (hashPosition != -1) { // Fragment including "#" fragment = uri.substring(hashPosition); // The full uri before the fragment uri = uri.substring(0, hashPosition); } if (uri.contains("?")) { uri += "&"; } else { uri += "?"; } uri += extraParams; if (fragment != null) { uri += fragment; } return uri; } VPaintableMap getPaintableMap() { return paintableMap; } @Deprecated public void unregisterPaintable(VPaintable p) { paintableMap.unregisterPaintable(p); } public VTooltip getVTooltip() { return tooltip; } @Deprecated public void handleWidgetTooltipEvent(Event event, Widget owner, Object key) { handleTooltipEvent(event, getPaintableMap().getPaintable(owner), key); } @Deprecated public void handleWidgetTooltipEvent(Event event, Widget owner) { handleTooltipEvent(event, getPaintableMap().getPaintable(owner)); } @Deprecated public void registerWidgetTooltip(Widget owner, Object key, TooltipInfo info) { registerTooltip(getPaintableMap().getPaintable(owner), key, info); } @Deprecated public boolean hasWidgetEventListeners(Widget widget, String eventIdentifier) { return hasEventListeners(getPaintableMap().getPaintable(widget), eventIdentifier); } private boolean layoutScheduled = false; private ScheduledCommand layoutCommand = new ScheduledCommand() { public void execute() { layoutScheduled = false; measureManager.doLayout(ApplicationConnection.this); } }; public void doLayout(boolean lazy) { if (!lazy) { layoutCommand.execute(); } else if (!layoutScheduled) { layoutScheduled = true; Scheduler.get().scheduleDeferred(layoutCommand); } } public MeasureManager.MeasuredSize getMeasuredSize( VPaintableWidget paintable) { return paintableMap.getMeasuredSize(paintable); } }