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

Matt Tucker's avatar
Matt Tucker committed
12 13
package org.jivesoftware.messenger;

14
import org.dom4j.Element;
Matt Tucker's avatar
Matt Tucker committed
15 16 17
import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.messenger.handler.IQHandler;
import org.jivesoftware.util.LocaleUtils;
18 19 20 21
import org.jivesoftware.util.Log;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
Matt Tucker's avatar
Matt Tucker committed
22 23

import java.util.ArrayList;
24
import java.util.List;
Matt Tucker's avatar
Matt Tucker committed
25 26
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
27

Matt Tucker's avatar
Matt Tucker committed
28
/**
Matt Tucker's avatar
Matt Tucker committed
29 30 31 32
 * Routes iq packets throughout the server. Routing is based on the recipient
 * and sender addresses. The typical packet will often be routed twice, once
 * from the sender to some internal server component for handling or processing,
 * and then back to the router to be delivered to it's final destination.
Matt Tucker's avatar
Matt Tucker committed
33 34 35
 *
 * @author Iain Shigeoka
 */
Matt Tucker's avatar
Matt Tucker committed
36 37 38 39 40 41 42 43 44 45 46 47 48
public class IQRouter extends BasicModule {

    private RoutingTable routingTable;
    private List<IQHandler> iqHandlers = new ArrayList<IQHandler>();
    private Map<String, IQHandler> namespace2Handlers = new ConcurrentHashMap<String, IQHandler>();
    private SessionManager sessionManager;

    /**
     * Creates a packet router.
     */
    public IQRouter() {
        super("XMPP IQ Router");
    }
Matt Tucker's avatar
Matt Tucker committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62

    /**
     * <p>Performs the actual packet routing.</p>
     * <p>You routing is considered 'quick' and implementations may not take
     * excessive amounts of time to complete the routing. If routing will take
     * a long amount of time, the actual routing should be done in another thread
     * so this method returns quickly.</p>
     * <h2>Warning</h2>
     * <p>Be careful to enforce concurrency DbC of concurrent by synchronizing
     * any accesses to class resources.</p>
     *
     * @param packet The packet to route
     * @throws NullPointerException If the packet is null
     */
Matt Tucker's avatar
Matt Tucker committed
63 64 65 66 67
    public void route(IQ packet) {
        if (packet == null) {
            throw new NullPointerException();
        }
        Session session = sessionManager.getSession(packet.getFrom());
68 69 70 71 72 73 74
        if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED || (
                isLocalServer(packet.getTo()) && (
                        "jabber:iq:auth".equals(packet.getChildElement().getNamespaceURI()) ||
                                "jabber:iq:register"
                                        .equals(packet.getChildElement().getNamespaceURI()) ||
                                "urn:ietf:params:xml:ns:xmpp-bind"
                                        .equals(packet.getChildElement().getNamespaceURI())))) {
Matt Tucker's avatar
Matt Tucker committed
75 76 77
            handle(packet);
        }
        else {
78 79
            IQ reply = IQ.createResultIQ(packet);
            reply.setChildElement(packet.getChildElement().createCopy());
80
            reply.setError(PacketError.Condition.not_authorized);
81
            sessionManager.getSession(packet.getFrom()).process(reply);
Matt Tucker's avatar
Matt Tucker committed
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
        }
    }

    /**
     * <p>Adds a new IQHandler to the list of registered handler. The new IQHandler will be
     * responsible for handling IQ packet whose namespace matches the namespace of the
     * IQHandler.</p>
     *
     * An IllegalArgumentException may be thrown if the IQHandler to register was already provided
     * by the server. The server provides a certain list of IQHandlers when the server is
     * started up.
     *
     * @param handler the IQHandler to add to the list of registered handler.
     */
    public void addHandler(IQHandler handler) {
        if (iqHandlers.contains(handler)) {
            throw new IllegalArgumentException("IQHandler already provided by the server");
        }
100 101
        // Ask the handler to be initialized
        handler.initialize(XMPPServer.getInstance());
Matt Tucker's avatar
Matt Tucker committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
        // Register the handler as the handler of the namespace
        namespace2Handlers.put(handler.getInfo().getNamespace(), handler);
    }

    /**
     * <p>Removes an IQHandler from the list of registered handler. The IQHandler to remove was
     * responsible for handling IQ packet whose namespace matches the namespace of the
     * IQHandler.</p>
     *
     * An IllegalArgumentException may be thrown if the IQHandler to remove was already provided
     * by the server. The server provides a certain list of IQHandlers when the server is
     * started up.
     *
     * @param handler the IQHandler to remove from the list of registered handler.
     */
    public void removeHandler(IQHandler handler) {
        if (iqHandlers.contains(handler)) {
            throw new IllegalArgumentException("Cannot remove an IQHandler provided by the server");
        }
        // Unregister the handler as the handler of the namespace
        namespace2Handlers.remove(handler.getInfo().getNamespace());
    }

    public void initialize(XMPPServer server) {
        super.initialize(server);
        routingTable = server.getRoutingTable();
        iqHandlers.addAll(server.getIQHandlers());
        sessionManager = server.getSessionManager();
    }

132 133 134 135 136 137
    /**
     * A JID is considered local if:
     * 1) is null or
     * 2) has no domain or domain is empty or
     * 3) has no resource or resource is empty
     */
Matt Tucker's avatar
Matt Tucker committed
138 139 140 141 142 143 144 145 146
    private boolean isLocalServer(JID recipientJID) {
        return recipientJID == null || recipientJID.getDomain() == null
                || "".equals(recipientJID.getDomain()) || recipientJID.getResource() == null
                || "".equals(recipientJID.getResource());
    }

    private void handle(IQ packet) {
        JID recipientJID = packet.getTo();
        try {
Gaston Dombiak's avatar
Gaston Dombiak committed
147
            // Check for registered components, services or remote servers
148
            if (recipientJID != null) {
149 150 151
                try {
                    RoutableChannelHandler serviceRoute = routingTable.getRoute(recipientJID);
                    if (!(serviceRoute instanceof ClientSession)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
152
                        // A component/service/remote server was found that can handle the Packet
153 154 155 156 157 158 159
                        serviceRoute.process(packet);
                        return;
                    }
                }
                catch (NoSuchRouteException e) {
                    // Do nothing. Continue looking for a handler
                }
160
            }
161
            if (isLocalServer(recipientJID)) {
162
                // Let the server handle the Packet
Matt Tucker's avatar
Matt Tucker committed
163 164 165 166 167 168
                Element childElement = packet.getChildElement();
                String namespace = null;
                if (childElement != null) {
                    namespace = childElement.getNamespaceURI();
                }
                if (namespace == null) {
169 170 171 172
                    if (packet.getType() != IQ.Type.result) {
                        // Do nothing. We can't handle queries outside of a valid namespace
                        Log.warn("Unknown packet " + packet);
                    }
Matt Tucker's avatar
Matt Tucker committed
173 174 175 176 177 178 179
                }
                else {
                    IQHandler handler = getHandler(namespace);
                    if (handler == null) {
                        IQ reply = IQ.createResultIQ(packet);
                        if (recipientJID == null) {
                            // Answer an error since the server can't handle the requested namespace
180
                            reply.setChildElement(packet.getChildElement().createCopy());
Matt Tucker's avatar
Matt Tucker committed
181 182
                            reply.setError(PacketError.Condition.service_unavailable);
                        }
183 184
                        else if (recipientJID.getNode() == null ||
                                "".equals(recipientJID.getNode())) {
Matt Tucker's avatar
Matt Tucker committed
185
                            // Answer an error if JID is of the form <domain>
186
                            reply.setChildElement(packet.getChildElement().createCopy());
Matt Tucker's avatar
Matt Tucker committed
187 188 189 190 191
                            reply.setError(PacketError.Condition.feature_not_implemented);
                        }
                        else {
                            // JID is of the form <node@domain>
                            // Answer an error since the server can't handle packets sent to a node
192
                            reply.setChildElement(packet.getChildElement().createCopy());
Matt Tucker's avatar
Matt Tucker committed
193 194
                            reply.setError(PacketError.Condition.service_unavailable);
                        }
195 196 197 198 199

                        try {
                            // Locate a route to the sender of the IQ and ask it to process
                            // the packet. Use the routingTable so that routes to remote servers
                            // may be found
200
                            routingTable.getRoute(packet.getFrom()).process(reply);
Matt Tucker's avatar
Matt Tucker committed
201
                        }
202 203 204 205 206 207 208 209 210 211
                        catch (NoSuchRouteException e) {
                            // No root was found so try looking for local sessions that have never
                            // sent an available presence or haven't authenticated yet
                            Session session = sessionManager.getSession(packet.getFrom());
                            if (session != null) {
                                session.process(reply);
                            }
                            else {
                                Log.warn("Packet could not be delivered " + packet);
                            }
Matt Tucker's avatar
Matt Tucker committed
212 213 214 215 216 217 218 219 220
                        }
                    }
                    else {
                        handler.process(packet);
                    }
                }

            }
            else {
221 222 223 224 225 226 227 228
                // JID is of the form <node@domain/resource>
                boolean handlerFound = false;
                // IQ packets should be sent to users even before they send an available presence.
                // So if the target address belongs to this server then use the sessionManager
                // instead of the routingTable since unavailable clients won't have a route to them
                if (XMPPServer.getInstance().isLocal(recipientJID)) {
                    Session session = sessionManager.getBestRoute(recipientJID);
                    if (session != null) {
229
                        session.process(packet);
230 231 232 233 234
                        handlerFound = true;
                    }
                    else {
                        Log.info("Packet sent to unreachable address " + packet);
                    }
Matt Tucker's avatar
Matt Tucker committed
235
                }
236 237 238 239 240 241 242 243 244 245 246 247
                else {
                    try {
                        ChannelHandler route = routingTable.getRoute(recipientJID);
                        route.process(packet);
                        handlerFound = true;
                    }
                    catch (NoSuchRouteException e) {
                        Log.info("Packet sent to unreachable address " + packet);
                    }
                }
                // If a route to the target address was not found then try to answer a
                // service_unavailable error code to the sender of the IQ packet
248
                if (!handlerFound && IQ.Type.result != packet.getType()) {
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
                    IQ reply = IQ.createResultIQ(packet);
                    reply.setChildElement(packet.getChildElement().createCopy());
                    reply.setError(PacketError.Condition.service_unavailable);
                    try {
                        // Locate a route to the sender of the IQ and ask it to process
                        // the packet. Use the routingTable so that routes to remote servers
                        // may be found
                        routingTable.getRoute(packet.getFrom()).process(reply);
                    }
                    catch (NoSuchRouteException e) {
                        // No root was found so try looking for local sessions that have never
                        // sent an available presence or haven't authenticated yet
                        Session session = sessionManager.getSession(packet.getFrom());
                        if (session != null) {
                            session.process(reply);
                        }
                        else {
                            Log.warn("Packet could not be delivered " + reply);
                        }
268
                    }
Matt Tucker's avatar
Matt Tucker committed
269 270 271 272 273
                }
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error.routing"), e);
274 275 276 277 278
            Session session = sessionManager.getSession(packet.getFrom());
            if (session != null) {
                Connection conn = session.getConnection();
                if (conn != null) {
                    conn.close();
Matt Tucker's avatar
Matt Tucker committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
                }
            }
        }
    }

    private IQHandler getHandler(String namespace) {
        IQHandler handler = namespace2Handlers.get(namespace);
        if (handler == null) {
            for (IQHandler handlerCandidate : iqHandlers) {
                IQHandlerInfo handlerInfo = handlerCandidate.getInfo();
                if (handlerInfo != null && namespace.equalsIgnoreCase(handlerInfo.getNamespace())) {
                    handler = handlerCandidate;
                    namespace2Handlers.put(namespace, handler);
                    break;
                }
            }
        }
        return handler;
    }
Matt Tucker's avatar
Matt Tucker committed
298
}