MultiplexerPacketHandler.java 11.1 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5
/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
6
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
Gaston Dombiak's avatar
Gaston Dombiak committed
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.
Gaston Dombiak's avatar
Gaston Dombiak committed
11 12
 */

13
package org.jivesoftware.openfire.multiplex;
Gaston Dombiak's avatar
Gaston Dombiak committed
14 15 16 17

import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
18 19 20 21
import org.jivesoftware.openfire.SessionPacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.ConnectionMultiplexerSession;
22 23
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.util.Log;
24 25 26 27
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
Gaston Dombiak's avatar
Gaston Dombiak committed
28 29 30 31 32

import java.util.List;

/**
 * IQ packets sent from Connection Managers themselves to the server will be handled by
33
 * instances of this class.<p>
Gaston Dombiak's avatar
Gaston Dombiak committed
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 * <p/>
 * This class will interact with {@link ConnectionMultiplexerManager} to create, close or
 * get client sessions.
 *
 * @author Gaston Dombiak
 */
public class MultiplexerPacketHandler {

    private String connectionManagerDomain;
    private final ConnectionMultiplexerManager multiplexerManager;

    public MultiplexerPacketHandler(String connectionManagerDomain) {
        this.connectionManagerDomain = connectionManagerDomain;
        multiplexerManager = ConnectionMultiplexerManager.getInstance();
    }

50 51 52 53 54 55
    /**
     * Process IQ packet sent by a connection manager indicating that a new session has
     * been created, should be closed or that a packet was failed to be delivered.
     *
     * @param packet the IQ packet.
     */
Gaston Dombiak's avatar
Gaston Dombiak committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
    public void handle(Packet packet) {
        if (packet instanceof IQ) {
            IQ iq = (IQ) packet;
            if (iq.getType() == IQ.Type.result) {
                // Do nothing with result packets
            }
            else if (iq.getType() == IQ.Type.error) {
                // Log the IQ error packet that the connection manager failed to process
                Log.warn("Connection Manager failed to process IQ packet: " + packet.toXML());
            }
            else if (iq.getType() == IQ.Type.set) {
                Element child = iq.getChildElement();
                String streamID = child.attributeValue("id");
                if (streamID == null) {
                    // No stream ID was included so return a bad_request error
                    Element extraError = DocumentHelper.createElement(QName.get(
                            "id-required", "http://jabber.org/protocol/connectionmanager#errors"));
                    sendErrorPacket(iq, PacketError.Condition.bad_request, extraError);
                }
                else if ("session".equals(child.getName())) {
76 77 78 79 80 81
                    Element create = child.element("create");
                    if (create != null) {
                        // Get the InetAddress of the client
                        Element hostElement = create.element("host");
                        String hostName = hostElement != null ? hostElement.attributeValue("name") : null;
                        String hostAddress = hostElement != null ? hostElement.attributeValue("address") : null;
Gaston Dombiak's avatar
Gaston Dombiak committed
82
                        // Connection Manager wants to create a Client Session
83 84 85 86 87 88 89 90 91
                        boolean created = multiplexerManager
                                .createClientSession(connectionManagerDomain, streamID, hostName, hostAddress);
                        if (created) {
                            sendResultPacket(iq);
                        }
                        else {
                            // Send error to CM. The CM should close the new-borned connection
                            sendErrorPacket(iq, PacketError.Condition.not_allowed, null);
                        }
Gaston Dombiak's avatar
Gaston Dombiak committed
92 93 94 95 96 97 98 99 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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
                    }
                    else {
                        ClientSession session = multiplexerManager
                                .getClientSession(connectionManagerDomain, streamID);
                        if (session == null) {
                            // Specified Client Session does not exist
                            sendErrorPacket(iq, PacketError.Condition.item_not_found, null);
                        }
                        else if (child.element("close") != null) {
                            // Connection Manager wants to close a Client Session
                            multiplexerManager
                                    .closeClientSession(connectionManagerDomain, streamID);
                            sendResultPacket(iq);
                        }
                        else if (child.element("failed") != null) {
                            // Connection Manager failed to deliver a message
                            // Connection Manager wrapped a packet from a Client Session.
                            List wrappedElements = child.element("failed").elements();
                            if (wrappedElements.size() != 1) {
                                // Wrapper element is wrapping 0 or many items
                                Element extraError = DocumentHelper.createElement(QName.get(
                                        "invalid-payload",
                                        "http://jabber.org/protocol/connectionmanager#errors"));
                                sendErrorPacket(iq, PacketError.Condition.bad_request, extraError);
                            }
                            else {
                                Element wrappedElement = (Element) wrappedElements.get(0);
                                String tag = wrappedElement.getName();
                                if ("message".equals(tag)) {
                                    XMPPServer.getInstance().getOfflineMessageStrategy()
                                            .storeOffline(new Message(wrappedElement));
                                    sendResultPacket(iq);
                                }
                                else {
                                    Element extraError = DocumentHelper.createElement(QName.get(
                                            "unknown-stanza",
                                            "http://jabber.org/protocol/connectionmanager#errors"));
                                    sendErrorPacket(iq, PacketError.Condition.bad_request,
                                            extraError);
                                }
                            }
                        }
                        else {
                            // Unknown IQ packet received so return error to sender
                            sendErrorPacket(iq, PacketError.Condition.bad_request, null);
                        }
                    }
                }
                else {
                    // Unknown IQ packet received so return error to sender
                    sendErrorPacket(iq, PacketError.Condition.bad_request, null);
                }
            }
            else {
                // Unknown IQ packet received so return error to sender
                sendErrorPacket(iq, PacketError.Condition.bad_request, null);
            }
        }
    }

152 153 154 155 156 157 158 159 160 161 162 163 164 165
    /**
     * Processes a route packet that is wrapping a stanza sent by a client that is connected
     * to the connection manager.
     *
     * @param route the route packet.
     */
    public void route(Route route) {
        String streamID = route.getStreamID();
        if (streamID == null) {
            // No stream ID was included so return a bad_request error
            Element extraError = DocumentHelper.createElement(QName.get(
                    "id-required", "http://jabber.org/protocol/connectionmanager#errors"));
            sendErrorPacket(route, PacketError.Condition.bad_request, extraError);
        }
166
        LocalClientSession session = multiplexerManager.getClientSession(connectionManagerDomain, streamID);
167 168 169 170 171
        if (session == null) {
            // Specified Client Session does not exist
            sendErrorPacket(route, PacketError.Condition.item_not_found, null);
            return;
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
172

173
        SessionPacketRouter router = new SessionPacketRouter(session);
174 175
        // Connection Manager already validate JIDs so just skip this expensive operation
        router.setSkipJIDValidation(true);
Gaston Dombiak's avatar
Gaston Dombiak committed
176
        try {
Alex Wenckus's avatar
Alex Wenckus committed
177
            router.route(route.getChildElement());
Gaston Dombiak's avatar
Gaston Dombiak committed
178
        }
Alex Wenckus's avatar
Alex Wenckus committed
179 180 181 182 183
        catch (UnknownStanzaException use) {
            Element extraError = DocumentHelper.createElement(QName.get(
                    "unknown-stanza",
                    "http://jabber.org/protocol/connectionmanager#errors"));
            sendErrorPacket(route, PacketError.Condition.bad_request, extraError);
Gaston Dombiak's avatar
Gaston Dombiak committed
184
        }
185
        catch (Exception e) {
Alex Wenckus's avatar
Alex Wenckus committed
186 187
            Log.error("Error processing wrapped packet: " + route.getChildElement().asXML(), e);
            sendErrorPacket(route, PacketError.Condition.internal_server_error, null);
Gaston Dombiak's avatar
Gaston Dombiak committed
188 189 190 191 192 193 194 195 196
        }
    }

    /**
     * Sends an IQ error with the specified condition to the sender of the original
     * IQ packet.
     *
     * @param packet     the packet to be bounced.
     * @param extraError application specific error or null if none.
Alex Wenckus's avatar
Alex Wenckus committed
197
     * @param error the error.
Gaston Dombiak's avatar
Gaston Dombiak committed
198 199 200 201 202 203 204 205 206 207 208 209
     */
    private void sendErrorPacket(IQ packet, PacketError.Condition error, Element extraError) {
        IQ reply = IQ.createResultIQ(packet);
        reply.setChildElement(packet.getChildElement().createCopy());
        reply.setError(error);
        if (extraError != null) {
            // Add specific application error if available
            reply.getError().getElement().add(extraError);
        }
        deliver(reply);
    }

210 211 212 213 214 215
    /**
     * Sends an IQ error with the specified condition to the sender of the original
     * IQ packet.
     *
     * @param packet     the packet to be bounced.
     * @param extraError application specific error or null if none.
Alex Wenckus's avatar
Alex Wenckus committed
216
     * @param error the error.
217 218 219 220 221 222 223 224 225 226 227 228 229 230
     */
    private void sendErrorPacket(Route packet, PacketError.Condition error, Element extraError) {
        Route reply = new Route(packet.getStreamID());
        reply.setID(packet.getID());
        reply.setFrom(packet.getTo());
        reply.setTo(packet.getFrom());
        reply.setError(error);
        if (extraError != null) {
            // Add specific application error if available
            reply.getError().getElement().add(extraError);
        }
        deliver(reply);
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
231 232 233 234 235 236 237 238 239 240 241
    /**
     * Sends an IQ result packet confirming that the operation was successful.
     *
     * @param packet the original IQ packet.
     */
    private void sendResultPacket(IQ packet) {
        IQ reply = IQ.createResultIQ(packet);
        reply.setChildElement(packet.getChildElement().createCopy());
        deliver(reply);
    }

242
    private void deliver(Packet reply) {
Gaston Dombiak's avatar
Gaston Dombiak committed
243 244 245 246
        // Get any session of the connection manager to deliver the packet
        ConnectionMultiplexerSession session =
                multiplexerManager.getMultiplexerSession(connectionManagerDomain);
        if (session != null) {
247
            session.process(reply);
Gaston Dombiak's avatar
Gaston Dombiak committed
248 249 250 251 252 253
        }
        else {
            Log.warn("No multiplexer session found. Packet not delivered: " + reply.toXML());
        }
    }
}