Package com.pusher.client.connection.websocket

Source Code of com.pusher.client.connection.websocket.WebSocketConnection$ActivityTimer

package com.pusher.client.connection.websocket;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLException;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.pusher.client.connection.ConnectionEventListener;
import com.pusher.client.connection.ConnectionState;
import com.pusher.client.connection.ConnectionStateChange;
import com.pusher.client.connection.impl.InternalConnection;
import com.pusher.client.util.Factory;

public class WebSocketConnection implements InternalConnection, WebSocketListener {
    private static final Logger log = LoggerFactory.getLogger(WebSocketConnection.class);

    private static final String INTERNAL_EVENT_PREFIX = "pusher:";
    static final String PING_EVENT_SERIALIZED = "{\"event\": \"pusher:ping\"}";

    private final Factory factory;
    private final ActivityTimer activityTimer;
    private final Map<ConnectionState, Set<ConnectionEventListener>> eventListeners = new HashMap<ConnectionState, Set<ConnectionEventListener>>();
    private final URI webSocketUri;

    private volatile ConnectionState state = ConnectionState.DISCONNECTED;
    private WebSocketClient underlyingConnection;
    private String socketId;

    public WebSocketConnection(String url, long activityTimeout, long pongTimeout, Factory factory) throws URISyntaxException {
        this.webSocketUri = new URI(url);
        this.activityTimer = new ActivityTimer(activityTimeout, pongTimeout);
        this.factory = factory;

        for (ConnectionState state : ConnectionState.values()) {
            eventListeners.put(state, new HashSet<ConnectionEventListener>());
        }
    }

    /* Connection implementation */

    @Override
    public void connect() {
        factory.getEventQueue().execute(new Runnable() {
            @Override
            public void run() {
                if (state == ConnectionState.DISCONNECTED) {
                    try {
                        underlyingConnection = factory
                                .newWebSocketClientWrapper(webSocketUri, WebSocketConnection.this);

                        updateState(ConnectionState.CONNECTING);
                        underlyingConnection.connect();
                    } catch (SSLException e) {
                        sendErrorToAllListeners("Error connecting over SSL", null, e);
                    }
                }
            }
        });
    }

    @Override
    public void disconnect() {
        factory.getEventQueue().execute(new Runnable() {
            @Override
            public void run() {
                if (state == ConnectionState.CONNECTED) {
                    updateState(ConnectionState.DISCONNECTING);
                    underlyingConnection.close();
                }
            }
        });
    }

    @Override
    public void bind(ConnectionState state, ConnectionEventListener eventListener) {
        eventListeners.get(state).add(eventListener);
    }

    @Override
    public boolean unbind(ConnectionState state, ConnectionEventListener eventListener) {
        return eventListeners.get(state).remove(eventListener);
    }

    @Override
    public ConnectionState getState() {
        return state;
    }

    /* InternalConnection implementation detail */

    @Override
    public void sendMessage(final String message) {
        factory.getEventQueue().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (state == ConnectionState.CONNECTED) {
                        underlyingConnection.send(message);
                    } else {
                        sendErrorToAllListeners("Cannot send a message while in " + state + " state", null, null);
                    }
                } catch (Exception e) {
                    sendErrorToAllListeners("An exception occurred while sending message [" + message + "]", null, e);
                }
            }
        });
    }

    @Override
    public String getSocketId() {
        return socketId;
    }

    /* implementation detail */

    private void updateState(ConnectionState newState) {
        log.debug("State transition requested, current [" + state + "], new [" + newState + "]");

        final ConnectionStateChange change = new ConnectionStateChange(state, newState);
        this.state = newState;

        Set<ConnectionEventListener> interestedListeners = new HashSet<ConnectionEventListener>();
        interestedListeners.addAll(eventListeners.get(ConnectionState.ALL));
        interestedListeners.addAll(eventListeners.get(newState));

        for (final ConnectionEventListener listener : interestedListeners) {
            factory.getEventQueue().execute(new Runnable() {
                @Override
                public void run() {
                    listener.onConnectionStateChange(change);
                }
            });
        }
    }

    private void handleEvent(String event, String wholeMessage) {
        if (event.startsWith(INTERNAL_EVENT_PREFIX)) {
            handleInternalEvent(event, wholeMessage);
        } else {
            factory.getChannelManager().onMessage(event, wholeMessage);
        }
    }

    private void handleInternalEvent(String event, String wholeMessage) {
        if (event.equals("pusher:connection_established")) {
            handleConnectionMessage(wholeMessage);
        } else if (event.equals("pusher:error")) {
            handleError(wholeMessage);
        }
    }

    @SuppressWarnings("rawtypes")
    private void handleConnectionMessage(String message) {
        Map jsonObject = new Gson().fromJson(message, Map.class);
        String dataString = (String) jsonObject.get("data");
        Map dataMap = new Gson().fromJson(dataString, Map.class);
        socketId = (String) dataMap.get("socket_id");

        updateState(ConnectionState.CONNECTED);
    }

    @SuppressWarnings("rawtypes")
    private void handleError(String wholeMessage) {
        Map json = new Gson().fromJson(wholeMessage, Map.class);
        Object data = json.get("data");

        Map dataMap;
        if (data instanceof String) {
            dataMap = new Gson().fromJson(((String) data), Map.class);
        } else {
            dataMap = (Map) data;
        }

        String message = (String) dataMap.get("message");

        Object codeObject = dataMap.get("code");
        String code = null;
        if (codeObject != null) {
            code = String.valueOf(Math.round((Double) codeObject));
        }

        sendErrorToAllListeners(message, code, null);
    }

    private void sendErrorToAllListeners(final String message, final String code, final Exception e) {
        Set<ConnectionEventListener> allListeners = new HashSet<ConnectionEventListener>();
        for (Set<ConnectionEventListener> listenersForState : eventListeners.values()) {
            allListeners.addAll(listenersForState);
        }

        for (final ConnectionEventListener listener : allListeners) {
            factory.getEventQueue().execute(new Runnable() {
                @Override
                public void run() {
                    listener.onError(message, code, e);
                }
            });
        }
    }

    /* WebSocketListener implementation */

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        // TODO: log the handshake data
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onMessage(final String message) {
        activityTimer.activity();

        factory.getEventQueue().execute(new Runnable() {
            @Override
            public void run() {
                Map<String, String> map = new Gson().fromJson(message, Map.class);
                String event = map.get("event");
                handleEvent(event, message);
            }
        });
    }

    @Override
    public void onClose(final int code, final String reason, final boolean remote) {
        activityTimer.cancelTimeouts();

        factory.getEventQueue().execute(new Runnable() {
            @Override
            public void run() {
                if (state != ConnectionState.DISCONNECTED) {
                    updateState(ConnectionState.DISCONNECTED);
                } else {
                    log.error("Received close from underlying socket when already disconnected. "
                            + "Close code [" + code + "], Reason [" + reason + "], Remote [" + remote + "]");
                }
                factory.shutdownThreads();
            }
        });
    }

    @Override
    public void onError(final Exception ex) {
        factory.getEventQueue().execute(new Runnable() {
            @Override
            public void run() {
                // Do not change connection state as Java_WebSocket will also call onClose.
                // See: https://github.com/leggetter/pusher-java-client/issues/8#issuecomment-16128590
                //updateState(ConnectionState.DISCONNECTED);
                sendErrorToAllListeners("An exception was thrown by the websocket",    null, ex);
            }
        });
    }


    private class ActivityTimer {
        private final long activityTimeout;
        private final long pongTimeout;

        private Future<?> pingTimer;
        private Future<?> pongTimer;

        public ActivityTimer(final long activityTimeout, final long pongTimeout) {
            this.activityTimeout = activityTimeout;
            this.pongTimeout = pongTimeout;
        }

        /**
         * On any activity from the server
         *  - Cancel pong timeout
         *  - Cancel currently ping timeout and re-schedule
         */
        public synchronized void activity() {
            if (pongTimer != null) pongTimer.cancel(true);

            if (pingTimer != null) pingTimer.cancel(false);
            pingTimer = factory.getTimers().schedule(new Runnable() {
                @Override
                public void run() {
                    log.debug("Sending ping");
                    sendMessage(PING_EVENT_SERIALIZED);
                    schedulePongCheck();
                }
            }, activityTimeout, TimeUnit.MILLISECONDS);
        }

        /**
         * Cancel any pending timeouts, for example because we are disconnected.
         */
        public synchronized void cancelTimeouts() {
            if (pingTimer != null) pingTimer.cancel(false);
            if (pongTimer != null) pongTimer.cancel(false);
        }

        /**
         * Called when a ping is sent to await the response
         *  - Cancel any existing timeout
         *  - Schedule new one
         */
        private synchronized void schedulePongCheck() {
            if (pongTimer != null) pongTimer.cancel(false);

            pongTimer = factory.getTimers().schedule(new Runnable() {
                @Override
                public void run() {
                    log.debug("Timed out awaiting pong from server - disconnecting");
                    disconnect();
                }
            }, pongTimeout, TimeUnit.MILLISECONDS);
        }
    }
}
TOP

Related Classes of com.pusher.client.connection.websocket.WebSocketConnection$ActivityTimer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.