com.vaadin.client.communication.ServerRpcQueue.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.client.communication.ServerRpcQueue.java

Source

/*
 * 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.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.logging.Logger;

import com.google.gwt.core.client.Scheduler;
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 {
    private static final Runnable NO_OP = () -> {
        // NOOP
    };

    /**
     * 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<String, MethodInvocation> pendingInvocations = new LinkedHashMap<>();

    private int lastInvocationTag = 0;

    protected ApplicationConnection connection;
    private boolean flushPending = false;

    private Runnable doFlushStrategy = NO_OP;

    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<MethodInvocation> 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
     *            <code>true</code> to remove all previously delayed invocations
     *            of the same method that were also enqueued with lastonly set
     *            to <code>true</code>. <code>false</code> 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.
     * <p>
     * The returned collection must not be modified in any way
     *
     * @return a collection of all queued method invocations
     */
    public Collection<MethodInvocation> getAll() {
        return pendingInvocations.values();
    }

    /**
     * Clears the queue.
     */
    public void clear() {
        pendingInvocations.clear();
        // Keep tag string short
        lastInvocationTag = 0;
        flushPending = false;
        doFlushStrategy = NO_OP;
    }

    /**
     * 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 (isFlushScheduled() || isEmpty()) {
            return;
        }

        flushPending = true;
        doFlushStrategy = this::doFlush;
        Scheduler.get().scheduleFinally(() -> doFlushStrategy.run());
    }

    private void doFlush() {
        doFlushStrategy = NO_OP;
        if (!isFlushPending()) {
            // Somebody else cleared the queue before we had the chance
            return;
        }
        connection.getMessageSender().sendInvocationsToServer();
    }

    private boolean isFlushScheduled() {
        return NO_OP != doFlushStrategy;
    }

    /**
     * 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);
                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, 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());
    }

}