nz.co.fortytwo.signalk.server.SubscriptionManager.java Source code

Java tutorial

Introduction

Here is the source code for nz.co.fortytwo.signalk.server.SubscriptionManager.java

Source

/*
 *
 * Copyright (C) 2012-2014 R T Huitema. All Rights Reserved.
 * Web: www.42.co.nz
 * Email: robert@42.co.nz
 * Author: R T Huitema
 *
 * This file is part of the signalk-server-java project
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * 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 nz.co.fortytwo.signalk.server;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

/**
 * Track and manage the sessionId's and corresponding webSocket identifiers and subscriptions for a consumer
 *
 * @author robert
 *
 */
public class SubscriptionManager {

    private static Logger logger = LogManager.getLogger(SubscriptionManager.class);

    //hold sessionid <> wsSessionId
    BiMap<String, String> wsSessionMap = HashBiMap.create();
    //map wsSession to output type
    Map<String, String> outPutMap = new HashMap<String, String>();
    //wsSession> localIp#remoteIp
    Map<String, String> ipMap = new HashMap<String, String>();
    //wsSessionId>Subscription
    ConcurrentLinkedQueue<Subscription> subscriptions = new ConcurrentLinkedQueue<Subscription>();
    ConcurrentLinkedQueue<String> heartbeats = new ConcurrentLinkedQueue<String>();

    /**
     * Add a new subscription.
     * @param sub
     * @throws Exception
     */
    public void addSubscription(Subscription sub) throws Exception {
        if (!subscriptions.contains(sub)) {
            if (logger.isDebugEnabled())
                logger.debug("Adding sub " + sub);
            subscriptions.add(sub);
            //create a new route if we have too
            if (sub.isActive() && !hasExistingRoute(sub)) {
                RouteManager routeManager = RouteManagerFactory.getInstance();
                SignalkRouteFactory.configureSubscribeTimer(routeManager, sub);
                if (logger.isDebugEnabled())
                    logger.debug("Started route for sub" + sub);
                heartbeats.remove(sub.getWsSession());
            }
            if (logger.isDebugEnabled())
                logger.debug("Subs size =" + subscriptions.size());
        }

    }

    /**
     * True if another subscription has the same route and is active
     * @param sub
     * @return
     */
    private boolean hasExistingRoute(Subscription sub) {
        for (Subscription s : getSubscriptions(sub.getWsSession())) {
            if (sub.equals(s))
                continue;
            if (sub.isSameRoute(s) && s.isActive()) {
                sub.setRouteId(s.getRouteId());
                return true;
            }
        }
        ;
        return false;
    }

    /**
     * Remove a subscription
     * 
     * @param sub
     * @throws Exception
     */
    public void removeSubscription(Subscription sub) throws Exception {
        subscriptions.remove(sub);
        if (sub.isActive() && !hasExistingRoute(sub)) {
            RouteManager routeManager = RouteManagerFactory.getInstance();
            SignalkRouteFactory.removeSubscribeTimer(routeManager, sub);
        }
        //if we have no subs, then we should put a sub for empty updates as heartbeat
        if (getSubscriptions(sub.getWsSession()).size() == 0) {
            heartbeats.add(sub.getWsSession());
        }
    }

    public ConcurrentLinkedQueue<Subscription> getSubscriptions(String wsSession) {
        ConcurrentLinkedQueue<Subscription> subs = new ConcurrentLinkedQueue<Subscription>();
        for (Subscription s : subscriptions) {
            if (s.getWsSession().equals(wsSession)) {
                subs.add(s);
            }
        }
        return subs;
    }

    /**
     * Returns the wsSessionId for the sessionId if it exists
     * Returns the sessionId if not. This allows for subscriptions to occur before wsSocket starts
     * @param sessionId
     * @return
     */
    public String getWsSession(String sessionId) {
        if (!wsSessionMap.containsKey(sessionId))
            return sessionId;
        return wsSessionMap.get(sessionId);
    }

    public String getSessionId(String wsSession) {
        return wsSessionMap.inverse().get(wsSession);
    }

    /**
     * Inserts the sessionId, wsSession pair.
     * Swaps the wsSessionId for any any inactive sessions that have been entered with sessionId, sessionId
     * If this is a new connection with no subs then nothing will be tx'd
     * @param sessionId
     * @param wsSession
     * @param ipAddress 
     * @param string 
     * @throws Exception 
     */
    public void add(String sessionId, String wsSession, String outputType, String localIpAddress,
            String remoteIpAddress) throws Exception {
        if (StringUtils.isBlank(wsSession) || StringUtils.isBlank(sessionId))
            return;
        wsSessionMap.put(sessionId, wsSession);
        outPutMap.put(wsSession, outputType);
        ipMap.put(wsSession, localIpAddress + "#" + remoteIpAddress);
        logger.debug("Adding " + sessionId + "/" + wsSession + ", outputType=" + outputType + ", localAddress:"
                + localIpAddress + ", remoteAddress:" + remoteIpAddress);
        //now update any subscriptions for sessionId
        ConcurrentLinkedQueue<Subscription> subs = getSubscriptions(sessionId);

        for (Subscription s : subs) {
            if (s.getWsSession().equals(sessionId)) {
                subscriptions.remove(s);
                s.setWsSession(wsSession);
                subscriptions.add(s);
            }
            s.setActive(true);
            if (!hasExistingRoute(s)) {
                RouteManager routeManager = RouteManagerFactory.getInstance();
                SignalkRouteFactory.configureSubscribeTimer(routeManager, s);
            }

        }
        //if we have no subs, then we should put a sub for empty updates as heartbeat
        if (getSubscriptions(wsSession).size() == 0) {
            heartbeats.add(wsSession);
        }
    }

    public void removeAllSessions() throws Exception {
        wsSessionMap.clear();

        outPutMap.clear();
        ipMap.clear();
        //remove all subscriptions
        RouteManager routeManager = RouteManagerFactory.getInstance();

        SignalkRouteFactory.removeSubscribeTimers(routeManager, subscriptions);
        subscriptions.clear();
        heartbeats.clear();

    }

    public void removeSessionId(String sessionId) throws Exception {
        String wsSession = wsSessionMap.get(sessionId);
        wsSessionMap.remove(sessionId);
        outPutMap.remove(wsSession);
        ipMap.remove(wsSession);
        //remove all subscriptions
        RouteManager routeManager = RouteManagerFactory.getInstance();
        ConcurrentLinkedQueue<Subscription> subs = getSubscriptions(wsSession);
        SignalkRouteFactory.removeSubscribeTimers(routeManager, subs);
        subscriptions.removeAll(subs);
        subscriptions.removeAll(getSubscriptions(sessionId));
        heartbeats.remove(wsSession);

    }

    public void removeWsSession(String wsSession) throws Exception {
        wsSessionMap.inverse().remove(wsSession);
        outPutMap.remove(wsSession);
        ipMap.remove(wsSession);
        //remove all subscriptions
        RouteManager routeManager = RouteManagerFactory.getInstance();
        ConcurrentLinkedQueue<Subscription> subs = getSubscriptions(wsSession);
        SignalkRouteFactory.removeSubscribeTimers(routeManager, subs);
        subscriptions.removeAll(subs);
        heartbeats.remove(wsSession);
    }

    /**
     * Returns a Set of all the current sessionIds.
     * 
     * @return
     */
    public Set<String> getSessionKeys() {
        return wsSessionMap.keySet();
    }

    public String getOutputType(String wsSession) {
        return outPutMap.get(wsSession);
    }

    /**
     * Return the ipAddress of the client on this websocket session
     * @param wsSession
     * @return
     */
    public String getRemoteIpAddress(String wsSession) {
        String ips = ipMap.get(wsSession);
        if (StringUtils.isBlank(ips))
            return null;
        return ips.split("#")[1];
    }

    public String getLocalIpAddress(String wsSession) {
        String ips = ipMap.get(wsSession);
        if (StringUtils.isBlank(ips))
            return null;
        return ips.split("#")[0];
    }

    /**
     * Gets a Set of all the current wsSessions
     * @return
     */
    public Set<String> getWsSessionKeys() {
        return wsSessionMap.inverse().keySet();
    }

    public boolean isValid(String sessionId) {
        if (wsSessionMap.containsKey(sessionId))
            return true;
        return false;
    }

    public ConcurrentLinkedQueue<String> getHeartbeats() {
        return heartbeats;
    }

}