MultiplexerPacketHandler.java 10.3 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5
/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
6
 * Copyright (C) 2007 Jive Software. All rights reserved.
Gaston Dombiak's avatar
Gaston Dombiak committed
7 8 9 10 11 12 13 14 15 16 17
 *
 * 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.multiplex;

import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.util.Log;
18
import org.jivesoftware.wildfire.SessionPacketRouter;
19 20 21 22 23 24 25
import org.jivesoftware.wildfire.XMPPServer;
import org.jivesoftware.wildfire.session.ClientSession;
import org.jivesoftware.wildfire.session.ConnectionMultiplexerSession;
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
26 27 28 29 30

import java.util.List;

/**
 * IQ packets sent from Connection Managers themselves to the server will be handled by
31
 * instances of this class.<p>
Gaston Dombiak's avatar
Gaston Dombiak committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
 * <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();
    }

48 49 50 51 52 53
    /**
     * 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
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 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
    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())) {
                    if (child.element("create") != null) {
                        // Connection Manager wants to create a Client Session
                        multiplexerManager.createClientSession(connectionManagerDomain, streamID);
                        sendResultPacket(iq);
                    }
                    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);
            }
        }
    }

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
    /**
     * 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);
        }
        ClientSession session = multiplexerManager
                .getClientSession(connectionManagerDomain, streamID);
        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
159

160
        SessionPacketRouter router = new SessionPacketRouter(session);
161 162
        // Connection Manager already validate JIDs so just skip this expensive operation
        router.setSkipJIDValidation(true);
Gaston Dombiak's avatar
Gaston Dombiak committed
163
        try {
Alex Wenckus's avatar
Alex Wenckus committed
164
            router.route(route.getChildElement());
Gaston Dombiak's avatar
Gaston Dombiak committed
165
        }
Alex Wenckus's avatar
Alex Wenckus committed
166 167 168 169 170
        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
171
        }
172
        catch (Exception e) {
Alex Wenckus's avatar
Alex Wenckus committed
173 174
            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
175 176 177 178 179 180 181 182 183
        }
    }

    /**
     * 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
184
     * @param error the error.
Gaston Dombiak's avatar
Gaston Dombiak committed
185 186 187 188 189 190 191 192 193 194 195 196
     */
    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);
    }

197 198 199 200 201 202
    /**
     * 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
203
     * @param error the error.
204 205 206 207 208 209 210 211 212 213 214 215 216 217
     */
    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
218 219 220 221 222 223 224 225 226 227 228
    /**
     * 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);
    }

229
    private void deliver(Packet reply) {
Gaston Dombiak's avatar
Gaston Dombiak committed
230 231 232 233
        // Get any session of the connection manager to deliver the packet
        ConnectionMultiplexerSession session =
                multiplexerManager.getMultiplexerSession(connectionManagerDomain);
        if (session != null) {
234
            session.process(reply);
Gaston Dombiak's avatar
Gaston Dombiak committed
235 236 237 238 239 240
        }
        else {
            Log.warn("No multiplexer session found. Packet not delivered: " + reply.toXML());
        }
    }
}