Java tutorial
/* * 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 } }