All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ws.wamp.jawampa.connection.QueueingConnectionController Maven / Gradle / Ivy

/*
 * Copyright 2015 Matthias Einwag
 *
 * The jawampa authors license this file to you 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 ws.wamp.jawampa.connection;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;

import ws.wamp.jawampa.ApplicationError;
import ws.wamp.jawampa.WampMessages.WampMessage;
import ws.wamp.jawampa.WampSerialization;

public class QueueingConnectionController implements IConnectionController {
    
    static class QueuedMessage {
        public final WampMessage message;
        public final IWampConnectionPromise promise;
        
        public QueuedMessage(WampMessage message, IWampConnectionPromise promise) {
            this.message = message;
            this.promise = promise;
        }
    }
    
    /** Possible states while closing the connection */
    enum CloseStatus {
        /** Close was not issued */
        None,
        /** Connection should be closed at the next possible point of time */
        CloseNow,
        /** Connection should be closed after all already queued messages have been sent */
        CloseAfterRemaining,
        /** Close was issued but not yet acknowledged */
        CloseSent,
        /** Close is acknowledged */
        Closed
    }
    
    final ICompletionCallback messageSentHandler = new ICompletionCallback () {
        @Override
        public void onCompletion(final IWampConnectionFuture future) {
            tryScheduleAction(new Runnable() {
                @Override
                public void run() {
                    // Dequeue the first element of the queue.
                    // Queue might be empty if closed in between
                    QueuedMessage first = queuedMessages.poll();
                    if (future.isSuccess())
                        first.promise.fulfill(null);
                    else {
                        first.promise.reject(future.error());
                    }
                    
                    /** Whether to close after this call */
                    boolean sendClose =
                        (closeStatus == CloseStatus.CloseNow) ||
                        (closeStatus == CloseStatus.CloseAfterRemaining && queuedMessages.size() == 0);
                    
                    if (sendClose) {
                        // Close the connection now
                        closeStatus = CloseStatus.CloseSent;
                        connection.close(true, connectionClosedPromise);
                    } else if (queuedMessages.size() >= 1) {
                        // There's more to send
                        WampMessage nextMessage = queuedMessages.peek().message;
                        messageSentPromise.reset(messageSentHandler, null);
                        connection.sendMessage(nextMessage, messageSentPromise);
                    }
                }
            });
        }
    };
    
    final ICompletionCallback connectionClosedHandler = new ICompletionCallback () {
        @Override
        public void onCompletion(final IWampConnectionFuture future) {
            tryScheduleAction(new Runnable() {
                @Override
                public void run() {
                    assert (closeStatus == CloseStatus.CloseSent);
                    // The connection is now finally closed
                    closeStatus = CloseStatus.Closed;
                    
                    // Complete all pending sends
                    while (queuedMessages.size() > 0) {
                        QueuedMessage nextMessage = queuedMessages.remove();
                        nextMessage.promise.reject(
                            new ApplicationError(ApplicationError.TRANSPORT_CLOSED));
                        // This could theoretically cause side effects.
                        // However it is not valid to call anything on the controller after
                        // close() anyway, so it isn't valid.
                    } 
                    
                    // Forward the result 
                    if (future.isSuccess()) queuedClose.fulfill(null);
                    else queuedClose.reject(future.error());
                    queuedClose = null;
                }
            });
        }
    };
    
    /**
     * Promise that will be fulfilled when the underlying connection
     * has sent a single message. The promise will be reused for
     * all messages that will be sent through this controller.
     */
    final WampConnectionPromise messageSentPromise =
        new WampConnectionPromise(messageSentHandler, null);
    
    /**
     * Promise that will be fulfilled when the connection was closed
     * and the close was acknowledged by the underlying connection.
     */
    final WampConnectionPromise connectionClosedPromise =
        new WampConnectionPromise(connectionClosedHandler, null);
    
    
    /** The scheduler on which all state transitions will run */
    final ScheduledExecutorService scheduler;
    /** The wrapped connection object. Must be injected later due to Router design */
    IWampConnection connection;
    /** The wrapped listener object */
    final IWampConnectionListener connectionListener;
    
    /** Queued messages */
    Deque queuedMessages = new ArrayDeque();
    /** Holds the promise that will be fulfilled when the connection was closed */
    IWampConnectionPromise queuedClose = null;
    
    /** Whether to forward incoming messages or not */
    boolean forwardIncoming = true;
    CloseStatus closeStatus = CloseStatus.None;
    
    public QueueingConnectionController(ScheduledExecutorService scheduler, 
            IWampConnectionListener connectionListener) {
        this.scheduler = scheduler;
        this.connectionListener = connectionListener;
    }
    
    @Override
    public IWampConnectionListener connectionListener() {
        return connectionListener;
    }
    
    @Override
    public IWampConnection connection() {
        return connection;
    }
    
    @Override
    public void setConnection(IWampConnection connection) {
        this.connection = connection;
    }
    
    /**
     * Tries to schedule a runnable on the underlying executor.
* Rejected executions will be suppressed.
* This is useful for cases when the clients EventLoop is shut down before * the EventLoop of the underlying connection. * * @param action The action to schedule. */ private void tryScheduleAction(Runnable action) { try { scheduler.submit(action); } catch (RejectedExecutionException e) {} } // IWampConnection members @Override public WampSerialization serialization() { return connection.serialization(); } @Override public boolean isSingleWriteOnly() { return false; } @Override public void sendMessage(WampMessage message, IWampConnectionPromise promise) { if (closeStatus != CloseStatus.None) throw new IllegalStateException("close() was already called"); queuedMessages.add(new QueuedMessage(message, promise)); // Check if there is already a send in progress if (queuedMessages.size() == 1) { // We are first in queue. Send immediately messageSentPromise.reset(messageSentHandler, null); connection.sendMessage(message, messageSentPromise); } } @Override public void close(boolean sendRemaining, IWampConnectionPromise promise) { if (closeStatus != CloseStatus.None) throw new IllegalStateException("close() was already called"); // Mark as closed. No other actions allowed after that if (sendRemaining) closeStatus = CloseStatus.CloseAfterRemaining; else closeStatus = CloseStatus.CloseNow; queuedClose = promise; // Avoid forwarding of new incoming messages forwardIncoming = false; if (queuedMessages.size() == 0) { // Can immediately start to close closeStatus = CloseStatus.CloseSent; connection.close(true, connectionClosedPromise); } } // IWampConnectionListener methods @Override public void transportClosed() { tryScheduleAction(new Runnable() { @Override public void run() { // Avoid forwarding more than once if (!forwardIncoming) return; forwardIncoming = false; connectionListener.transportClosed(); } }); } @Override public void transportError(final Throwable cause) { tryScheduleAction(new Runnable() { @Override public void run() { // Avoid forwarding more than once if (!forwardIncoming) return; forwardIncoming = false; connectionListener.transportError(cause); } }); } @Override public void messageReceived(final WampMessage message) { tryScheduleAction(new Runnable() { @Override public void run() { // Drop messages that arrive after close if (!forwardIncoming) return; connectionListener.messageReceived(message); } }); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy