com.vaadin.client.communication.MessageSender Maven / Gradle / Ivy
Show all versions of vaadin-client Show documentation
/*
* Copyright 2000-2016 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.google.gwt.user.client.Command;
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.
*
* 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 extraParams
* Parameters that 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 uri
* The URI to use for the request. May includes GET parameters
* @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
* true
to enable the push connection;
* false
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(new Command() {
@Override
public void execute() {
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(new Command() {
@Override
public void execute() {
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 clientToServerMessageId
* 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
}
}
}