RoutingTableImpl.java 34 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) 2005-2008 Jive Software. All rights reserved.
7 8
 *
 * This software is published under the terms of the GNU Public License (GPL),
9 10
 * a copy of which is included in this distribution, or a commercial license
 * agreement with Jive.
11 12
 */

13
package org.jivesoftware.openfire.spi;
14

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

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

/**
Gaston Dombiak's avatar
Gaston Dombiak committed
35 36 37 38 39 40 41 42
 * 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>
43
 *
44 45 46
 * 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
47 48
 *
 * @author Gaston Dombiak
49
 */
50 51 52 53 54 55
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";
56 57

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

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

    public RoutingTableImpl() {
        super("Routing table");
95 96 97 98
        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
99 100
        usersSessions = CacheFactory.createCache("Routing User Sessions");
        localRoutingTable = new LocalRoutingTable();
101 102
    }

103
    public void addServerRoute(JID route, LocalOutgoingServerSession destination) {
104
        String address = route.getDomain();
Gaston Dombiak's avatar
Gaston Dombiak committed
105
        localRoutingTable.addRoute(address, destination);
106 107 108 109 110 111 112 113
        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
114
    }
115

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

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

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

        // 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
206
    public void routePacket(JID jid, Packet packet, boolean fromServer) throws PacketException {
Gaston Dombiak's avatar
Gaston Dombiak committed
207 208
        boolean routed = false;
        if (serverName.equals(jid.getDomain())) {
209 210 211 212 213
            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);
214
                }
Gaston Dombiak's avatar
Gaston Dombiak committed
215
                else {
216 217 218 219 220 221 222 223 224 225
                    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) {
226 227
                    if (!clientRoute.isAvailable() && routeOnlyAvailable(packet, fromServer) &&
                            !presenceUpdateHandler.hasDirectPresence(packet.getTo(), packet.getFrom())) {
228 229
                        // Packet should only be sent to available sessions and the route is not available
                        routed = false;
Gaston Dombiak's avatar
Gaston Dombiak committed
230 231
                    }
                    else {
232
                        if (server.getNodeID().equals(clientRoute.getNodeID())) {
233 234 235 236 237 238 239 240 241 242 243
                            // 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
244 245
                                routed = remotePacketRouter
                                        .routePacket(clientRoute.getNodeID().toByteArray(), jid, packet);
246
                            }
Gaston Dombiak's avatar
Gaston Dombiak committed
247 248 249 250 251
                        }
                    }
                }
            }
        }
252
        else if (jid.getDomain().contains(serverName) && hasComponentRoute(jid)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
253
            // Packet sent to component hosted in this server
254 255 256 257 258 259 260 261
            // 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
262
                }
263 264 265
            }
            else {
                // Check if other cluster nodes are hosting this component
266
                Set<NodeID> nodes = componentsCache.get(jid.getDomain());
267
                if (nodes != null) {
268 269
                    for (NodeID nodeID : nodes) {
                        if (server.getNodeID().equals(nodeID)) {
270 271 272 273 274 275 276 277 278 279 280 281 282
                            // 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) {
283
                                routed = remotePacketRouter.routePacket(nodeID.toByteArray(), jid, packet);
284 285 286 287 288
                                if (routed) {
                                    break;
                                }
                            }
                        }
Gaston Dombiak's avatar
Gaston Dombiak committed
289 290 291 292 293 294 295 296
                    }
                }
            }
        }
        else {
            // Packet sent to remote server
            byte[] nodeID = serversCache.get(jid.getDomain());
            if (nodeID != null) {
297
                if (server.getNodeID().equals(nodeID)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
298
                    // This is a route to a remote server connected from this node
299 300 301 302 303 304
                    try {
                        localRoutingTable.getRoute(jid.getDomain()).process(packet);
                        routed = true;
                    } catch (UnauthorizedException e) {
                        Log.error(e);
                    }
305 306
                }
                else {
Gaston Dombiak's avatar
Gaston Dombiak committed
307 308 309 310
                    // This is a route to a remote server connected from other node
                    if (remotePacketRouter != null) {
                        routed = remotePacketRouter.routePacket(nodeID, jid, packet);
                    }
311 312
                }
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
313 314 315 316 317 318
            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;
            }
319
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
320 321

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

337 338 339 340
    /**
     * 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
341
     * @param fromServer true if the packet was created by the server.
342 343
     * @return true if the specified packet must only be route to available client sessions.
     */
Gaston Dombiak's avatar
Gaston Dombiak committed
344 345 346 347 348 349
    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;
        }
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
        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;
    }

368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
    /**
     * 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>();
390 391
        // Get existing AVAILABLE sessions of this user or AVAILABLE to the sender of the packet
        for (JID address : getRoutes(recipientJID, packet.getFrom())) {
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 455
            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) {
456
                        return o2.getLastActiveDate().compareTo(o1.getLastActiveDate());
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 514
                    }
                });
                // 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
515
                    session = locator.getClientSession(route.getNodeID().toByteArray(), jid);
516 517 518 519 520 521
                }
            }
        }
        return session;
    }

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

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

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

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

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

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

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

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

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

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

    public boolean removeServerRoute(JID route) {
        String address = route.getDomain();
696 697 698 699 700 701 702 703 704
        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
705 706 707 708 709 710
        localRoutingTable.removeRoute(address);
        return removed;
    }

    public boolean removeComponentRoute(JID route) {
        String address = route.getDomain();
711
        boolean removed = false;
712
        Lock lock = CacheFactory.getLock(address, componentsCache);
713 714
        try {
            lock.lock();
715
            Set<NodeID> nodes = componentsCache.get(address);
716 717 718 719 720 721 722 723 724 725 726 727
            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
728 729 730 731 732 733
        localRoutingTable.removeRoute(address);
        return removed;
    }

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

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

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

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

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

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

        // 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());
        }
    }

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

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

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

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

    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
815
}