IQRouter.java 11.4 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1 2 3 4
/**
 * $RCSfile$
 * $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 68 69 70 71 72 73 74 75
    public void route(IQ packet) {
        if (packet == null) {
            throw new NullPointerException();
        }
        Session session = sessionManager.getSession(packet.getFrom());
        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())))
        ) {
            handle(packet);
        }
        else {
76 77
            IQ reply = IQ.createResultIQ(packet);
            reply.setChildElement(packet.getChildElement().createCopy());
Matt Tucker's avatar
Matt Tucker committed
78
            packet.setError(PacketError.Condition.not_authorized);
79
            sessionManager.getSession(packet.getFrom()).process(reply);
Matt Tucker's avatar
Matt Tucker committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
        }
    }

    /**
     * <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");
        }
98 99
        // Ask the handler to be initialized
        handler.initialize(XMPPServer.getInstance());
Matt Tucker's avatar
Matt Tucker committed
100 101 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
        // 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();
    }

130 131 132 133 134 135
    /**
     * 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
136 137 138 139 140 141 142 143 144
    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
145
            // Check for registered components, services or remote servers
146
            if (recipientJID != null) {
147 148 149
                try {
                    RoutableChannelHandler serviceRoute = routingTable.getRoute(recipientJID);
                    if (!(serviceRoute instanceof ClientSession)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
150
                        // A component/service/remote server was found that can handle the Packet
151 152 153 154 155 156 157
                        serviceRoute.process(packet);
                        return;
                    }
                }
                catch (NoSuchRouteException e) {
                    // Do nothing. Continue looking for a handler
                }
158
            }
159
            if (isLocalServer(recipientJID)) {
160
                // Let the server handle the Packet
Matt Tucker's avatar
Matt Tucker committed
161 162 163 164 165 166
                Element childElement = packet.getChildElement();
                String namespace = null;
                if (childElement != null) {
                    namespace = childElement.getNamespaceURI();
                }
                if (namespace == null) {
167 168 169 170
                    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
171 172 173 174 175 176 177
                }
                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
178
                            reply.setChildElement(packet.getChildElement().createCopy());
Matt Tucker's avatar
Matt Tucker committed
179 180
                            reply.setError(PacketError.Condition.service_unavailable);
                        }
181 182
                        else if (recipientJID.getNode() == null ||
                                "".equals(recipientJID.getNode())) {
Matt Tucker's avatar
Matt Tucker committed
183
                            // Answer an error if JID is of the form <domain>
184
                            reply.setChildElement(packet.getChildElement().createCopy());
Matt Tucker's avatar
Matt Tucker committed
185 186 187 188 189
                            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
190
                            reply.setChildElement(packet.getChildElement().createCopy());
Matt Tucker's avatar
Matt Tucker committed
191 192 193 194
                            reply.setError(PacketError.Condition.service_unavailable);
                        }
                        Session session = sessionManager.getSession(packet.getFrom());
                        if (session != null) {
195
                            session.process(reply);
Matt Tucker's avatar
Matt Tucker committed
196 197 198 199 200 201 202 203 204 205 206 207
                        }
                        else {
                            Log.warn("Packet could not be delivered " + packet);
                        }
                    }
                    else {
                        handler.process(packet);
                    }
                }

            }
            else {
208 209 210 211 212 213 214 215
                // 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) {
216
                        session.process(packet);
217 218 219 220 221
                        handlerFound = true;
                    }
                    else {
                        Log.info("Packet sent to unreachable address " + packet);
                    }
Matt Tucker's avatar
Matt Tucker committed
222
                }
223 224 225 226 227 228 229 230 231 232 233 234 235
                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
                if (!handlerFound) {
236 237
                    Session session = sessionManager.getSession(packet.getFrom());
                    if (session != null) {
238 239 240
                        IQ reply = IQ.createResultIQ(packet);
                        reply.setChildElement(packet.getChildElement().createCopy());
                        reply.setError(PacketError.Condition.service_unavailable);
241
                        session.process(reply);
242
                    }
Matt Tucker's avatar
Matt Tucker committed
243 244 245 246 247
                }
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error.routing"), e);
248 249 250 251 252
            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
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
                }
            }
        }
    }

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