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
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

12
package org.jivesoftware.openfire.multiplex;
Gaston Dombiak's avatar
Gaston Dombiak committed
13

14 15 16 17 18 19 20
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.event.SessionEventDispatcher;
import org.jivesoftware.openfire.event.SessionEventListener;
import org.jivesoftware.openfire.session.ConnectionMultiplexerSession;
21
import org.jivesoftware.openfire.session.LocalClientSession;
22
import org.jivesoftware.openfire.session.Session;
23 24 25 26
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.TaskEngine;
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
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
     */
63 64
    private Map<String, Map<String, LocalClientSession>> sessionsByManager =
            new ConcurrentHashMap<String, Map<String, LocalClientSession>>();
Gaston Dombiak's avatar
Gaston Dombiak committed
65 66 67 68 69 70 71 72 73 74 75 76

    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
                try {
106 107
                    for (ConnectionMultiplexerSession session : sessionManager.getConnectionMultiplexerSessions()) {
                        session.deliverRawText(" ");
108 109
                    }
                }
110 111 112
                catch(Exception e) {
                    Log.error(e);
                }
113 114
            }
        };
115
        TaskEngine.getInstance().schedule(heartbeatTask, 30*JiveConstants.SECOND, 30*JiveConstants.SECOND);
Gaston Dombiak's avatar
Gaston Dombiak committed
116 117 118 119 120 121 122 123 124 125 126
    }

    /**
     * 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) {
127 128
        // TODO Consider that client session may return null when IP address is forbidden
        Connection connection = new ClientSessionConnection(connectionManagerDomain);
129 130
        LocalClientSession session =
                SessionManager.getInstance().createClientSession(connection, new BasicStreamID(streamID));
131 132 133
        // 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
134
        Map<String, LocalClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
135 136 137 138
        if (sessions == null) {
            synchronized (connectionManagerDomain.intern()) {
                sessions = sessionsByManager.get(connectionManagerDomain);
                if (sessions == null) {
139
                    sessions = new ConcurrentHashMap<String, LocalClientSession>();
140
                    sessionsByManager.put(connectionManagerDomain, sessions);
Gaston Dombiak's avatar
Gaston Dombiak committed
141 142 143
                }
            }
        }
144
        sessions.put(streamID, session);
Gaston Dombiak's avatar
Gaston Dombiak committed
145 146 147 148 149 150 151 152 153 154
    }

    /**
     * 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) {
155
        Map<String, LocalClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
Gaston Dombiak's avatar
Gaston Dombiak committed
156 157 158 159
        if (sessions != null) {
            Session session = sessions.remove(streamID);
            if (session != null) {
                // Close the session
160
                session.close();
Gaston Dombiak's avatar
Gaston Dombiak committed
161 162 163 164 165
            }
        }
    }

    /**
166 167 168 169 170 171 172 173
     * 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
174
        Map<String, LocalClientSession> sessions = sessionsByManager.get(connectionManagerName);
175 176 177 178
        if (sessions == null) {
            synchronized (connectionManagerName.intern()) {
                sessions = sessionsByManager.get(connectionManagerName);
                if (sessions == null) {
179
                    sessions = new ConcurrentHashMap<String, LocalClientSession>();
180 181 182 183 184 185 186 187 188
                    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
189
     *
190
     * @param connectionManagerName the connection manager that is no longer available.
Gaston Dombiak's avatar
Gaston Dombiak committed
191
     */
192
    public void multiplexerUnavailable(String connectionManagerName) {
Gaston Dombiak's avatar
Gaston Dombiak committed
193
        // Remove the connection manager and the hosted sessions
194
        Map<String, LocalClientSession> sessions = sessionsByManager.remove(connectionManagerName);
Gaston Dombiak's avatar
Gaston Dombiak committed
195 196 197 198 199
        if (sessions != null) {
            for (String streamID : sessions.keySet()) {
                // Remove inverse track of connection manager hosting streamIDs
                streamIDs.remove(streamID);
                // Close the session
200
                sessions.get(streamID).close();
Gaston Dombiak's avatar
Gaston Dombiak committed
201 202 203 204 205 206 207 208 209 210 211 212 213
            }
        }
    }

    /**
     * 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.
     */
214 215
    public LocalClientSession getClientSession(String connectionManagerDomain, String streamID) {
        Map<String, LocalClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
Gaston Dombiak's avatar
Gaston Dombiak committed
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
        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()));
        }
    }

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
    /**
     * 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) {
261
        Map<String, LocalClientSession> clients = sessionsByManager.get(managerName);
262 263 264 265 266 267 268 269
        if (clients == null) {
            return 0;
        }
        else {
            return clients.size();
        }
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
    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) {
292
            Map<String, LocalClientSession> sessions = sessionsByManager.get(connectionManagerDomain);
Gaston Dombiak's avatar
Gaston Dombiak committed
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
            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();
        }
    }
}