com.test.android.push.xmpp.net.Connection.java Source code

Java tutorial

Introduction

Here is the source code for com.test.android.push.xmpp.net.Connection.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.test.android.push.xmpp.net;

import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.security.KeyStore;

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.ssl.SslFilter;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.xmpp.packet.Packet;

import com.test.android.push.util.Config;
import com.test.android.push.xmpp.session.Session;
import com.test.android.push.xmpp.ssl.SSLConfig;
import com.test.android.push.xmpp.ssl.SSLKeyManagerFactory;
import com.test.android.push.xmpp.ssl.SSLTrustManagerFactory;

/**
 * This class represents a XMPP connection on the server.
 * 
 * @author Sehwan Noh (devnoh@gmail.com)
 */
public class Connection {

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

    private IoSession ioSession;

    private Session session;

    private ConnectionCloseListener closeListener;

    private int majorVersion = 1;

    private int minorVersion = 0;

    private String language = null;

    private TLSPolicy tlsPolicy = TLSPolicy.optional;

    @SuppressWarnings("unchecked")
    private static ThreadLocal encoder = new ThreadLocalEncoder();

    private boolean closed;

    /**
     * Constructor.
     * 
     * @param ioSession the IoSession
     */
    public Connection(IoSession ioSession) {
        this.ioSession = ioSession;
        this.closed = false;
    }

    //    /**
    //     * Verifies that the connection is still live.
    //     * 
    //     * @return true if the socket remains valid, false otherwise.
    //     */
    //    public boolean validate() {
    //        if (isClosed()) {
    //            return false;
    //        }
    //        deliverRawText(" ");
    //        return !isClosed();
    //    }

    /**
     * Closes the session including associated socket connection,
     * notifing all listeners that the channel is shutting down.
     */
    public void close() {
        log.debug("close() start...");
        boolean closedSuccessfully = false;
        synchronized (this) {
            if (!isClosed()) {
                try {
                    deliverRawText("</stream:stream>", false);
                } catch (Exception e) {
                    // Ignore
                }
                if (session != null) {
                    session.setStatus(Session.STATUS_CLOSED);
                }
                ioSession.close(false);
                closed = true;
                closedSuccessfully = true;
            }
        }
        if (closedSuccessfully) {
            notifyCloseListeners();
        }
    }

    /**
     * Sends notification message indicating that the server is being shutdown.
     */
    public void systemShutdown() {
        log.debug("systemShutdown() start...");
        deliverRawText(
                "<stream:error><system-shutdown " + "xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>");
        close();
    }

    /**
     * Initializes the connection with it's owning session.
     * 
     * @param session the session that owns this connection
     */
    public void init(Session session) {
        log.debug("init() start...");
        this.session = session;
    }

    /**
     * Returns true if the connection is closed.
     * 
     * @return true if the connection is closed, false otherwise.
     */
    public boolean isClosed() {
        log.debug("isClosed() start...");
        if (session == null) {
            return closed;
        }
        return session.getStatus() == Session.STATUS_CLOSED;
    }

    //    /**
    //     * Returns true if this connection is secure.
    //     * 
    //     * @return true if the connection is secure
    //     */
    //    public boolean isSecure() {
    //        return ioSession.getFilterChain().contains("tls");
    //    }

    /**
     * Registers a listener for close event notification.
     * 
     * @param listener the listener to register for close events.
     */
    public void registerCloseListener(ConnectionCloseListener listener) {
        log.debug("registerCloseListener() start...");
        if (closeListener != null) {
            throw new IllegalStateException("Close listener already configured");
        }
        if (isClosed()) {
            listener.onConnectionClose(session);
        } else {
            closeListener = listener;
        }
    }

    /**
     *  Removes a registered close event listener.
     *  
     * @param listener the listener to unregister for close events.
     */
    public void unregisterCloseListener(ConnectionCloseListener listener) {
        log.debug("unregisterCloseListener() start...");
        if (closeListener == listener) {
            closeListener = null;
        }
    }

    private void notifyCloseListeners() {
        log.debug("notifyCloseListeners() start...");
        if (closeListener != null) {
            try {
                closeListener.onConnectionClose(session);
            } catch (Exception e) {
                log.error("Error notifying listener: " + closeListener, e);
            }
        }
    }

    /**
     * Delivers the packet to this connection (without checking the recipient).
     * 
     * @param packet the packet to deliver
     */
    public void deliver(Packet packet) {
        log.debug("SENT: " + packet.toXML());
        if (!isClosed()) {
            IoBuffer buffer = IoBuffer.allocate(4096);
            buffer.setAutoExpand(true);

            boolean errorDelivering = false;
            try {
                XMLWriter xmlSerializer = new XMLWriter(new IoBufferWriter(buffer, (CharsetEncoder) encoder.get()),
                        new OutputFormat());
                xmlSerializer.write(packet.getElement());
                xmlSerializer.flush();
                buffer.flip();
                ioSession.write(buffer);
            } catch (Exception e) {
                log.debug("Connection: Error delivering packet" + "\n" + this.toString(), e);
                errorDelivering = true;
            }
            if (errorDelivering) {
                close();
            } else {
                session.incrementServerPacketCount();
            }
        }
        log.debug("deliver() end...");
    }

    /**
     * Delivers raw text to this connection (in asynchronous mode).
     * 
     * @param text the XML stanza string to deliver
     */
    public void deliverRawText(String text) {
        log.debug("deliverRawText() start...");
        deliverRawText(text, true);
    }

    private void deliverRawText(String text, boolean asynchronous) {
        log.debug("SENT: " + text);
        if (!isClosed()) {
            IoBuffer buffer = IoBuffer.allocate(text.length());
            buffer.setAutoExpand(true);

            boolean errorDelivering = false;
            try {
                buffer.put(text.getBytes("UTF-8"));
                buffer.flip();
                if (asynchronous) {
                    ioSession.write(buffer);
                } else {
                    // Send stanza and wait for ACK
                    boolean ok = ioSession.write(buffer)
                            .awaitUninterruptibly(Config.getInt("connection.ack.timeout", 2000));
                    if (!ok) {
                        log.warn("No ACK was received when sending stanza to: " + this.toString());
                    }
                }
            } catch (Exception e) {
                log.debug("Connection: Error delivering raw text" + "\n" + this.toString(), e);
                errorDelivering = true;
            }
            // Close the connection if delivering text fails
            if (errorDelivering && asynchronous) {
                close();
            }
        }
        log.debug("deliverRawText(..) start...");
    }

    public void startTLS(ClientAuth authentication) throws Exception {
        log.debug("startTLS() start...");
        KeyStore ksKeys = SSLConfig.getKeyStore();
        String keypass = SSLConfig.getKeyPassword();

        KeyStore ksTrust = SSLConfig.getc2sTrustStore();
        String trustpass = SSLConfig.getc2sTrustPassword();

        KeyManager[] km = SSLKeyManagerFactory.getKeyManagers(ksKeys, keypass);
        TrustManager[] tm = SSLTrustManagerFactory.getTrustManagers(ksTrust, trustpass);

        SSLContext tlsContext = SSLContext.getInstance("TLS");
        tlsContext.init(km, tm, null);

        SslFilter filter = new SslFilter(tlsContext);
        ioSession.getFilterChain().addFirst("tls", filter);
        //ioSession.getFilterChain().addBefore("executor", "tls", filter);
        ioSession.setAttribute(SslFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);

        deliverRawText("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
        log.debug("startTLS() end...");
    }

    /**
     * Returns the IP address.
     * 
     * @return the IP address
     * @throws UnknownHostException if IP address of host could not be determined.
     */
    public String getHostAddress() throws UnknownHostException {
        log.debug("getHostAddress() start...");
        return ((InetSocketAddress) ioSession.getRemoteAddress()).getAddress().getHostAddress();
    }

    /**
     * Gets the host name for the IP address.
     * 
     * @return the host name for this IP address
     * @throws UnknownHostException  if IP address of host could not be determined.
     */
    public String getHostName() throws UnknownHostException {
        log.debug("getHostName() start...");
        return ((InetSocketAddress) ioSession.getRemoteAddress()).getAddress().getHostName();
    }

    /**
     * Returns the major version of XMPP being used by this connection.
     * 
     * @return the major XMPP version
     */
    public int getMajorXMPPVersion() {
        log.debug("getHostAddress() end...");
        return majorVersion;
    }

    /**
     * Returns the minor version of XMPP being used by this connection.
     * 
     * @return the minor XMPP version
     */
    public int getMinorXMPPVersion() {
        log.debug("getMinorXMPPVersion() end...");
        return minorVersion;
    }

    /**
     * Sets the XMPP version information.
     * 
     * @param majorVersion the major version
     * @param minorVersion the minor version
     */
    public void setXMPPVersion(int majorVersion, int minorVersion) {
        this.majorVersion = majorVersion;
        this.minorVersion = minorVersion;
    }

    /**
     * Returns the language code that should be used for this connection.
     * 
     * @return the language code
     */
    public String getLanguage() {
        return language;
    }

    /**
     * Sets the language code that should be used for this connection.
     * 
     * @param language the language code
     */
    public void setLanaguage(String language) {
        this.language = language;
    }

    @SuppressWarnings("unchecked")
    private static class ThreadLocalEncoder extends ThreadLocal {
        protected Object initialValue() {
            log.debug("initialValue() end...");
            return Charset.forName("UTF-8").newEncoder();
        }
    }

    public TLSPolicy getTlsPolicy() {
        return tlsPolicy;
    }

    public void setTlsPolicy(TLSPolicy tlsPolicy) {
        this.tlsPolicy = tlsPolicy;
    }

    /**
     * Enumeration of possible TLS policies required to interact with the server.
     */
    public enum TLSPolicy {
        required, optional, disabled
    }

    /**
     * Enumeration that specifies if clients should be authenticated (and how)
     * while negotiating TLS.
     */
    public enum ClientAuth {
        disabled, wanted, needed
    }

}