RoutingTableImpl.java 33.9 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile: RoutingTableImpl.java,v $
 * $Revision: 3138 $
 * $Date: 2005-12-01 02:13:26 -0300 (Thu, 01 Dec 2005) $
 *
6
 * Copyright (C) 2007 Jive Software. All rights reserved.
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.spi;
13

Gaston Dombiak's avatar
Gaston Dombiak committed
14 15
import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.auth.UnauthorizedException;
16 17
import org.jivesoftware.openfire.cluster.ClusterEventListener;
import org.jivesoftware.openfire.cluster.ClusterManager;
18
import org.jivesoftware.openfire.cluster.NodeID;
19
import org.jivesoftware.openfire.container.BasicModule;
20
import org.jivesoftware.openfire.handler.PresenceUpdateHandler;
21
import org.jivesoftware.openfire.server.OutgoingSessionPromise;
22
import org.jivesoftware.openfire.session.*;
23
import org.jivesoftware.util.ConcurrentHashSet;
24
import org.jivesoftware.util.JiveGlobals;
Gaston Dombiak's avatar
Gaston Dombiak committed
25 26 27 28
import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.packet.*;
29

30
import java.util.*;
Gaston Dombiak's avatar
Gaston Dombiak committed
31
import java.util.concurrent.locks.Lock;
32 33

/**
Gaston Dombiak's avatar
Gaston Dombiak committed
34 35 36 37 38 39 40 41
 * Routing table that stores routes to client sessions, outgoing server sessions
 * and components. As soon as a user authenticates with the server its client session
 * will be added to the routing table. Whenever the client session becomes available
 * or unavailable the routing table will be updated too.<p>
 *
 * When running inside of a cluster the routing table will also keep references to routes
 * hosted in other cluster nodes. A {@link RemotePacketRouter} will be use to route packets
 * to routes hosted in other cluster nodes.<p>
42
 *
43 44 45
 * Failure to route a packet will end up sending {@link IQRouter#routingFailed(JID, Packet)},
 * {@link MessageRouter#routingFailed(JID, Packet)} or {@link PresenceRouter#routingFailed(JID, Packet)}
 * depending on the packet type that tried to be sent.
Gaston Dombiak's avatar
Gaston Dombiak committed
46 47
 *
 * @author Gaston Dombiak
48
 */
49 50 51 52 53 54
public class RoutingTableImpl extends BasicModule implements RoutingTable, ClusterEventListener {

    public static final String C2S_CACHE_NAME = "Routing Users Cache";
    public static final String ANONYMOUS_C2S_CACHE_NAME = "Routing AnonymousUsers Cache";
    public static final String S2S_CACHE_NAME = "Routing Servers Cache";
    public static final String COMPONENT_CACHE_NAME = "Routing Components Cache";
55 56

    /**
Gaston Dombiak's avatar
Gaston Dombiak committed
57
     * Cache (unlimited, never expire) that holds outgoing sessions to remote servers from this server.
58
     * Key: server domain, Value: nodeID
Gaston Dombiak's avatar
Gaston Dombiak committed
59 60 61
     */
    private Cache<String, byte[]> serversCache;
    /**
62 63
     * Cache (unlimited, never expire) that holds components connected to the server.
     * Key: component domain, Value: list of nodeIDs hosting the component
64
     */
65
    private Cache<String, Set<NodeID>> componentsCache;
Gaston Dombiak's avatar
Gaston Dombiak committed
66 67
    /**
     * Cache (unlimited, never expire) that holds sessions of user that have authenticated with the server.
68
     * Key: full JID, Value: {nodeID, available/unavailable}
Gaston Dombiak's avatar
Gaston Dombiak committed
69 70 71
     */
    private Cache<String, ClientRoute> usersCache;
    /**
72
     * Cache (unlimited, never expire) that holds sessions of anonymous user that have authenticated with the server.
73
     * Key: full JID, Value: {nodeID, available/unavailable}
Gaston Dombiak's avatar
Gaston Dombiak committed
74 75 76 77
     */
    private Cache<String, ClientRoute> anonymousUsersCache;
    /**
     * Cache (unlimited, never expire) that holds list of connected resources of authenticated users
78 79
     * (includes anonymous).
     * Key: bare JID, Value: list of full JIDs of the user
Gaston Dombiak's avatar
Gaston Dombiak committed
80
     */
81
    private Cache<String, Collection<String>> usersSessions;
82 83

    private String serverName;
Gaston Dombiak's avatar
Gaston Dombiak committed
84 85 86 87 88 89
    private XMPPServer server;
    private LocalRoutingTable localRoutingTable;
    private RemotePacketRouter remotePacketRouter;
    private IQRouter iqRouter;
    private MessageRouter messageRouter;
    private PresenceRouter presenceRouter;
90
    private PresenceUpdateHandler presenceUpdateHandler;
91 92 93

    public RoutingTableImpl() {
        super("Routing table");
94 95 96 97
        serversCache = CacheFactory.createCache(S2S_CACHE_NAME);
        componentsCache = CacheFactory.createCache(COMPONENT_CACHE_NAME);
        usersCache = CacheFactory.createCache(C2S_CACHE_NAME);
        anonymousUsersCache = CacheFactory.createCache(ANONYMOUS_C2S_CACHE_NAME);
Gaston Dombiak's avatar
Gaston Dombiak committed
98 99
        usersSessions = CacheFactory.createCache("Routing User Sessions");
        localRoutingTable = new LocalRoutingTable();
100 101
    }

102
    public void addServerRoute(JID route, LocalOutgoingServerSession destination) {
103
        String address = route.getDomain();
Gaston Dombiak's avatar
Gaston Dombiak committed
104
        localRoutingTable.addRoute(address, destination);
105 106 107 108 109 110 111 112
        Lock lock = CacheFactory.getLock(address, serversCache);
        try {
            lock.lock();
            serversCache.put(address, server.getNodeID().toByteArray());
        }
        finally {
            lock.unlock();
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
113
    }
114

Gaston Dombiak's avatar
Gaston Dombiak committed
115
    public void addComponentRoute(JID route, RoutableChannelHandler destination) {
116
        String address = route.getDomain();
Gaston Dombiak's avatar
Gaston Dombiak committed
117
        localRoutingTable.addRoute(address, destination);
118
        Lock lock = CacheFactory.getLock(address, componentsCache);
119 120
        try {
            lock.lock();
121
            Set<NodeID> nodes = componentsCache.get(address);
122
            if (nodes == null) {
123
                nodes = new HashSet<NodeID>();
124 125 126 127 128 129
            }
            nodes.add(server.getNodeID());
            componentsCache.put(address, nodes);
        } finally {
            lock.unlock();
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
130
    }
131

132
    public boolean addClientRoute(JID route, LocalClientSession destination) {
133
        boolean added;
Gaston Dombiak's avatar
Gaston Dombiak committed
134
        boolean available = destination.getPresence().isAvailable();
135
        localRoutingTable.addRoute(route.toString(), destination);
Gaston Dombiak's avatar
Gaston Dombiak committed
136
        if (destination.getAuthToken().isAnonymous()) {
137 138 139 140 141 142 143 144 145
            Lock lockAn = CacheFactory.getLock(route.toString(), anonymousUsersCache);
            try {
                lockAn.lock();
                added = anonymousUsersCache.put(route.toString(), new ClientRoute(server.getNodeID(), available)) ==
                        null;
            }
            finally {
                lockAn.unlock();
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
146
            // Add the session to the list of user sessions
147
            if (route.getResource() != null && (!available || added)) {
148
                Lock lock = CacheFactory.getLock(route.toBareJID(), usersSessions);
Gaston Dombiak's avatar
Gaston Dombiak committed
149 150 151
                try {
                    lock.lock();
                    usersSessions.put(route.toBareJID(), Arrays.asList(route.toString()));
152
                }
Gaston Dombiak's avatar
Gaston Dombiak committed
153 154
                finally {
                    lock.unlock();
155
                }
156 157
            }
        }
158
        else {
159 160 161 162 163 164 165 166
            Lock lockU = CacheFactory.getLock(route.toString(), usersCache);
            try {
                lockU.lock();
                added = usersCache.put(route.toString(), new ClientRoute(server.getNodeID(), available)) == null;
            }
            finally {
                lockU.unlock();
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
167
            // Add the session to the list of user sessions
168
            if (route.getResource() != null && (!available || added)) {
169
                Lock lock = CacheFactory.getLock(route.toBareJID(), usersSessions);
Gaston Dombiak's avatar
Gaston Dombiak committed
170 171
                try {
                    lock.lock();
172
                    Collection<String> jids = usersSessions.get(route.toBareJID());
Gaston Dombiak's avatar
Gaston Dombiak committed
173
                    if (jids == null) {
174 175 176 177 178 179 180
                        // Optimization - use different class depending on current setup
                        if (ClusterManager.isClusteringStarted()) {
                            jids = new HashSet<String>();
                        }
                        else {
                            jids = new ConcurrentHashSet<String>();
                        }
Gaston Dombiak's avatar
Gaston Dombiak committed
181 182 183 184 185 186 187 188
                    }
                    jids.add(route.toString());
                    usersSessions.put(route.toBareJID(), jids);
                }
                finally {
                    lock.unlock();
                }
            }
189
        }
190
        return added;
191 192
    }

193 194
    public void broadcastPacket(Message packet, boolean onlyLocal) {
        // Send the message to client sessions connected to this JVM
195 196
        for(ClientSession session : localRoutingTable.getClientRoutes()) {
            session.process(packet);
197 198 199 200 201 202 203 204
        }

        // Check if we need to broadcast the message to client sessions connected to remote cluter nodes
        if (!onlyLocal && remotePacketRouter != null) {
            remotePacketRouter.broadcastPacket(packet);
        }
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
205
    public void routePacket(JID jid, Packet packet, boolean fromServer) throws PacketException {
Gaston Dombiak's avatar
Gaston Dombiak committed
206 207
        boolean routed = false;
        if (serverName.equals(jid.getDomain())) {
208 209 210 211 212
            if (jid.getResource() == null) {
                // Packet sent to a bare JID of a user
                if (packet instanceof Message) {
                    // Find best route of local user
                    routed = routeToBareJID(jid, (Message) packet);
213
                }
Gaston Dombiak's avatar
Gaston Dombiak committed
214
                else {
215 216 217 218 219 220 221 222 223 224
                    throw new PacketException("Cannot route packet of type IQ or Presence to bare JID: " + packet);
                }
            }
            else {
                // Packet sent to local user (full JID)
                ClientRoute clientRoute = usersCache.get(jid.toString());
                if (clientRoute == null) {
                    clientRoute = anonymousUsersCache.get(jid.toString());
                }
                if (clientRoute != null) {
225 226
                    if (!clientRoute.isAvailable() && routeOnlyAvailable(packet, fromServer) &&
                            !presenceUpdateHandler.hasDirectPresence(packet.getTo(), packet.getFrom())) {
227 228
                        // Packet should only be sent to available sessions and the route is not available
                        routed = false;
Gaston Dombiak's avatar
Gaston Dombiak committed
229 230
                    }
                    else {
231
                        if (server.getNodeID().equals(clientRoute.getNodeID())) {
232 233 234 235 236 237 238 239 240 241 242
                            // This is a route to a local user hosted in this node
                            try {
                                localRoutingTable.getRoute(jid.toString()).process(packet);
                                routed = true;
                            } catch (UnauthorizedException e) {
                                Log.error(e);
                            }
                        }
                        else {
                            // This is a route to a local user hosted in other node
                            if (remotePacketRouter != null) {
Gaston Dombiak's avatar
Gaston Dombiak committed
243 244
                                routed = remotePacketRouter
                                        .routePacket(clientRoute.getNodeID().toByteArray(), jid, packet);
245
                            }
Gaston Dombiak's avatar
Gaston Dombiak committed
246 247 248 249 250 251 252
                        }
                    }
                }
            }
        }
        else if (jid.getDomain().contains(serverName)) {
            // Packet sent to component hosted in this server
253 254 255 256 257 258 259 260
            // First check if the component is being hosted in this JVM
            RoutableChannelHandler route = localRoutingTable.getRoute(jid.getDomain());
            if (route != null) {
                try {
                    route.process(packet);
                    routed = true;
                } catch (UnauthorizedException e) {
                    Log.error(e);
Gaston Dombiak's avatar
Gaston Dombiak committed
261
                }
262 263 264
            }
            else {
                // Check if other cluster nodes are hosting this component
265
                Set<NodeID> nodes = componentsCache.get(jid.getDomain());
266
                if (nodes != null) {
267 268
                    for (NodeID nodeID : nodes) {
                        if (server.getNodeID().equals(nodeID)) {
269 270 271 272 273 274 275 276 277 278 279 280 281
                            // This is a route to a local component hosted in this node (route
                            // could have been added after our previous check)
                            try {
                                localRoutingTable.getRoute(jid.getDomain()).process(packet);
                                routed = true;
                                break;
                            } catch (UnauthorizedException e) {
                                Log.error(e);
                            }
                        }
                        else {
                            // This is a route to a local component hosted in other node
                            if (remotePacketRouter != null) {
282
                                routed = remotePacketRouter.routePacket(nodeID.toByteArray(), jid, packet);
283 284 285 286 287
                                if (routed) {
                                    break;
                                }
                            }
                        }
Gaston Dombiak's avatar
Gaston Dombiak committed
288 289 290 291 292 293 294 295
                    }
                }
            }
        }
        else {
            // Packet sent to remote server
            byte[] nodeID = serversCache.get(jid.getDomain());
            if (nodeID != null) {
296
                if (server.getNodeID().equals(nodeID)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
297
                    // This is a route to a remote server connected from this node
298 299 300 301 302 303
                    try {
                        localRoutingTable.getRoute(jid.getDomain()).process(packet);
                        routed = true;
                    } catch (UnauthorizedException e) {
                        Log.error(e);
                    }
304 305
                }
                else {
Gaston Dombiak's avatar
Gaston Dombiak committed
306 307 308 309
                    // This is a route to a remote server connected from other node
                    if (remotePacketRouter != null) {
                        routed = remotePacketRouter.routePacket(nodeID, jid, packet);
                    }
310 311
                }
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
312 313 314 315 316 317
            else {
                // Return a promise of a remote session. This object will queue packets pending
                // to be sent to remote servers
                OutgoingSessionPromise.getInstance().process(packet);
                routed = true;
            }
318
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
319 320

        if (!routed) {
321
            if (Log.isDebugEnabled()) {
322
                Log.debug("RoutingTableImpl: Failed to route packet to JID: " + jid + " packet: " + packet);
Gaston Dombiak's avatar
Gaston Dombiak committed
323 324
            }
            if (packet instanceof IQ) {
325
                iqRouter.routingFailed(jid, packet);
Gaston Dombiak's avatar
Gaston Dombiak committed
326 327
            }
            else if (packet instanceof Message) {
328
                messageRouter.routingFailed(jid, packet);
Gaston Dombiak's avatar
Gaston Dombiak committed
329 330
            }
            else if (packet instanceof Presence) {
331
                presenceRouter.routingFailed(jid, packet);
332 333
            }
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
334
    }
335

336 337 338 339
    /**
     * Returns true if the specified packet must only be route to available client sessions.
     *
     * @param packet the packet to route.
Gaston Dombiak's avatar
Gaston Dombiak committed
340
     * @param fromServer true if the packet was created by the server.
341 342
     * @return true if the specified packet must only be route to available client sessions.
     */
Gaston Dombiak's avatar
Gaston Dombiak committed
343 344 345 346 347 348
    private boolean routeOnlyAvailable(Packet packet, boolean fromServer) {
        if (fromServer) {
            // Packets created by the server (no matter their FROM value) must always be delivered no
            // matter the available presence of the user
            return false;
        }
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
        boolean onlyAvailable = true;
        JID from = packet.getFrom();
        boolean hasSender = from != null;
        if (packet instanceof IQ) {
            onlyAvailable = hasSender && !(serverName.equals(from.getDomain()) && from.getResource() == null) &&
                    !componentsCache.containsKey(from.toString());
        }
        else if (packet instanceof Message) {
            onlyAvailable = !hasSender ||
                    (!serverName.equals(from.toString()) && !componentsCache.containsKey(from.toString()));
        }
        else if (packet instanceof Presence) {
            onlyAvailable = !hasSender ||
                    (!serverName.equals(from.toString()) && !componentsCache.containsKey(from.toString()));
        }
        return onlyAvailable;
    }

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
    /**
     * Deliver the message sent to the bare JID of a local user to the best connected resource. If the
     * target user is not online then messages will be stored offline according to the offline strategy.
     * However, if the user is connected from only one resource then the message will be delivered to
     * that resource. In the case that the user is connected from many resources the logic will be the
     * following:
     * <ol>
     *  <li>Select resources with highest priority</li>
     *  <li>Select resources with highest show value (chat, available, away, xa, dnd)</li>
     *  <li>Select resource with most recent activity</li>
     * </ol>
     *
     * Admins can override the above logic and just send the message to all connected resources
     * with highest priority by setting the system property <tt>route.all-resources</tt> to
     * <tt>true</tt>.
     *
     * @param recipientJID the bare JID of the target local user.
     * @param packet the message to send.
     * @return true if at least one target session was found
     */
    private boolean routeToBareJID(JID recipientJID, Message packet) {
        List<ClientSession> sessions = new ArrayList<ClientSession>();
389 390
        // Get existing AVAILABLE sessions of this user or AVAILABLE to the sender of the packet
        for (JID address : getRoutes(recipientJID, packet.getFrom())) {
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
            ClientSession session = getClientRoute(address);
            if (session != null) {
                sessions.add(session);
            }
        }
        sessions = getHighestPrioritySessions(sessions);
        if (sessions.isEmpty()) {
            // No session is available so store offline
            return false;
        }
        else if (sessions.size() == 1) {
            // Found only one session so deliver message
            sessions.get(0).process(packet);
        }
        else {
            // Many sessions have the highest priority (be smart now) :)
            if (!JiveGlobals.getBooleanProperty("route.all-resources", false)) {
                // Sort sessions by show value (e.g. away, xa)
                Collections.sort(sessions, new Comparator<ClientSession>() {

                    public int compare(ClientSession o1, ClientSession o2) {
                        int thisVal = getShowValue(o1);
                        int anotherVal = getShowValue(o2);
                        return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
                    }

                    /**
                     * Priorities are: chat, available, away, xa, dnd.
                     */
                    private int getShowValue(ClientSession session) {
                        Presence.Show show = session.getPresence().getShow();
                        if (show == Presence.Show.chat) {
                            return 1;
                        }
                        else if (show == null) {
                            return 2;
                        }
                        else if (show == Presence.Show.away) {
                            return 3;
                        }
                        else if (show == Presence.Show.xa) {
                            return 4;
                        }
                        else {
                            return 5;
                        }
                    }
                });

                // Get same sessions with same max show value
                List<ClientSession> targets = new ArrayList<ClientSession>();
                Presence.Show showFilter = sessions.get(0).getPresence().getShow();
                for (ClientSession session : sessions) {
                    if (session.getPresence().getShow() == showFilter) {
                        targets.add(session);
                    }
                    else {
                        break;
                    }
                }

                // Get session with most recent activity (and highest show value)
                Collections.sort(targets, new Comparator<ClientSession>() {
                    public int compare(ClientSession o1, ClientSession o2) {
455
                        return o2.getLastActiveDate().compareTo(o1.getLastActiveDate());
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
                    }
                });
                // Deliver stanza to session with highest priority, highest show value and most recent activity
                targets.get(0).process(packet);
            }
            else {
                // Deliver stanza to all connected resources with highest priority
                for (ClientSession session : sessions) {
                    session.process(packet);
                }
            }
        }
        return true;
    }

    /**
     * Returns the sessions that had the highest presence priority greater than zero.
     *
     * @param sessions the list of user sessions that filter and get the ones with highest priority.
     * @return the sessions that had the highest presence priority greater than zero or empty collection
     *         if all were negative.
     */
    private List<ClientSession> getHighestPrioritySessions(List<ClientSession> sessions) {
        int highest = Integer.MIN_VALUE;
        // Get the highest priority amongst the sessions
        for (ClientSession session : sessions) {
            int priority = session.getPresence().getPriority();
            if (priority >= 0 && priority > highest) {
                highest = priority;
            }
        }
        // Answer an empty collection if all have negative priority
        if (highest == Integer.MIN_VALUE) {
            return Collections.emptyList();
        }
        // Get sessions that have the highest priority
        List<ClientSession> answer = new ArrayList<ClientSession>(sessions.size());
        for (ClientSession session : sessions) {
            if (session.getPresence().getPriority() == highest) {
                answer.add(session);
            }
        }
        return answer;
    }

    public ClientSession getClientRoute(JID jid) {
        // Check if this session is hosted by this cluster node
        ClientSession session = (ClientSession) localRoutingTable.getRoute(jid.toString());
        if (session == null) {
            // The session is not in this JVM so assume remote
            RemoteSessionLocator locator = server.getRemoteSessionLocator();
            if (locator != null) {
                // Check if the session is hosted by other cluster node
                ClientRoute route = usersCache.get(jid.toString());
                if (route == null) {
                    route = anonymousUsersCache.get(jid.toString());
                }
                if (route != null) {
Gaston Dombiak's avatar
Gaston Dombiak committed
514
                    session = locator.getClientSession(route.getNodeID().toByteArray(), jid);
515 516 517 518 519 520
                }
            }
        }
        return session;
    }

521
    public Collection<ClientSession> getClientsRoutes(boolean onlyLocal) {
522 523
        // Add sessions hosted by this cluster node
        Collection<ClientSession> sessions = new ArrayList<ClientSession>(localRoutingTable.getClientRoutes());
524 525 526 527 528 529 530
        if (!onlyLocal) {
            // Add sessions not hosted by this JVM
            RemoteSessionLocator locator = server.getRemoteSessionLocator();
            if (locator != null) {
                // Add sessions of non-anonymous users hosted by other cluster nodes
                for (Map.Entry<String, ClientRoute> entry : usersCache.entrySet()) {
                    ClientRoute route = entry.getValue();
531
                    if (!server.getNodeID().equals(route.getNodeID())) {
Gaston Dombiak's avatar
Gaston Dombiak committed
532
                        sessions.add(locator.getClientSession(route.getNodeID().toByteArray(), new JID(entry.getKey())));
533
                    }
534
                }
535 536 537
                // Add sessions of anonymous users hosted by other cluster nodes
                for (Map.Entry<String, ClientRoute> entry : anonymousUsersCache.entrySet()) {
                    ClientRoute route = entry.getValue();
538
                    if (!server.getNodeID().equals(route.getNodeID())) {
Gaston Dombiak's avatar
Gaston Dombiak committed
539
                        sessions.add(locator.getClientSession(route.getNodeID().toByteArray(), new JID(entry.getKey())));
540
                    }
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
                }
            }
        }
        return sessions;
    }

    public OutgoingServerSession getServerRoute(JID jid) {
        // Check if this session is hosted by this cluster node
        OutgoingServerSession session = (OutgoingServerSession) localRoutingTable.getRoute(jid.getDomain());
        if (session == null) {
            // The session is not in this JVM so assume remote
            RemoteSessionLocator locator = server.getRemoteSessionLocator();
            if (locator != null) {
                // Check if the session is hosted by other cluster node
                byte[] nodeID = serversCache.get(jid.getDomain());
                if (nodeID != null) {
                    session = locator.getOutgoingServerSession(nodeID, jid);
                }
            }
        }
        return session;
    }

    public Collection<String> getServerHostnames() {
565
        return serversCache.keySet();
566 567
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
568 569 570 571
    public int getServerSessionsCount() {
        return localRoutingTable.getServerRoutes().size();
    }

572 573 574 575
    public Collection<String> getComponentsDomains() {
        return componentsCache.keySet();
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
576
    public boolean hasClientRoute(JID jid) {
577 578 579
        return usersCache.containsKey(jid.toString()) || isAnonymousRoute(jid);
    }

580
    public boolean isAnonymousRoute(JID jid) {
581
        return anonymousUsersCache.containsKey(jid.toString());
582 583
    }

584 585 586 587
    public boolean isLocalRoute(JID jid) {
        return localRoutingTable.isLocalRoute(jid);
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
588
    public boolean hasServerRoute(JID jid) {
589
        return serversCache.containsKey(jid.getDomain());
Gaston Dombiak's avatar
Gaston Dombiak committed
590
    }
591

Gaston Dombiak's avatar
Gaston Dombiak committed
592
    public boolean hasComponentRoute(JID jid) {
593
        return componentsCache.containsKey(jid.getDomain());
Gaston Dombiak's avatar
Gaston Dombiak committed
594 595
    }

596
    public List<JID> getRoutes(JID route, JID requester) {
Gaston Dombiak's avatar
Gaston Dombiak committed
597 598 599 600 601 602 603 604 605
        List<JID> jids = new ArrayList<JID>();
        if (serverName.equals(route.getDomain())) {
            // Address belongs to local user
            if (route.getResource() != null) {
                // Address is a full JID of a user
                ClientRoute clientRoute = usersCache.get(route.toString());
                if (clientRoute == null) {
                    clientRoute = anonymousUsersCache.get(route.toString());
                }
606 607
                if (clientRoute != null &&
                        (clientRoute.isAvailable() || presenceUpdateHandler.hasDirectPresence(route, requester))) {
Gaston Dombiak's avatar
Gaston Dombiak committed
608 609
                    jids.add(route);
                }
610 611
            }
            else {
Gaston Dombiak's avatar
Gaston Dombiak committed
612
                // Address is a bare JID so return all AVAILABLE resources of user
613
                Collection<String> sessions = usersSessions.get(route.toBareJID());
Gaston Dombiak's avatar
Gaston Dombiak committed
614 615 616 617 618 619 620
                if (sessions != null) {
                    // Select only available sessions
                    for (String jid : sessions) {
                        ClientRoute clientRoute = usersCache.get(jid);
                        if (clientRoute == null) {
                            clientRoute = anonymousUsersCache.get(jid);
                        }
621 622
                        if (clientRoute != null && (clientRoute.isAvailable() ||
                                presenceUpdateHandler.hasDirectPresence(new JID(jid), requester))) {
Gaston Dombiak's avatar
Gaston Dombiak committed
623 624
                            jids.add(new JID(jid));
                        }
625 626 627 628
                    }
                }
            }
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
629 630
        else if (route.getDomain().contains(serverName)) {
            // Packet sent to component hosted in this server
631
            if (componentsCache.containsKey(route.getDomain())) {
632
                jids.add(new JID(route.getDomain()));
Gaston Dombiak's avatar
Gaston Dombiak committed
633
            }
634 635
        }
        else {
Gaston Dombiak's avatar
Gaston Dombiak committed
636
            // Packet sent to remote server
Gaston Dombiak's avatar
Gaston Dombiak committed
637
            jids.add(route);
638
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
639
        return jids;
640 641
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
642 643 644
    public boolean removeClientRoute(JID route) {
        boolean anonymous = false;
        String address = route.toString();
645 646 647 648 649 650 651 652 653
        ClientRoute clientRoute = null;
        Lock lockU = CacheFactory.getLock(address, usersCache);
        try {
            lockU.lock();
            clientRoute = usersCache.remove(address);
        }
        finally {
            lockU.unlock();
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
654
        if (clientRoute == null) {
655 656 657 658 659 660 661 662 663
            Lock lockA = CacheFactory.getLock(address, anonymousUsersCache);
            try {
                lockA.lock();
                clientRoute = anonymousUsersCache.remove(address);
                anonymous = true;
            }
            finally {
                lockA.unlock();
            }
664
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
665
        if (clientRoute != null && route.getResource() != null) {
666
            Lock lock = CacheFactory.getLock(route.toBareJID(), usersSessions);
Gaston Dombiak's avatar
Gaston Dombiak committed
667 668 669 670
            try {
                lock.lock();
                if (anonymous) {
                    usersSessions.remove(route.toBareJID());
671 672
                }
                else {
673
                    Collection<String> jids = usersSessions.get(route.toBareJID());
Gaston Dombiak's avatar
Gaston Dombiak committed
674 675 676 677 678 679 680 681 682
                    if (jids != null) {
                        jids.remove(route.toString());
                        if (!jids.isEmpty()) {
                            usersSessions.put(route.toBareJID(), jids);
                        }
                        else {
                            usersSessions.remove(route.toBareJID());
                        }
                    }
683 684
                }
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
685 686
            finally {
                lock.unlock();
687 688
            }
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
689 690 691 692 693 694
        localRoutingTable.removeRoute(address);
        return clientRoute != null;
    }

    public boolean removeServerRoute(JID route) {
        String address = route.getDomain();
695 696 697 698 699 700 701 702 703
        boolean removed = false;
        Lock lock = CacheFactory.getLock(address, serversCache);
        try {
            lock.lock();
            removed = serversCache.remove(address) != null;
        }
        finally {
            lock.unlock();
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
704 705 706 707 708 709
        localRoutingTable.removeRoute(address);
        return removed;
    }

    public boolean removeComponentRoute(JID route) {
        String address = route.getDomain();
710
        boolean removed = false;
711
        Lock lock = CacheFactory.getLock(address, componentsCache);
712 713
        try {
            lock.lock();
714
            Set<NodeID> nodes = componentsCache.get(address);
715 716 717 718 719 720 721 722 723 724 725 726
            if (nodes != null) {
                removed = nodes.remove(server.getNodeID());
                if (nodes.isEmpty()) {
                    componentsCache.remove(address);
                }
                else {
                    componentsCache.put(address, nodes);
                }
            }
        } finally {
            lock.unlock();
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
727 728 729 730 731 732
        localRoutingTable.removeRoute(address);
        return removed;
    }

    public void setRemotePacketRouter(RemotePacketRouter remotePacketRouter) {
        this.remotePacketRouter = remotePacketRouter;
733 734
    }

735 736 737 738
    public RemotePacketRouter getRemotePacketRouter() {
        return remotePacketRouter;
    }

739 740
    public void initialize(XMPPServer server) {
        super.initialize(server);
Gaston Dombiak's avatar
Gaston Dombiak committed
741
        this.server = server;
742
        serverName = server.getServerInfo().getXMPPDomain();
Gaston Dombiak's avatar
Gaston Dombiak committed
743 744 745
        iqRouter = server.getIQRouter();
        messageRouter = server.getMessageRouter();
        presenceRouter = server.getPresenceRouter();
746
        presenceUpdateHandler = server.getPresenceUpdateHandler();
747 748
        // Listen to cluster events
        ClusterManager.addListener(this);
749
    }
750 751 752

    public void start() throws IllegalStateException {
        super.start();
753 754 755 756 757 758
        localRoutingTable.start();
    }

    public void stop() {
        super.stop();
        localRoutingTable.stop();
759
    }
760

761
    public void joinedCluster() {
762
        restoreCacheContent();
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777

        // Broadcast presence of local sessions to remote sessions when subscribed to presence
        // Probe presences of remote sessions when subscribed to presence of local session
        // Send pending subscription requests to local sessions from remote sessions
        // Deliver offline messages sent to local sessions that were unavailable in other nodes
        // Send available presences of local sessions to other resources of the same user
        PresenceUpdateHandler presenceUpdateHandler = XMPPServer.getInstance().getPresenceUpdateHandler();
        for (LocalClientSession session : localRoutingTable.getClientRoutes()) {
            // Simulate that the local session has just became available
            session.setInitialized(false);
            // Simulate that current session presence has just been received
            presenceUpdateHandler.process(session.getPresence());
        }
    }

778 779
    public void joinedCluster(byte[] nodeID) {
        // Do nothing
780 781 782 783
    }

    public void leftCluster() {
        if (!XMPPServer.getInstance().isShuttingDown()) {
784 785
            // Add local sessions to caches
            restoreCacheContent();
786 787 788
        }
    }

789 790 791 792
    public void leftCluster(byte[] nodeID) {
        // Do nothing
    }

793 794 795
    public void markedAsSeniorClusterMember() {
        // Do nothing
    }
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813

    private void restoreCacheContent() {
        // Add outgoing server sessions hosted locally to the cache (using new nodeID)
        for (LocalOutgoingServerSession session : localRoutingTable.getServerRoutes()) {
            addServerRoute(session.getAddress(), session);
        }

        // Add component sessions hosted locally to the cache (using new nodeID) and remove traces to old nodeID
        for (RoutableChannelHandler route : localRoutingTable.getComponentRoute()) {
            addComponentRoute(route.getAddress(), route);
        }

        // Add client sessions hosted locally to the cache (using new nodeID)
        for (LocalClientSession session : localRoutingTable.getClientRoutes()) {
            addClientRoute(session.getAddress(), session);
        }
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
814
}