com.github.mrstampy.gameboot.websocket.WebSocketSessionRegistry.java Source code

Java tutorial

Introduction

Here is the source code for com.github.mrstampy.gameboot.websocket.WebSocketSessionRegistry.java

Source

/*
 *              ______                        ____              __ 
 *             / ____/___ _____ ___  ___     / __ )____  ____  / /_
 *            / / __/ __ `/ __ `__ \/ _ \   / __  / __ \/ __ \/ __/
 *           / /_/ / /_/ / / / / / /  __/  / /_/ / /_/ / /_/ / /_  
 *           \____/\__,_/_/ /_/ /_/\___/  /_____/\____/\____/\__/  
 *                                                 
 *                                 .-'\
 *                              .-'  `/\
 *                           .-'      `/\
 *                           \         `/\
 *                            \         `/\
 *                             \    _-   `/\       _.--.
 *                              \    _-   `/`-..--\     )
 *                               \    _-   `,','  /    ,')
 *                                `-_   -   ` -- ~   ,','
 *                                 `-              ,','
 *                                  \,--.    ____==-~
 *                                   \   \_-~\
 *                                    `_-~_.-'
 *                                     \-~
 * 
 *                       http://mrstampy.github.io/gameboot/
 *
 * Copyright (C) 2015, 2016 Burton Alexander
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 */
package com.github.mrstampy.gameboot.websocket;

import static com.github.mrstampy.gameboot.messaging.MessagingGroups.ALL;
import static org.apache.commons.lang3.StringUtils.isEmpty;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import com.github.mrstampy.gameboot.metrics.MetricsHelper;
import com.github.mrstampy.gameboot.systemid.SystemIdKey;
import com.github.mrstampy.gameboot.util.registry.AbstractRegistryKey;
import com.github.mrstampy.gameboot.util.registry.GameBootRegistry;
import com.github.mrstampy.gameboot.util.registry.RegistryCleanerListener;

/**
 * The Class WebSocketSessionRegistry.
 */
@Component
public class WebSocketSessionRegistry extends GameBootRegistry<WebSocketSession>
        implements RegistryCleanerListener {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private static final String REGISTRY_SIZE = "Web Socket Connections";
    private static final String GROUP_OF_ONE = "SINGLE";

    @Autowired
    private MetricsHelper helper;

    private Map<String, List<WebSocketSession>> sessionGroups = new ConcurrentHashMap<>();

    private Map<SystemIdKey, WebSocketSession> activeInGroups = new ConcurrentHashMap<>();

    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private ReadLock rLock = rwLock.readLock();
    private WriteLock wLock = rwLock.writeLock();

    /**
     * Post construct.
     *
     * @throws Exception
     *           the exception
     */
    @PostConstruct
    public void postConstruct() throws Exception {
        helper.gauge(() -> allConnected(), REGISTRY_SIZE, getClass(), "web", "socket", "connections");
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.github.mrstampy.gameboot.util.registry.RegistryCleanerListener#cleanup(
     * com.github.mrstampy.gameboot.util.registry.AbstractRegistryKey)
     */
    /**
     * Cleanup.
     *
     * @param key
     *          the key
     */
    @Override
    public void cleanup(AbstractRegistryKey<?> key) {
        if (!(key instanceof SystemIdKey))
            return;

        WebSocketSession session = activeInGroups.remove(key);
        if (session == null)
            return;

        sessionGroups.entrySet().forEach(e -> removeFromGroups(e, session));
    }

    /**
     * Put in all.
     *
     * @param key
     *          the key
     * @param session
     *          the session
     */
    public void putInAll(SystemIdKey key, WebSocketSession session) {
        putInGroup(ALL, session);
        activeInGroups.put(key, session);
    }

    /**
     * Adds the session to the group.
     *
     * @param groupName
     *          the group name
     * @param session
     *          the session
     */
    public void putInGroup(String groupName, WebSocketSession session) {
        groupAndSessionCheck(groupName, session);

        List<WebSocketSession> list = getSessionsForGroup(groupName);

        rLock.lock();
        try {
            if (list.contains(session))
                return;
        } finally {
            rLock.unlock();
        }

        wLock.lock();
        try {
            list.add(session);
        } finally {
            wLock.unlock();
        }

        addToActiveInGroups(session);
    }

    /**
     * Gets the group.
     *
     * @param groupName
     *          the group name
     * @return the group
     */
    public List<WebSocketSession> getGroup(String groupName) {
        groupNameCheck(groupName);

        List<WebSocketSession> list = sessionGroups.get(groupName);

        return list == null ? null : new ArrayList<>(list);
    }

    /**
     * Contains group.
     *
     * @param groupName
     *          the group name
     * @return true, if successful
     */
    public boolean containsGroup(String groupName) {
        groupNameCheck(groupName);

        return sessionGroups.containsKey(groupName);
    }

    /**
     * Removes the group.
     *
     * @param groupName
     *          the group name
     */
    public void removeGroup(String groupName) {
        groupNameCheck(groupName);

        sessionGroups.remove(groupName);
    }

    /**
     * Removes the session from the group.
     *
     * @param groupName
     *          the group name
     * @param session
     *          the session
     */
    public void removeFromGroup(String groupName, WebSocketSession session) {
        groupAndSessionCheck(groupName, session);

        List<WebSocketSession> list = sessionGroups.get(groupName);

        rLock.lock();
        try {
            if (list == null || !list.contains(session))
                return;
        } finally {
            rLock.unlock();
        }

        wLock.lock();
        try {
            list.remove(session);
        } finally {
            wLock.unlock();
        }

        rLock.lock();
        try {
            if (!list.isEmpty())
                return;
        } finally {
            rLock.unlock();
        }

        sessionGroups.remove(groupName);
    }

    /**
     * Sends the message to the {@link WebSocketSession} specified by the
     * {@link AbstractRegistryKey}.
     *
     * @param key
     *          the key
     * @param message
     *          the message
     */
    public void send(AbstractRegistryKey<?> key, String message) {
        checkMessage(message);

        WebSocketSession session = get(key);
        if (session == null || !session.isOpen()) {
            log.warn("Cannot send message to {}, no session", key);
            return;
        }

        sendText(GROUP_OF_ONE, session, message);
    }

    /**
     * Sends the message to the {@link WebSocketSession} specified by the
     * {@link AbstractRegistryKey}.
     *
     * @param key
     *          the key
     * @param message
     *          the message
     */
    public void send(AbstractRegistryKey<?> key, byte[] message) {
        checkMessage(message);

        WebSocketSession session = get(key);
        if (session == null || !session.isOpen()) {
            log.warn("Cannot send message to {}, no session", key);
            return;
        }

        sendBinary(GROUP_OF_ONE, session, message);
    }

    /**
     * Send to all.
     *
     * @param message
     *          the message
     * @param except
     *          the except
     */
    public void sendToAll(byte[] message, SystemIdKey... except) {
        sendToGroup(ALL, message, except);
    }

    /**
     * Send to all.
     *
     * @param message
     *          the message
     * @param except
     *          the except
     */
    public void sendToAll(String message, SystemIdKey... except) {
        sendToGroup(ALL, message, except);
    }

    /**
     * Send to group.
     *
     * @param groupName
     *          the group name
     * @param message
     *          the message
     * @param except
     *          the except
     */
    public void sendToGroup(String groupName, byte[] message, SystemIdKey... except) {
        groupNameCheck(groupName);
        checkMessage(message);

        List<WebSocketSession> list = sessionGroups.get(groupName);
        List<WebSocketSession> toSend = null;

        rLock.lock();
        try {
            if (list == null || list.isEmpty())
                return;

            List<WebSocketSession> exceptions = getExceptions(except);
            toSend = list.stream().filter(wss -> !exceptions.contains(wss)).collect(Collectors.toList());
        } finally {
            rLock.unlock();
        }

        toSend.forEach(wss -> sendMessage(groupName, wss, message));
    }

    /**
     * Send to group.
     *
     * @param groupName
     *          the group name
     * @param message
     *          the message
     * @param except
     *          the except
     */
    public void sendToGroup(String groupName, String message, SystemIdKey... except) {
        groupNameCheck(groupName);
        checkMessage(message);

        List<WebSocketSession> list = sessionGroups.get(groupName);
        List<WebSocketSession> toSend = null;

        rLock.lock();
        try {
            if (list == null || list.isEmpty())
                return;

            List<WebSocketSession> exceptions = getExceptions(except);

            toSend = list.stream().filter(wss -> !exceptions.contains(wss)).collect(Collectors.toList());
        } finally {
            rLock.unlock();
        }

        toSend.forEach(wss -> sendMessage(groupName, wss, message));
    }

    private void sendMessage(String groupName, WebSocketSession wss, byte[] message) {
        if (!sessionCheck(groupName, wss))
            return;

        sendBinary(groupName, wss, message);
    }

    private void sendMessage(String groupName, WebSocketSession wss, String message) {
        if (!sessionCheck(groupName, wss))
            return;

        sendText(groupName, wss, message);
    }

    private boolean sessionCheck(String groupName, WebSocketSession wss) {
        boolean open = wss.isOpen();

        if (!open) {
            log.warn("Session {} is closed in group {}, cannot send message", wss.getId(), groupName);
            removeFromGroup(groupName, wss);
        }

        return open;
    }

    private void sendBinary(String groupName, WebSocketSession wss, byte[] message) {
        BinaryMessage bm = new BinaryMessage(message);
        try {
            wss.sendMessage(bm);
            log.debug("Sent message to web socket session {} in group {}", wss.getId(), groupName);
        } catch (IOException e) {
            log.error("Unexpected exception sending message to web socket session {}", wss.getId(), e);
        }
    }

    private void sendText(String groupName, WebSocketSession wss, String message) {
        TextMessage bm = new TextMessage(message);
        try {
            wss.sendMessage(bm);
            log.debug("Sent message to web socket session {} in group {}", wss.getId(), groupName);
        } catch (IOException e) {
            log.error("Unexpected exception sending message to web socket session {}", wss.getId(), e);
        }
    }

    @SuppressWarnings("unchecked")
    private List<WebSocketSession> getExceptions(SystemIdKey... except) {
        if (except == null || except.length == 0)
            return Collections.EMPTY_LIST;

        List<WebSocketSession> exceptions = new ArrayList<>();
        for (SystemIdKey key : except) {
            WebSocketSession session = get(key);
            if (session != null)
                exceptions.add(session);
        }

        return exceptions;
    }

    private List<WebSocketSession> getSessionsForGroup(String groupName) {
        List<WebSocketSession> list = sessionGroups.get(groupName);

        if (list == null) {
            list = new ArrayList<>();
            sessionGroups.put(groupName, list);
        }

        return list;
    }

    private void addToActiveInGroups(WebSocketSession session) {
        Optional<Entry<AbstractRegistryKey<?>, WebSocketSession>> o = getKeysForValue(session).stream()
                .filter(k -> k instanceof SystemIdKey).findFirst();

        if (!o.isPresent())
            return;

        SystemIdKey key = (SystemIdKey) o.get().getKey();

        if (!activeInGroups.containsKey(key))
            activeInGroups.put(key, session);
    }

    private void removeFromGroups(Entry<String, List<WebSocketSession>> e, WebSocketSession session) {
        List<WebSocketSession> list = e.getValue();

        rLock.lock();
        try {
            if (!list.contains(session))
                return;
        } finally {
            rLock.unlock();
        }

        wLock.lock();
        try {
            list.remove(session);
        } finally {
            wLock.unlock();
        }
    }

    private int allConnected() {
        List<WebSocketSession> group = sessionGroups.get(ALL);

        rLock.lock();
        try {
            return group == null ? 0 : group.size();
        } finally {
            rLock.unlock();
        }
    }

    private void groupAndSessionCheck(String groupName, WebSocketSession session) {
        groupNameCheck(groupName);
        if (session == null)
            throw new NullPointerException("No web socket session");
    }

    private void groupNameCheck(String groupName) {
        if (isEmpty(groupName))
            throw new NullPointerException("No groupName");
    }

    private void checkMessage(String message) {
        if (isEmpty(message))
            fail("No message");
    }

    private void checkMessage(byte[] message) {
        if (message == null || message.length == 0)
            fail("No message");
    }

}