com.hand.hemp.push.server.xmpp.net.StanzaHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.hand.hemp.push.server.xmpp.net.StanzaHandler.java

Source

/*
 * Copyright (C) 2010 Moduad Co., Ltd.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package com.hand.hemp.push.server.xmpp.net;

import com.hand.hemp.push.server.entities.HpnsNotification;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.Random;

import com.hand.hemp.push.server.service.NotificationService;
import com.hand.hemp.push.server.service.ServiceLocator;
import com.hand.hemp.push.server.util.Config;
import com.hand.hemp.push.server.xmpp.push.NotificationManager;
import com.hand.hemp.push.server.xmpp.router.PacketRouter;
import com.hand.hemp.push.server.xmpp.session.ClientSession;
import com.hand.hemp.push.server.xmpp.session.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.openfire.net.MXParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
import org.xmpp.packet.Roster;
import org.xmpp.packet.StreamError;

/**
 * This class is to handle incoming XML stanzas.
 *
 * @author HAND
 */
public class StanzaHandler {

    private static final Log log = LogFactory.getLog(StanzaHandler.class);

    protected Connection connection;

    protected Session session;

    protected String serverName;

    private boolean sessionCreated = false;

    private boolean startedTLS = false;

    private PacketRouter router;

    private NotificationService notificationService;

    private NotificationManager notificationManager;

    /**
     * Constructor.
     *
     * @param serverName the server name
     * @param connection the connection
     */
    public StanzaHandler(String serverName, Connection connection) {
        this.serverName = serverName;
        this.connection = connection;
        this.router = new PacketRouter();
        this.notificationService = ServiceLocator.getNotificationService();
        this.notificationManager = new NotificationManager();
    }

    /**
     * Process the received stanza using the given XMPP packet reader.
     *
     * @param stanza the received statza
     * @param reader the XMPP packet reader
     * @throws Exception if the XML stream is not valid.
     */
    public void process(String stanza, XMPPPacketReader reader) throws Exception {
        boolean initialStream = stanza.startsWith("<stream:stream");
        if (!sessionCreated || initialStream) {
            if (!initialStream) {
                return; // Ignore <?xml version="1.0"?>
            }
            if (!sessionCreated) {
                sessionCreated = true;
                MXParser parser = reader.getXPPParser();
                parser.setInput(new StringReader(stanza));
                createSession(parser);
            } else if (startedTLS) {
                startedTLS = false;
                tlsNegotiated();
            }
            return;
        }

        // If end of stream was requested
        if (stanza.equals("</stream:stream>")) {
            session.close();
            return;
        }
        // Ignore <?xml version="1.0"?>
        if (stanza.startsWith("<?xml")) {
            return;
        }
        // Create DOM object
        Element doc = reader.read(new StringReader(stanza)).getRootElement();
        if (doc == null) {
            return;
        }

        String tag = doc.getName();
        if ("starttls".equals(tag)) {
            if (negotiateTLS()) { // Negotiate TLS
                startedTLS = true;
            } else {
                connection.close();
                session = null;
            }
        } else if ("message".equals(tag)) {
            processMessage(doc);
        } else if ("presence".equals(tag)) {
            log.debug("presence...");
            processPresence(doc);
        } else if ("iq".equals(tag)) {
            log.debug("iq...");
            processIQ(doc);
        } else {
            log.warn("Unexpected packet tag (not message, iq, presence)" + doc.asXML());
            session.close();
        }

    }

    private void processMessage(Element doc) {
        log.debug("processMessage()...");
        Message packet;
        try {
            packet = new Message(doc, false);
        } catch (IllegalArgumentException e) {
            log.debug("Rejecting packet. JID malformed", e);
            Message reply = new Message();
            reply.setID(doc.attributeValue("id"));
            reply.setTo(session.getAddress());
            reply.getElement().addAttribute("from", doc.attributeValue("to"));
            reply.setError(PacketError.Condition.jid_malformed);
            session.process(reply);
            return;
        }

        packet.setFrom(session.getAddress());
        router.route(packet);
        session.incrementClientPacketCount();
    }

    private void processPresence(Element doc) {
        log.debug("processPresence()...");
        Presence packet;
        try {
            packet = new Presence(doc, false);
        } catch (IllegalArgumentException e) {
            log.debug("Rejecting packet. JID malformed", e);
            Presence reply = new Presence();
            reply.setID(doc.attributeValue("id"));
            reply.setTo(session.getAddress());
            reply.getElement().addAttribute("from", doc.attributeValue("to"));
            reply.setError(PacketError.Condition.jid_malformed);
            session.process(reply);
            return;
        }
        if (session.getStatus() == Session.STATUS_CLOSED && packet.isAvailable()) {
            log.warn("Ignoring available presence packet of closed session: " + packet);
            return;
        }
        packet.setFrom(session.getAddress());
        router.route(packet);
        session.incrementClientPacketCount();

        // send offline notifications when client online
        if (session.getStatus() == Session.STATUS_AUTHENTICATED && packet.isAvailable()) {
            // get username
            String userName = session.getAddress().getNode();
            if (null != userName && !"".equals(userName)) {
                // query offline notifications by username
                List<HpnsNotification> list = notificationService.queryNotSendNotification(userName);
                if (list != null && !list.isEmpty()) {
                    // send all offline notification about this client
                    for (HpnsNotification notification : list) {
                        // send notification
                        notificationManager.sendOfflineNotification(notification);
                    }
                } else {
                    log.info(" no offline notification, username = " + userName);
                }
            } else {
                log.warn("userName is null !!!!!!");
            }

        }
    }

    private void processIQ(Element doc) {
        log.debug("processIQ()...");
        IQ packet;
        try {
            packet = getIQ(doc);
        } catch (IllegalArgumentException e) {
            log.debug("Rejecting packet. JID malformed", e);
            IQ reply = new IQ();
            if (!doc.elements().isEmpty()) {
                reply.setChildElement(((Element) doc.elements().get(0)).createCopy());
            }
            reply.setID(doc.attributeValue("id"));
            reply.setTo(session.getAddress());
            String to = doc.attributeValue("to");
            if (to != null) {
                reply.getElement().addAttribute("from", to);
            }
            reply.setError(PacketError.Condition.jid_malformed);
            session.process(reply);
            return;
        }

        //        if (packet.getID() == null) {
        //            // IQ packets MUST have an 'id' attribute
        //            StreamError error = new StreamError(
        //                    StreamError.Condition.invalid_xml);
        //            session.deliverRawText(error.toXML());
        //            session.close();
        //            return;
        //        }
        packet.setFrom(session.getAddress());
        router.route(packet);
        session.incrementClientPacketCount();
    }

    private IQ getIQ(Element doc) {
        Element query = doc.element("query");
        if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) {
            return new Roster(doc);
        } else {
            return new IQ(doc, false);
        }
    }

    private void createSession(XmlPullParser xpp) throws XmlPullParserException, IOException {
        for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
            eventType = xpp.next();
        }
        // Create the correct session based on the sent namespace
        String namespace = xpp.getNamespace(null);
        if ("jabber:client".equals(namespace)) {
            session = ClientSession.createSession(serverName, connection, xpp);
            if (session == null) {
                StringBuilder sb = new StringBuilder(250);
                sb.append("<?xml version='1.0' encoding='UTF-8'?>");
                sb.append("<stream:stream from=\"").append(serverName);
                sb.append("\" id=\"").append(randomString(5));
                sb.append("\" xmlns=\"").append(xpp.getNamespace(null));
                sb.append("\" xmlns:stream=\"").append(xpp.getNamespace("stream"));
                sb.append("\" version=\"1.0\">");

                // bad-namespace-prefix in the response
                StreamError error = new StreamError(StreamError.Condition.bad_namespace_prefix);
                sb.append(error.toXML());
                connection.deliverRawText(sb.toString());
                connection.close();
                log.warn("Closing session due to bad_namespace_prefix in stream header: " + namespace);
            }
        }
    }

    private boolean negotiateTLS() {
        if (connection.getTlsPolicy() == Connection.TLSPolicy.disabled) {
            // Set the not_authorized error
            StreamError error = new StreamError(StreamError.Condition.not_authorized);
            connection.deliverRawText(error.toXML());
            connection.close();
            log.warn("TLS requested by initiator when TLS was never offered" + " by server. Closing connection : "
                    + connection);
            return false;
        }
        // Client requested to secure the connection using TLS.
        try {
            startTLS();
        } catch (Exception e) {
            log.error("Error while negotiating TLS", e);
            connection.deliverRawText("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
            connection.close();
            return false;
        }
        return true;
    }

    private void startTLS() throws Exception {
        Connection.ClientAuth policy;
        try {
            policy = Connection.ClientAuth.valueOf(Config.getString("xmpp.client.cert.policy", "disabled"));
        } catch (IllegalArgumentException e) {
            policy = Connection.ClientAuth.disabled;
        }
        connection.startTLS(policy);
    }

    private void tlsNegotiated() {
        // Offer stream features including SASL Mechanisms
        StringBuilder sb = new StringBuilder(620);
        sb.append("<?xml version='1.0' encoding='UTF-8'?>");
        sb.append("<stream:stream ");
        sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
        sb.append("xmlns=\"jabber:client\" from=\"");
        sb.append(serverName);
        sb.append("\" id=\"");
        sb.append(session.getStreamID());
        sb.append("\" xml:lang=\"");
        sb.append(connection.getLanguage());
        sb.append("\" version=\"");
        sb.append(Session.MAJOR_VERSION).append(".").append(Session.MINOR_VERSION);
        sb.append("\">");
        sb.append("<stream:features>");
        // Include specific features such as auth and register for client sessions
        String specificFeatures = session.getAvailableStreamFeatures();
        if (specificFeatures != null) {
            sb.append(specificFeatures);
        }
        sb.append("</stream:features>");
        connection.deliverRawText(sb.toString());
    }

    private String randomString(int length) {
        if (length < 1) {
            return null;
        }
        char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
                .toCharArray();
        char[] randBuffer = new char[length];
        for (int i = 0; i < randBuffer.length; i++) {
            randBuffer[i] = numbersAndLetters[new Random().nextInt(71)];
        }
        return new String(randBuffer);
    }

    //  public String getNamespace() {
    //  return "jabber:client";
    //}
}