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

package org.jivesoftware.wildfire.spi;

14
import org.jivesoftware.util.Log;
15 16 17 18
import org.jivesoftware.wildfire.ChannelHandler;
import org.jivesoftware.wildfire.RoutableChannelHandler;
import org.jivesoftware.wildfire.RoutingTable;
import org.jivesoftware.wildfire.XMPPServer;
19 20 21
import org.jivesoftware.wildfire.component.InternalComponentManager;
import org.jivesoftware.wildfire.container.BasicModule;
import org.jivesoftware.wildfire.server.OutgoingSessionPromise;
22
import org.jivesoftware.wildfire.session.ClientSession;
23 24 25 26 27 28
import org.xmpp.packet.JID;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
29 30
 * <p>Uses simple Maps for table storage.</p>
 * <p>Leaves in the tree are indicated by a PacketHandler, while branches are stored in Maps.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
 * Traverse the tree according to an XMPPAddress' fields (host -> name -> resource) and when you
 * hit a PacketHandler, you have found the handler for that node and all sub-nodes. </p>
 *
 * @author Iain Shigeoka
 */
public class RoutingTableImpl extends BasicModule implements RoutingTable {

    /**
     * We need a three level tree built of hashtables: host -> name -> resource
     */
    private Map routes = new ConcurrentHashMap();

    private String serverName;
    private InternalComponentManager componentManager;

    public RoutingTableImpl() {
        super("Routing table");
    }

    public void addRoute(JID node, RoutableChannelHandler destination) {

        String nodeJID = node.getNode() == null ? "" : node.getNode();
        String resourceJID = node.getResource() == null ? "" : node.getResource();

55 56 57 58 59 60 61 62 63 64 65 66
        if (destination instanceof ClientSession) {
            Object nameRoutes = routes.get(node.getDomain());
            if (nameRoutes == null) {
                // No route to the requested domain. Create a new entry in the table
                synchronized (node.getDomain().intern()) {
                    // Check again if a route exists now that we have a lock
                    nameRoutes = routes.get(node.getDomain());
                    if (nameRoutes == null) {
                        // Still nothing so create a new entry in the map for domain
                        nameRoutes = new ConcurrentHashMap();
                        routes.put(node.getDomain(), nameRoutes);
                    }
67 68
                }
            }
69 70 71 72 73 74 75 76 77 78 79
            // Check if there is something associated with the node of the JID
            Object resourceRoutes = ((Map) nameRoutes).get(nodeJID);
            if (resourceRoutes == null) {
                // Nothing was found so create a new entry for this node (a.k.a. user)
                synchronized (nodeJID.intern()) {
                    resourceRoutes = ((Map) nameRoutes).get(nodeJID);
                    if (resourceRoutes == null) {
                        resourceRoutes = new ConcurrentHashMap();
                        ((Map) nameRoutes).put(nodeJID, resourceRoutes);
                    }
                }
80
            }
81 82
            // Add the connected resource to the node's Map
            ((Map) resourceRoutes).put(resourceJID, destination);
83
        }
84 85
        else {
            routes.put(node.getDomain(), destination);
86 87 88 89
        }
    }

    public RoutableChannelHandler getRoute(JID node) {
Gaston Dombiak's avatar
Gaston Dombiak committed
90 91 92
        if (node == null) {
            return null;
        }
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
        return getRoute(node.toString(), node.getNode() == null ? "" : node.getNode(),
                node.getDomain(), node.getResource() == null ? "" : node.getResource());
    }

    private RoutableChannelHandler getRoute(String jid, String node, String domain,
            String resource) {
        RoutableChannelHandler route = null;

        // Check if the address belongs to a remote server
        if (!serverName.equals(domain) && routes.get(domain) == null &&
                componentManager.getComponent(domain) == null) {
            // Return a promise of a remote session. This object will queue packets pending
            // to be sent to remote servers
            return OutgoingSessionPromise.getInstance();
        }

        try {
            Object nameRoutes = routes.get(domain);
            if (nameRoutes instanceof ChannelHandler) {
112
                route = (RoutableChannelHandler) nameRoutes;
113
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
114
            else if (nameRoutes != null) {
115
                Object resourceRoutes = ((Map) nameRoutes).get(node);
116
                if (resourceRoutes instanceof ChannelHandler) {
117
                    route = (RoutableChannelHandler) resourceRoutes;
118 119
                }
                else if (resourceRoutes != null) {
120
                    route = (RoutableChannelHandler) ((Map) resourceRoutes).get(resource);
121 122 123 124 125 126 127 128
                }
                else {
                    route = null;
                }
            }
        }
        catch (Exception e) {
            if (Log.isDebugEnabled()) {
129
                Log.debug("Route not found for JID: " + jid, e);
130 131 132 133 134 135
            }
        }

        return route;
    }

136
    public List<ChannelHandler> getRoutes(JID node) {
137 138
        // Check if the address belongs to a remote server
        if (!serverName.equals(node.getDomain()) && routes.get(node.getDomain()) == null &&
139
                componentManager.getComponent(node) == null) {
140 141
            // Return a promise of a remote session. This object will queue packets pending
            // to be sent to remote servers
142 143 144
            List<ChannelHandler> list = new ArrayList<ChannelHandler>();
            list.add(OutgoingSessionPromise.getInstance());
            return list;
145 146 147
        }

        LinkedList list = null;
148 149 150 151 152 153 154
        Object nameRoutes = routes.get(node.getDomain());
        if (nameRoutes != null) {
            if (nameRoutes instanceof ChannelHandler) {
                list = new LinkedList();
                list.add(nameRoutes);
            }
            else if (node.getNode() == null) {
155
                list = new LinkedList();
156
                getRoutes(list, (Map) nameRoutes);
157 158
            }
            else {
159 160 161
                Object resourceRoutes = ((Map) nameRoutes).get(node.getNode());
                if (resourceRoutes != null) {
                    if (resourceRoutes instanceof ChannelHandler) {
162
                        list = new LinkedList();
163
                        list.add(resourceRoutes);
164
                    }
165
                    else if (node.getResource() == null || node.getResource().length() == 0) {
166
                        list = new LinkedList();
167
                        getRoutes(list, (Map) resourceRoutes);
168 169
                    }
                    else {
170 171 172 173
                        Object entry = ((Map) resourceRoutes).get(node.getResource());
                        if (entry != null) {
                            list = new LinkedList();
                            list.add(entry);
174 175 176 177 178 179
                        }
                    }
                }
            }
        }
        if (list == null) {
180
            return Collections.emptyList();
181 182
        }
        else {
183
            return list;
184 185 186 187
        }
    }

    /**
188 189 190 191
     * Recursive method to iterate through the given table (and any embedded map)
     * and stuff non-Map values into the given list.<p>
     *
     * There should be no recursion problems since the routing table is at most 3 levels deep.
192 193 194 195 196 197 198 199
     *
     * @param list  The list to stuff entries into
     * @param table The hashtable who's values should be entered into the list
     */
    private void getRoutes(LinkedList list, Map table) {
        Iterator entryIter = table.values().iterator();
        while (entryIter.hasNext()) {
            Object entry = entryIter.next();
200 201
            if (entry instanceof ConcurrentHashMap) {
                getRoutes(list, (Map)entry);
202 203 204 205 206 207 208 209 210 211 212 213
            }
            else {
                // Do not include the same entry many times. This could be the case when the same 
                // session is associated with the bareJID and with a given resource
                if (!list.contains(entry)) {
                    list.add(entry);
                }
            }
        }
    }

    public ChannelHandler getBestRoute(JID node) {
214
        ChannelHandler route = getRoute(node);
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
        if (route == null) {
            // Try looking for a route based on the bare JID
            String nodeJID = node.getNode() == null ? "" : node.getNode();
            route = getRoute(node.toBareJID(), nodeJID, node.getDomain(), "");
        }
        return route;
    }

    public ChannelHandler removeRoute(JID node) {

        ChannelHandler route = null;
        String nodeJID = node.getNode() == null ? "" : node.getNode();
        String resourceJID = node.getResource() == null ? "" : node.getResource();

        try {
            Object nameRoutes = routes.get(node.getDomain());
231 232 233
            if (nameRoutes instanceof ConcurrentHashMap) {
                Object resourceRoutes = ((Map) nameRoutes).get(nodeJID);
                if (resourceRoutes instanceof ConcurrentHashMap) {
234
                    // Remove the requested resource for this user
235 236 237 238
                    route = (ChannelHandler) ((Map) resourceRoutes).remove(resourceJID);
                    if (((Map) resourceRoutes).isEmpty()) {
                        ((Map) nameRoutes).remove(nodeJID);
                        if (((Map) nameRoutes).isEmpty()) {
239 240 241 242 243 244
                            routes.remove(node.getDomain());
                        }
                    }
                }
                else {
                    // Remove the unique route to this node
245
                    ((Map) nameRoutes).remove(nodeJID);
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
                }
            }
            else if (nameRoutes != null) {
                // The retrieved route points to a RoutableChannelHandler
                if (("".equals(nodeJID) && "".equals(resourceJID)) ||
                        ((RoutableChannelHandler) nameRoutes).getAddress().equals(node)) {
                    // Remove the route to this domain
                    routes.remove(node.getDomain());
                }
            }
        }
        catch (Exception e) {
            Log.error("Error removing route", e);
        }
        return route;
    }

    public void initialize(XMPPServer server) {
        super.initialize(server);
        serverName = server.getServerInfo().getName();
    }
267 268 269 270 271

    public void start() throws IllegalStateException {
        super.start();
        componentManager = InternalComponentManager.getInstance();
    }
272
}