com.vaadin.client.communication.ServerRpcQueue 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.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.logging.Logger;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.metadata.Method;
import com.vaadin.client.metadata.NoDataException;
import com.vaadin.client.metadata.Type;
import com.vaadin.client.metadata.TypeDataStore;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.communication.MethodInvocation;
import elemental.json.Json;
import elemental.json.JsonArray;
import elemental.json.JsonValue;
/**
* Manages the queue of server invocations (RPC) which are waiting to be sent to
* the server.
*
* @since 7.6
* @author Vaadin Ltd
*/
public class ServerRpcQueue {
/**
* The pending method invocations that will be send to the server by
* {@link #sendPendingCommand}. The key is defined differently based on
* whether the method invocation is enqueued with lastonly. With lastonly
* enabled, the method signature ( {@link MethodInvocation#getLastOnlyTag()}
* ) is used as the key to make enable removing a previously enqueued
* invocation. Without lastonly, an incremental id based on
* {@link #lastInvocationTag} is used to get unique values.
*/
private LinkedHashMap pendingInvocations = new LinkedHashMap<>();
private int lastInvocationTag = 0;
protected ApplicationConnection connection;
private boolean flushPending = false;
private boolean flushScheduled = false;
public ServerRpcQueue() {
}
/**
* 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;
}
private static Logger getLogger() {
return Logger.getLogger(ServerRpcQueue.class.getName());
}
/**
* Removes any pending invocation of the given method from the queue
*
* @param invocation
* The invocation to remove
*/
public void removeMatching(MethodInvocation invocation) {
Iterator iter = pendingInvocations.values()
.iterator();
while (iter.hasNext()) {
MethodInvocation mi = iter.next();
if (mi.equals(invocation)) {
iter.remove();
}
}
}
/**
* Adds an explicit RPC method invocation to the send queue.
*
* @param invocation
* RPC method invocation
* @param lastOnly
* true
to remove all previously delayed invocations
* of the same method that were also enqueued with lastonly set
* to true
. false
to add invocation to
* the end of the queue without touching previously enqueued
* invocations.
*/
public void add(MethodInvocation invocation, boolean lastOnly) {
if (!connection.isApplicationRunning()) {
getLogger().warning(
"Trying to invoke method on not yet started or stopped application");
return;
}
String tag;
if (lastOnly) {
tag = invocation.getLastOnlyTag();
assert !tag.matches(
"\\d+") : "getLastOnlyTag value must have at least one non-digit character";
pendingInvocations.remove(tag);
} else {
tag = Integer.toString(lastInvocationTag++);
}
pendingInvocations.put(tag, invocation);
}
/**
* Returns a collection of all queued method invocations
*
* The returned collection must not be modified in any way
*
* @return a collection of all queued method invocations
*/
public Collection getAll() {
return pendingInvocations.values();
}
/**
* Clears the queue
*/
public void clear() {
pendingInvocations.clear();
// Keep tag string short
lastInvocationTag = 0;
flushPending = false;
}
/**
* Returns the current size of the queue
*
* @return the number of invocations in the queue
*/
public int size() {
return pendingInvocations.size();
}
/**
* Returns the server RPC queue for the given application
*
* @param connection
* the application connection which owns the queue
* @return the server rpc queue for the given application
*/
public static ServerRpcQueue get(ApplicationConnection connection) {
return connection.getServerRpcQueue();
}
/**
* Checks if the queue is empty
*
* @return true if the queue is empty, false otherwise
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* Triggers a send of server RPC and legacy variable changes to the server.
*/
public void flush() {
if (flushScheduled || isEmpty()) {
return;
}
flushPending = true;
flushScheduled = true;
Scheduler.get().scheduleFinally(scheduledFlushCommand);
}
private final ScheduledCommand scheduledFlushCommand = new ScheduledCommand() {
@Override
public void execute() {
flushScheduled = false;
if (!isFlushPending()) {
// Somebody else cleared the queue before we had the chance
return;
}
connection.getMessageSender().sendInvocationsToServer();
}
};
/**
* Checks if a flush operation is pending
*
* @return true if a flush is pending, false otherwise
*/
public boolean isFlushPending() {
return flushPending;
}
/**
* Checks if a loading indicator should be shown when the RPCs have been
* sent to the server and we are waiting for a response
*
* @return true if a loading indicator should be shown, false otherwise
*/
public boolean showLoadingIndicator() {
for (MethodInvocation invocation : getAll()) {
if (isLegacyVariableChange(invocation)
|| isJavascriptRpc(invocation)) {
// Always show loading indicator for legacy requests
return true;
} else {
Type type = new Type(invocation.getInterfaceName(), null);
Method method = type.getMethod(invocation.getMethodName());
if (!TypeDataStore.isNoLoadingIndicator(method)) {
return true;
}
}
}
return false;
}
/**
* Returns the current invocations as JSON
*
* @return the current invocations in a JSON format ready to be sent to the
* server
*/
public JsonArray toJson() {
JsonArray json = Json.createArray();
if (isEmpty()) {
return json;
}
for (MethodInvocation invocation : getAll()) {
String connectorId = invocation.getConnectorId();
if (!connectorExists(connectorId)) {
getLogger().info("Ignoring RPC for removed connector: "
+ connectorId + ": " + invocation.toString());
continue;
}
JsonArray invocationJson = Json.createArray();
invocationJson.set(0, connectorId);
invocationJson.set(1, invocation.getInterfaceName());
invocationJson.set(2, invocation.getMethodName());
JsonArray paramJson = Json.createArray();
Type[] parameterTypes = null;
if (!isLegacyVariableChange(invocation)
&& !isJavascriptRpc(invocation)) {
try {
Type type = new Type(invocation.getInterfaceName(), null);
Method method = type.getMethod(invocation.getMethodName());
parameterTypes = method.getParameterTypes();
} catch (NoDataException e) {
throw new RuntimeException(
"No type data for " + invocation.toString(), e);
}
}
for (int i = 0; i < invocation.getParameters().length; ++i) {
// TODO non-static encoder?
Type type = null;
if (parameterTypes != null) {
type = parameterTypes[i];
}
Object value = invocation.getParameters()[i];
JsonValue jsonValue = JsonEncoder.encode(value, type,
connection);
paramJson.set(i, jsonValue);
}
invocationJson.set(3, paramJson);
json.set(json.length(), invocationJson);
}
return json;
}
/**
* Checks if the connector with the given id is still ok to use (has not
* been removed)
*
* @param connectorId
* the connector id to check
* @return true if the connector exists, false otherwise
*/
private boolean connectorExists(String connectorId) {
ConnectorMap connectorMap = ConnectorMap.get(connection);
return connectorMap.hasConnector(connectorId)
|| connectorMap.isDragAndDropPaintable(connectorId);
}
/**
* Checks if the given method invocation originates from Javascript
*
* @param invocation
* the invocation to check
* @return true if the method invocation originates from javascript, false
* otherwise
*/
public static boolean isJavascriptRpc(MethodInvocation invocation) {
return invocation instanceof JavaScriptMethodInvocation;
}
/**
* Checks if the given method invocation represents a Vaadin 6 variable
* change
*
* @param invocation
* the invocation to check
* @return true if the method invocation is a legacy variable change, false
* otherwise
*/
public static boolean isLegacyVariableChange(MethodInvocation invocation) {
return ApplicationConstants.UPDATE_VARIABLE_METHOD
.equals(invocation.getInterfaceName())
&& ApplicationConstants.UPDATE_VARIABLE_METHOD
.equals(invocation.getMethodName());
}
}