MultiplexerPacketHandler.java 11.6 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 9 10 11 12 13 14 15 16 17 18
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
Gaston Dombiak's avatar
Gaston Dombiak committed
19 20
 */

21
package org.jivesoftware.openfire.multiplex;
Gaston Dombiak's avatar
Gaston Dombiak committed
22

23 24
import java.util.List;

Gaston Dombiak's avatar
Gaston Dombiak committed
25 26 27
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
28 29 30 31
import org.jivesoftware.openfire.SessionPacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.ConnectionMultiplexerSession;
32
import org.jivesoftware.openfire.session.LocalClientSession;
33 34
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
35 36 37 38
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
39 40 41

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

51 52
	private static final Logger Log = LoggerFactory.getLogger(MultiplexerPacketHandler.class);

Gaston Dombiak's avatar
Gaston Dombiak committed
53 54 55 56 57 58 59 60
    private String connectionManagerDomain;
    private final ConnectionMultiplexerManager multiplexerManager;

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

61 62 63 64 65 66
    /**
     * 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
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    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())) {
87 88 89 90 91 92
                    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
93
                        // Connection Manager wants to create a Client Session
94 95 96 97 98 99
                        boolean created = multiplexerManager
                                .createClientSession(connectionManagerDomain, streamID, hostName, hostAddress);
                        if (created) {
                            sendResultPacket(iq);
                        }
                        else {
100
                            // Send error to CM. The CM should close the new-born connection
101 102
                            sendErrorPacket(iq, PacketError.Condition.not_allowed, null);
                        }
Gaston Dombiak's avatar
Gaston Dombiak committed
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 152 153 154 155 156 157 158 159 160 161 162
                    }
                    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);
            }
        }
    }

163 164 165 166 167 168 169 170 171 172 173 174 175 176
    /**
     * 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);
        }
177
        LocalClientSession session = multiplexerManager.getClientSession(connectionManagerDomain, streamID);
178 179 180 181 182
        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
183

184
        SessionPacketRouter router = new SessionPacketRouter(session);
185 186
        // Connection Manager already validate JIDs so just skip this expensive operation
        router.setSkipJIDValidation(true);
Gaston Dombiak's avatar
Gaston Dombiak committed
187
        try {
Alex Wenckus's avatar
Alex Wenckus committed
188
            router.route(route.getChildElement());
Gaston Dombiak's avatar
Gaston Dombiak committed
189
        }
Alex Wenckus's avatar
Alex Wenckus committed
190 191 192 193 194
        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
195
        }
196
        catch (Exception e) {
Alex Wenckus's avatar
Alex Wenckus committed
197 198
            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
199 200 201 202 203 204 205 206 207
        }
    }

    /**
     * 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
208
     * @param error the error.
Gaston Dombiak's avatar
Gaston Dombiak committed
209 210 211 212 213 214 215 216 217 218 219 220
     */
    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);
    }

221 222 223 224 225 226
    /**
     * 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
227
     * @param error the error.
228 229 230 231 232 233 234 235 236 237 238 239 240 241
     */
    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
242 243 244 245 246 247 248 249 250 251 252
    /**
     * 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);
    }

253
    private void deliver(Packet reply) {
Gaston Dombiak's avatar
Gaston Dombiak committed
254 255 256 257
        // Get any session of the connection manager to deliver the packet
        ConnectionMultiplexerSession session =
                multiplexerManager.getMultiplexerSession(connectionManagerDomain);
        if (session != null) {
258
            session.process(reply);
Gaston Dombiak's avatar
Gaston Dombiak committed
259 260 261 262 263 264
        }
        else {
            Log.warn("No multiplexer session found. Packet not delivered: " + reply.toXML());
        }
    }
}