123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- /*
- * Copyright 2000-2018 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.client.communication;
-
- import java.util.logging.Logger;
-
- import com.google.gwt.core.client.GWT;
- import com.google.gwt.core.client.Scheduler;
- import com.vaadin.client.ApplicationConfiguration;
- import com.vaadin.client.ApplicationConnection;
- import com.vaadin.client.ApplicationConnection.RequestStartingEvent;
- import com.vaadin.client.ApplicationConnection.ResponseHandlingEndedEvent;
- import com.vaadin.client.Util;
- import com.vaadin.client.VLoadingIndicator;
- import com.vaadin.shared.ApplicationConstants;
- import com.vaadin.shared.Version;
- import com.vaadin.shared.ui.ui.UIState.PushConfigurationState;
-
- import elemental.json.Json;
- import elemental.json.JsonArray;
- import elemental.json.JsonObject;
- import elemental.json.JsonValue;
-
- /**
- * MessageSender is responsible for sending messages to the server.
- * <p>
- * Internally uses {@link XhrConnection} and/or {@link PushConnection} for
- * delivering messages, depending on the application configuration.
- *
- * @since 7.6
- * @author Vaadin Ltd
- */
- public class MessageSender {
-
- private ApplicationConnection connection;
- private boolean hasActiveRequest = false;
-
- /**
- * Counter for the messages send to the server. First sent message has id 0.
- */
- private int clientToServerMessageId = 0;
- private XhrConnection xhrConnection;
- private PushConnection push;
-
- public MessageSender() {
- xhrConnection = GWT.create(XhrConnection.class);
- }
-
- /**
- * Sets the application connection this instance is connected to. Called
- * internally by the framework.
- *
- * @param connection
- * the application connection this instance is connected to
- */
- public void setConnection(ApplicationConnection connection) {
- this.connection = connection;
- xhrConnection.setConnection(connection);
- }
-
- private static Logger getLogger() {
- return Logger.getLogger(MessageSender.class.getName());
- }
-
- public void sendInvocationsToServer() {
- if (!connection.isApplicationRunning()) {
- getLogger().warning(
- "Trying to send RPC from not yet started or stopped application");
- return;
- }
-
- if (hasActiveRequest() || (push != null && !push.isActive())) {
- // There is an active request or push is enabled but not active
- // -> send when current request completes or push becomes active
- } else {
- doSendInvocationsToServer();
- }
- }
-
- /**
- * Sends all pending method invocations (server RPC and legacy variable
- * changes) to the server.
- *
- */
- private void doSendInvocationsToServer() {
-
- ServerRpcQueue serverRpcQueue = getServerRpcQueue();
- if (serverRpcQueue.isEmpty()) {
- return;
- }
-
- if (ApplicationConfiguration.isDebugMode()) {
- Util.logMethodInvocations(connection, serverRpcQueue.getAll());
- }
-
- boolean showLoadingIndicator = serverRpcQueue.showLoadingIndicator();
- JsonArray reqJson = serverRpcQueue.toJson();
- serverRpcQueue.clear();
-
- if (reqJson.length() == 0) {
- // Nothing to send, all invocations were filtered out (for
- // non-existing connectors)
- getLogger().warning(
- "All RPCs filtered out, not sending anything to the server");
- return;
- }
-
- JsonObject extraJson = Json.createObject();
- if (!connection.getConfiguration().isWidgetsetVersionSent()) {
- extraJson.put(ApplicationConstants.WIDGETSET_VERSION_ID,
- Version.getFullVersion());
- connection.getConfiguration().setWidgetsetVersionSent();
- }
- if (showLoadingIndicator) {
- connection.getLoadingIndicator().trigger();
- }
- send(reqJson, extraJson);
- }
-
- private ServerRpcQueue getServerRpcQueue() {
- return connection.getServerRpcQueue();
- }
-
- /**
- * Makes an UIDL request to the server.
- *
- * @param reqInvocations
- * Data containing RPC invocations and all related information.
- * @param extraJson
- * The JsonObject whose parameters are added to the payload
- */
- protected void send(final JsonArray reqInvocations,
- final JsonObject extraJson) {
- startRequest();
-
- JsonObject payload = Json.createObject();
- String csrfToken = getMessageHandler().getCsrfToken();
- if (!csrfToken.equals(ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) {
- payload.put(ApplicationConstants.CSRF_TOKEN, csrfToken);
- }
- payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations);
- payload.put(ApplicationConstants.SERVER_SYNC_ID,
- getMessageHandler().getLastSeenServerSyncId());
- payload.put(ApplicationConstants.CLIENT_TO_SERVER_ID,
- clientToServerMessageId++);
-
- if (extraJson != null) {
- for (String key : extraJson.keys()) {
- JsonValue value = extraJson.get(key);
- payload.put(key, value);
- }
- }
-
- send(payload);
-
- }
-
- /**
- * Sends an asynchronous or synchronous UIDL request to the server using the
- * given URI.
- *
- * @param payload
- * The contents of the request to send
- */
- public void send(final JsonObject payload) {
- if (push != null && push.isBidirectional()) {
- push.push(payload);
- } else {
- xhrConnection.send(payload);
- }
- }
-
- /**
- * Sets the status for the push connection.
- *
- * @param enabled
- * <code>true</code> to enable the push connection;
- * <code>false</code> to disable the push connection.
- */
- public void setPushEnabled(boolean enabled) {
- final PushConfigurationState pushState = connection.getUIConnector()
- .getState().pushConfiguration;
-
- if (enabled && push == null) {
- push = GWT.create(PushConnection.class);
- push.init(connection, pushState);
- } else if (!enabled && push != null && push.isActive()) {
- push.disconnect(() -> {
- push = null;
- /*
- * If push has been enabled again while we were waiting for the
- * old connection to disconnect, now is the right time to open a
- * new connection
- */
- if (pushState.mode.isEnabled()) {
- setPushEnabled(true);
- }
-
- /*
- * Send anything that was enqueued while we waited for the
- * connection to close
- */
- if (getServerRpcQueue().isFlushPending()) {
- getServerRpcQueue().flush();
- }
- });
- }
- }
-
- public void startRequest() {
- if (hasActiveRequest) {
- getLogger().severe(
- "Trying to start a new request while another is active");
- }
- hasActiveRequest = true;
- connection.fireEvent(new RequestStartingEvent(connection));
- }
-
- public void endRequest() {
- if (!hasActiveRequest) {
- getLogger().severe("No active request");
- }
- // After sendInvocationsToServer() there may be a new active
- // request, so we must set hasActiveRequest to false before, not after,
- // the call.
- hasActiveRequest = false;
-
- if (connection.isApplicationRunning()) {
- if (getServerRpcQueue().isFlushPending()) {
- sendInvocationsToServer();
- }
- runPostRequestHooks(connection.getConfiguration().getRootPanelId());
- }
-
- // deferring to avoid flickering
- Scheduler.get().scheduleDeferred(() -> {
- if (!connection.isApplicationRunning() || !(hasActiveRequest()
- || getServerRpcQueue().isFlushPending())) {
- getLoadingIndicator().hide();
-
- // 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();
- }
- });
- connection.fireEvent(new ResponseHandlingEndedEvent(connection));
- }
-
- /**
- * Runs possibly registered client side post request hooks. This is expected
- * to be run after each uidl request made by Vaadin application.
- *
- * @param appId
- */
- public static native void runPostRequestHooks(String appId)
- /*-{
- if ($wnd.vaadin.postRequestHooks) {
- for ( var hook in $wnd.vaadin.postRequestHooks) {
- if (typeof ($wnd.vaadin.postRequestHooks[hook]) == "function") {
- try {
- $wnd.vaadin.postRequestHooks[hook](appId);
- } catch (e) {
- }
- }
- }
- }
- }-*/;
-
- /**
- * If on Liferay and logged in, ask the client side session management
- * JavaScript to extend the session duration.
- *
- * Otherwise, Liferay client side JavaScript will explicitly expire the
- * session even though the server side considers the session to be active.
- * See ticket #8305 for more information.
- */
- public static native void extendLiferaySession()
- /*-{
- if ($wnd.Liferay && $wnd.Liferay.Session) {
- $wnd.Liferay.Session.extend();
- // if the extend banner is visible, hide it
- if ($wnd.Liferay.Session.banner) {
- $wnd.Liferay.Session.banner.remove();
- }
- }
- }-*/;
-
- /**
- * Indicates whether or not there are currently active UIDL requests. Used
- * internally to sequence requests properly, seldom needed in Widgets.
- *
- * @return true if there are active requests
- */
- public boolean hasActiveRequest() {
- return hasActiveRequest;
- }
-
- /**
- * Returns a human readable string representation of the method used to
- * communicate with the server.
- *
- * @return A string representation of the current transport type
- */
- public String getCommunicationMethodName() {
- String clientToServer = "XHR";
- String serverToClient = "-";
- if (push != null) {
- serverToClient = push.getTransportType();
- if (push.isBidirectional()) {
- clientToServer = serverToClient;
- }
- }
-
- return "Client to server: " + clientToServer + ", "
- + "server to client: " + serverToClient;
- }
-
- private ConnectionStateHandler getConnectionStateHandler() {
- return connection.getConnectionStateHandler();
- }
-
- private MessageHandler getMessageHandler() {
- return connection.getMessageHandler();
- }
-
- private VLoadingIndicator getLoadingIndicator() {
- return connection.getLoadingIndicator();
- }
-
- /**
- * Resynchronize the client side, i.e. reload all component hierarchy and
- * state from the server
- */
- public void resynchronize() {
- getLogger().info("Resynchronizing from server");
- JsonObject resyncParam = Json.createObject();
- resyncParam.put(ApplicationConstants.RESYNCHRONIZE_ID, true);
- send(Json.createArray(), resyncParam);
- }
-
- /**
- * Used internally to update what the server expects.
- *
- * @param nextExpectedId
- * the new client id to set
- * @param force
- * true if the id must be updated, false otherwise
- */
- public void setClientToServerMessageId(int nextExpectedId, boolean force) {
- if (nextExpectedId == clientToServerMessageId) {
- // No op as everything matches they way it should
- return;
- }
- if (force) {
- getLogger().info(
- "Forced update of clientId to " + clientToServerMessageId);
- clientToServerMessageId = nextExpectedId;
- return;
- }
-
- if (nextExpectedId > clientToServerMessageId) {
- if (clientToServerMessageId == 0) {
- // We have never sent a message to the server, so likely the
- // server knows better (typical case is that we refreshed a
- // @PreserveOnRefresh UI)
- getLogger().info("Updating client-to-server id to "
- + nextExpectedId + " based on server");
- } else {
- getLogger().warning(
- "Server expects next client-to-server id to be "
- + nextExpectedId + " but we were going to use "
- + clientToServerMessageId + ". Will use "
- + nextExpectedId + ".");
- }
- clientToServerMessageId = nextExpectedId;
- } else {
- // Server has not yet seen all our messages
- // Do nothing as they will arrive eventually
- }
- }
-
- }
|