ConnectionMultiplexerManager.java 12.4 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5
/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
6
 * Copyright (C) 2007 Jive Software. All rights reserved.
Gaston Dombiak's avatar
Gaston Dombiak committed
7 8 9 10 11 12 13
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.wildfire.multiplex;

14
import org.jivesoftware.util.JiveConstants;
15
import org.jivesoftware.util.JiveGlobals;
Gaston Dombiak's avatar
Gaston Dombiak committed
16
import org.jivesoftware.util.Log;
17
import org.jivesoftware.util.TaskEngine;
18 19 20 21
import org.jivesoftware.wildfire.Connection;
import org.jivesoftware.wildfire.SessionManager;
import org.jivesoftware.wildfire.StreamID;
import org.jivesoftware.wildfire.XMPPServer;
Gaston Dombiak's avatar
Gaston Dombiak committed
22 23
import org.jivesoftware.wildfire.event.SessionEventDispatcher;
import org.jivesoftware.wildfire.event.SessionEventListener;
24 25 26
import org.jivesoftware.wildfire.session.ClientSession;
import org.jivesoftware.wildfire.session.ConnectionMultiplexerSession;
import org.jivesoftware.wildfire.session.Session;
Gaston Dombiak's avatar
Gaston Dombiak committed
27

28
import java.util.*;
Gaston Dombiak's avatar
Gaston Dombiak committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
import java.util.concurrent.ConcurrentHashMap;

/**
 * A ConnectionMultiplexerManager is responsible for keeping track of the connected
 * Connection Managers and the sessions that were established with the Connection
 * Managers. Moreover, a ConnectionMultiplexerManager is able to create, get and close
 * client sessions based on Connection requests.
 *
 * @author Gaston Dombiak
 */
public class ConnectionMultiplexerManager implements SessionEventListener {

    private static final ConnectionMultiplexerManager instance = new ConnectionMultiplexerManager();

    /**
     * Pseudo-random number generator object for use with getMultiplexerSession(String).
     */
    private static Random randGen = new Random();

    static {
        // Add the unique instance of this class as a session listener. We need to react
        // when sessions are closed so we can clean up the registry of client sessions.
        SessionEventDispatcher.addListener(instance);
    }

    /**
     * Map that keeps track of connection managers and hosted connections.
     * Key: stream ID; Value: Domain of connection manager hosting connection
     */
    private Map<String, String> streamIDs = new ConcurrentHashMap<String, String>();
    /**
     * Map that keeps track of connection managers and hosted sessions.
     * Key: Domain of connection manager; Value: Map with Key: stream ID; Value: Client session
     */
    private Map<String, Map<String, ClientSession>> sessionsByManager =
            new ConcurrentHashMap<String, Map<String, ClientSession>>();

    private SessionManager sessionManager;

    /**
     * Returns the unique instance of this class.
     *
     * @return the unique instance of this class.
     */
    public static ConnectionMultiplexerManager getInstance() {
        return instance;
    }

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
    /**
     * Returns the default secret key that connection managers should present while trying to
     * establish a new connection.
     *
     * @return the default secret key that connection managers should present while trying to
     *         establish a new connection.
     */
    public static String getDefaultSecret() {
        return JiveGlobals.getProperty("xmpp.multiplex.defaultSecret");
    }

    /**
     * Sets the default secret key that connection managers should present while trying to
     * establish a new connection.
     *
     * @param defaultSecret the default secret key that connection managers should present
     *        while trying to establish a new connection.
     */
    public static void setDefaultSecret(String defaultSecret) {
        JiveGlobals.setProperty("xmpp.multiplex.defaultSecret", defaultSecret);
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
99 100
    private ConnectionMultiplexerManager() {
        sessionManager = XMPPServer.getInstance().getSessionManager();
101 102
        // Start thread that will send heartbeats to Connection Managers every 30 seconds
        // to keep connections open.
103
        TimerTask heartbeatTask = new TimerTask() {
104
            public void run() {
105 106 107 108 109
                try {
                    for (ConnectionMultiplexerSession session : sessionManager
                            .getConnectionMultiplexerSessions())
                    {
                        session.getConnection().deliverRawText(" ");
110 111
                    }
                }
112 113 114
                catch(Exception e) {
                    Log.error(e);
                }
115 116
            }
        };
117 118
        TaskEngine.getInstance().schedule(heartbeatTask, 30*JiveConstants.SECOND,
                30*JiveConstants.SECOND);
Gaston Dombiak's avatar
Gaston Dombiak committed
119 120 121 122 123 124 125 126 127 128 129
    }

    /**
     * Creates a new client session that was established to the specified connection manager.
     * The new session will not be findable through its stream ID.
     *
     * @param connectionManagerDomain the connection manager that is handling the connection
     *        of the session.
     * @param streamID the stream ID created by the connection manager for the new session.
     */
    public void createClientSession(String connectionManagerDomain, String streamID) {
130 131 132 133 134 135 136 137 138 139 140 141 142 143
        // TODO Consider that client session may return null when IP address is forbidden
        Connection connection = new ClientSessionConnection(connectionManagerDomain);
        ClientSession session = SessionManager.getInstance()
                .createClientSession(connection, new BasicStreamID(streamID));
        // Register that this streamID belongs to the specified connection manager
        streamIDs.put(streamID, connectionManagerDomain);
        // Register which sessions are being hosted by the speicifed connection manager
        Map<String, ClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
        if (sessions == null) {
            synchronized (connectionManagerDomain.intern()) {
                sessions = sessionsByManager.get(connectionManagerDomain);
                if (sessions == null) {
                    sessions = new ConcurrentHashMap<String, ClientSession>();
                    sessionsByManager.put(connectionManagerDomain, sessions);
Gaston Dombiak's avatar
Gaston Dombiak committed
144 145 146
                }
            }
        }
147
        sessions.put(streamID, session);
Gaston Dombiak's avatar
Gaston Dombiak committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    }

    /**
     * Closes an existing client session that was established through a connection manager.
     *
     * @param connectionManagerDomain the connection manager that is handling the connection
     *        of the session.
     * @param streamID the stream ID created by the connection manager for the session.
     */
    public void closeClientSession(String connectionManagerDomain, String streamID) {
        Map<String, ClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
        if (sessions != null) {
            Session session = sessions.remove(streamID);
            if (session != null) {
                // Close the session
                session.getConnection().close();
            }
        }
    }

    /**
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
     * A connection manager has become available. Clients can now connect to the server through
     * the connection manager.
     *
     * @param connectionManagerName the connection manager that has become available.
     */
    public void multiplexerAvailable(String connectionManagerName) {
        // Add a new entry in the list of available managers. Here is where we are going to store
        // which clients were connected through which connection manager
        Map<String, ClientSession> sessions = sessionsByManager.get(connectionManagerName);
        if (sessions == null) {
            synchronized (connectionManagerName.intern()) {
                sessions = sessionsByManager.get(connectionManagerName);
                if (sessions == null) {
                    sessions = new ConcurrentHashMap<String, ClientSession>();
                    sessionsByManager.put(connectionManagerName, sessions);
                }
            }
        }
    }

    /**
     * A connection manager has gone unavailable. Close client sessions that were established
     * to the specified connection manager.
Gaston Dombiak's avatar
Gaston Dombiak committed
192
     *
193
     * @param connectionManagerName the connection manager that is no longer available.
Gaston Dombiak's avatar
Gaston Dombiak committed
194
     */
195
    public void multiplexerUnavailable(String connectionManagerName) {
Gaston Dombiak's avatar
Gaston Dombiak committed
196
        // Remove the connection manager and the hosted sessions
197
        Map<String, ClientSession> sessions = sessionsByManager.remove(connectionManagerName);
Gaston Dombiak's avatar
Gaston Dombiak committed
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
        if (sessions != null) {
            for (String streamID : sessions.keySet()) {
                // Remove inverse track of connection manager hosting streamIDs
                streamIDs.remove(streamID);
                // Close the session
                sessions.get(streamID).getConnection().close();
            }
        }
    }

    /**
     * Returns the ClientSession with the specified stream ID that is being hosted by the
     * specified connection manager.
     *
     * @param connectionManagerDomain the connection manager that is handling the connection
     *        of the session.
     * @param streamID the stream ID created by the connection manager for the session.
     * @return the ClientSession with the specified stream ID.
     */
    public ClientSession getClientSession(String connectionManagerDomain, String streamID) {
        Map<String, ClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
        if (sessions != null) {
            return sessions.get(streamID);
        }
        return null;
    }

    /**
     * Returns a {@link ConnectionMultiplexerSession} for the specified connection manager
     * domain or <tt>null</tt> if none was found. In case the connection manager has many
     * connections established with the server then one of them will be selected randomly.
     *
     * @param connectionManagerDomain the domain of the connection manager to get a session.
     * @return a session to the specified connection manager domain or null if none was found.
     */
    public ConnectionMultiplexerSession getMultiplexerSession(String connectionManagerDomain) {
        List<ConnectionMultiplexerSession> sessions =
                sessionManager.getConnectionMultiplexerSessions(connectionManagerDomain);
        if (sessions.isEmpty()) {
            return null;
        }
        else if (sessions.size() == 1) {
            return sessions.get(0);
        }
        else {
            // Pick a random session so we can distribute traffic evenly
            return sessions.get(randGen.nextInt(sessions.size()));
        }
    }

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
    /**
     * Returns the names of the connected connection managers to this server.
     *
     * @return the names of the connected connection managers to this server.
     */
    public Collection<String> getMultiplexers() {
        return sessionsByManager.keySet();
    }

    /**
     * Returns the number of connected clients to a specific connection manager.
     *
     * @param managerName the name of the connection manager.
     * @return the number of connected clients to a specific connection manager.
     */
    public int getNumConnectedClients(String managerName) {
        Map<String, ClientSession> clients = sessionsByManager.get(managerName);
        if (clients == null) {
            return 0;
        }
        else {
            return clients.size();
        }
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
    public void anonymousSessionCreated(Session session) {
        // Do nothing.
    }

    public void anonymousSessionDestroyed(Session session) {
        removeSession(session);
    }

    public void sessionCreated(Session session) {
        // Do nothing.
    }

    public void sessionDestroyed(Session session) {
        removeSession(session);
    }

    private void removeSession(Session session) {
        // Remove trace indicating that a connection manager is hosting a connection
        String streamID = session.getStreamID().getID();
        String connectionManagerDomain = streamIDs.remove(streamID);
        // Remove trace indicating that a connection manager is hosting a session
        if (connectionManagerDomain != null) {
            Map<String, ClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
            if (sessions != null) {
                sessions.remove(streamID);
            }
        }
    }

    /**
     * Simple implementation of the StreamID interface to hold the stream ID assigned by
     * the Connection Manager to the Session.
     */
    private class BasicStreamID implements StreamID {
        String id;

        public BasicStreamID(String id) {
            this.id = id;
        }

        public String getID() {
            return id;
        }

        public String toString() {
            return id;
        }

        public int hashCode() {
            return id.hashCode();
        }
    }
}