/* * ConnectionUtils.java * * Created on Apr 1, 2010, 8:59:22 AM * * Description: Provides convenient static methods to establish server and client Netty SSL channels. * * Copyright (C) Apr 1, 2010, Stephen L. Reed. * * 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 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.texai.network.netty; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; import net.jcip.annotations.NotThreadSafe; import org.apache.log4j.Logger; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.texai.network.netty.handler.AbstractAlbusHCSMessageHandler; import org.texai.network.netty.handler.AbstractAlbusHCSMessageHandlerFactory; import org.texai.network.netty.handler.AbstractBitTorrentHandler; import org.texai.network.netty.handler.AbstractBitTorrentHandlerFactory; import org.texai.network.netty.handler.AbstractHTTPRequestHandlerFactory; import org.texai.network.netty.handler.AbstractHTTPResponseHandler; import org.texai.network.netty.pipeline.AlbusHCNMessageClientPipelineFactory; import org.texai.network.netty.pipeline.BitTorrentClientPipelineFactory; import org.texai.network.netty.pipeline.HTTPClientPipelineFactory; import org.texai.network.netty.pipeline.PortUnificationChannelPipelineFactory; import org.texai.util.TexaiException; import org.texai.x509.X509SecurityInfo; /** Provides convenient static methods to establish server and client Netty SSL channels. * * @author reed */ @NotThreadSafe public final class ConnectionUtils { /** the logger */ private static final Logger LOGGER = Logger.getLogger(ConnectionUtils.class); /** the connected channel dictionary, channel latch --> channel */ private final static Map<Object, Channel> connectedChannelDictionary = new HashMap<>(); /** Prevents the instantiation of this utility class. */ private ConnectionUtils() { } /** Creates a port unification server, handling Albus hierarchical control system messages, bit torrent messages, * and HTTP requests, using a single shared socket with SSL encryption. * * @param port the server port * @param x509SecurityInfo the X.509 security information * @param albusHCSMessageHandlerFactory the Albus hierarchical control system message handler factory * @param bitTorrentHandlerFactory the bit torrent message handler factory * @param httpRequestHandlerFactory the HTTP request message handler factory * @param bossExecutor the Executor which will execute the boss threads * @param workerExecutor the Executor which will execute the I/O worker threads * @return the server bootstrap, which contains a new server-side channel and accepts incoming connections */ public static ServerBootstrap createPortUnificationServer( final int port, final X509SecurityInfo x509SecurityInfo, final AbstractAlbusHCSMessageHandlerFactory albusHCSMessageHandlerFactory, final AbstractBitTorrentHandlerFactory bitTorrentHandlerFactory, final AbstractHTTPRequestHandlerFactory httpRequestHandlerFactory, final Executor bossExecutor, final Executor workerExecutor) { //Preconditions assert port >= 0 && port <= 65535 : "invalid port number"; assert bossExecutor != null : "bossExecutor must not be null"; assert workerExecutor != null : "workerExecutor must not be null"; // configure the server channel pipeline factory final ChannelPipelineFactory channelPipelineFactory = new PortUnificationChannelPipelineFactory( albusHCSMessageHandlerFactory, bitTorrentHandlerFactory, httpRequestHandlerFactory, x509SecurityInfo); // configure the server final ServerBootstrap serverBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory( bossExecutor, workerExecutor)); serverBootstrap.setPipelineFactory(channelPipelineFactory); // bind and start to accept incoming connections serverBootstrap.bind(new InetSocketAddress(port)); LOGGER.info("launcher accepting connections on port " + port); return serverBootstrap; } /** Releases the resources held by the port unification server, and closes its associated thread pools. * * @param serverBootstrap the server bootstrap */ public static void closePortUnificationServer(final ServerBootstrap serverBootstrap) { serverBootstrap.releaseExternalResources(); } /** Opens an Albus hierarchical control system message connection using SSL encryption. * * @param inetSocketAddress the IP socket address, host & port * @param x509SecurityInfo the X.509 security information * @param albusHCSMessageHandler the Albus hierarchical control system message handler * @param bossExecutor the Executor which will execute the boss threads * @param workerExecutor the Executor which will execute the I/O worker threads * @return the communication channel */ public static Channel openAlbusHCSConnection( final InetSocketAddress inetSocketAddress, final X509SecurityInfo x509SecurityInfo, final AbstractAlbusHCSMessageHandler albusHCSMessageHandler, final Executor bossExecutor, final Executor workerExecutor) { //Preconditions assert inetSocketAddress != null : "inetSocketAddress must not be null"; assert x509SecurityInfo != null : "x509SecurityInfo must not be null"; assert albusHCSMessageHandler != null : "albusHCSMessageHandler must not be null"; assert bossExecutor != null : "bossExecutor must not be null"; assert workerExecutor != null : "workerExecutor must not be null"; LOGGER.info("creating Albus client bootstrap"); final ClientBootstrap clientBootstrap = new ClientBootstrap(new NioClientSocketChannelFactory( bossExecutor, workerExecutor)); // configure the client pipeline LOGGER.info("configuring the Albus client pipeline"); final ChannelPipeline channelPipeline = AlbusHCNMessageClientPipelineFactory.getPipeline( albusHCSMessageHandler, x509SecurityInfo); clientBootstrap.setPipeline(channelPipeline); // start the connection attempt final Object channelConnection_lock = new Object(); bossExecutor.execute(new ChannelConnector( clientBootstrap, inetSocketAddress, channelConnection_lock)); synchronized (channelConnection_lock) { LOGGER.info("waiting for connection"); try { channelConnection_lock.wait(); } catch (InterruptedException ex) { // ignore } } final Channel channel; synchronized (connectedChannelDictionary) { channel = connectedChannelDictionary.get(channelConnection_lock); assert channel != null; connectedChannelDictionary.remove(channelConnection_lock); } return channel; } /** Provides a channel connector. */ static class ChannelConnector implements Runnable { /** the client bootstrap */ final ClientBootstrap clientBootstrap; /** the IP socket address, host & port */ final InetSocketAddress inetSocketAddress; /** the channel connection synchronization lock */ final Object channelConnection_lock; /** Constructs a new ChannelConnector instance. * * @param clientBootstrap the client bootstrap * @param inetSocketAddress the IP socket address, host & port * @param channelConnection_lock the channel connection synchronization lock */ ChannelConnector( final ClientBootstrap clientBootstrap, final InetSocketAddress inetSocketAddress, final Object channelConnection_lock) { //Preconditions assert clientBootstrap != null : "clientBootstrap must not be null"; assert inetSocketAddress != null : "inetSocketAddress must not be null"; assert channelConnection_lock != null : "channelConnection_lock must not be null"; this.clientBootstrap = clientBootstrap; this.inetSocketAddress = inetSocketAddress; this.channelConnection_lock = channelConnection_lock; } /** Connects to a channel. */ @Override public void run() { // start the connection attempt LOGGER.info("connecting Albus client"); clientBootstrap.connect(inetSocketAddress).addListener(new MyChannelFutureListener( channelConnection_lock, inetSocketAddress)); } } /** Provides a channel future listener that resumes the thread waiting for the channel connection event. */ static class MyChannelFutureListener implements ChannelFutureListener { /** the channel connection synchronization lock */ final Object channelConnection_lock; /** the IP socket address, host & port */ final InetSocketAddress inetSocketAddress; /** Constructs a new MyChannelFutureListener instance. * * @param channelConnection_lock the channel connection synchronization lock * @param inetSocketAddress the IP socket address, host & port */ MyChannelFutureListener( final Object channelConnection_lock, final InetSocketAddress inetSocketAddress) { //Preconditions assert channelConnection_lock != null : "channelConnection_lock must not be null"; assert inetSocketAddress != null : "inetSocketAddress must not be null"; this.channelConnection_lock = channelConnection_lock; this.inetSocketAddress = inetSocketAddress; } /** Invoked when the I/O operation associated with the {@link ChannelFuture} * has been completed. * * @param channelFuture The source {@link ChannelFuture} which called this * callback. * @throws java.lang.Exception */ @Override @SuppressWarnings("ThrowableResultIgnored") public void operationComplete(final ChannelFuture channelFuture) throws Exception { //Preconditions assert channelFuture != null : "future must not be null"; if (!channelFuture.isSuccess()) { LOGGER.info("cannot connect with " + inetSocketAddress); throw new TexaiException(channelFuture.getCause()); } LOGGER.info("Albus client connected"); synchronized (connectedChannelDictionary) { connectedChannelDictionary.put(channelConnection_lock, channelFuture.getChannel()); } synchronized (channelConnection_lock) { channelConnection_lock.notify(); } } } /** Opens bit torrent message connection using SSL encryption. * * @param inetSocketAddress the IP socket address, host & port * @param x509SecurityInfo the X.509 security information * @param bitTorrentHandler the bit torrent message handler * @param bossExecutor the Executor which will execute the boss threads * @param workerExecutor the Executor which will execute the I/O worker threads * @return the communication channel */ @SuppressWarnings("ThrowableResultIgnored") public static Channel openBitTorrentConnection( final InetSocketAddress inetSocketAddress, final X509SecurityInfo x509SecurityInfo, final AbstractBitTorrentHandler bitTorrentHandler, final Executor bossExecutor, final Executor workerExecutor) { //Preconditions assert inetSocketAddress != null : "inetSocketAddress must not be null"; assert x509SecurityInfo != null : "x509SecurityInfo must not be null"; assert bitTorrentHandler != null : "bitTorrentHandler must not be null"; assert bossExecutor != null : "bossExecutor must not be null"; assert workerExecutor != null : "workerExecutor must not be null"; final ClientBootstrap clientBootstrap = new ClientBootstrap(new NioClientSocketChannelFactory( bossExecutor, workerExecutor)); // configure the client pipeline final ChannelPipeline channelPipeline = BitTorrentClientPipelineFactory.getPipeline( bitTorrentHandler, x509SecurityInfo); clientBootstrap.setPipeline(channelPipeline); // start the connection attempt final ChannelFuture channelFuture = clientBootstrap.connect(inetSocketAddress); // wait until the connection attempt succeeds or fails final Channel channel = channelFuture.awaitUninterruptibly().getChannel(); if (!channelFuture.isSuccess()) { throw new TexaiException(channelFuture.getCause()); } LOGGER.info("bit torrent client connected"); return channel; } /** Opens an HTTP message connection using SSL encryption. * * @param inetSocketAddress the IP socket address, host & port * @param x509SecurityInfo the X.509 security information * @param httpResponseHandler the HTTP response message handler * @param bossExecutor the Executor which will execute the boss threads * @param workerExecutor the Executor which will execute the I/O worker threads * @return the communication channel */ @SuppressWarnings("ThrowableResultIgnored") public static Channel openHTTPConnection( final InetSocketAddress inetSocketAddress, final X509SecurityInfo x509SecurityInfo, final AbstractHTTPResponseHandler httpResponseHandler, final Executor bossExecutor, final Executor workerExecutor) { //Preconditions assert inetSocketAddress != null : "inetSocketAddress must not be null"; assert x509SecurityInfo != null : "x509SecurityInfo must not be null"; assert httpResponseHandler != null : "httpResponseHandler must not be null"; assert bossExecutor != null : "bossExecutor must not be null"; assert workerExecutor != null : "workerExecutor must not be null"; final ClientBootstrap clientBootstrap = new ClientBootstrap(new NioClientSocketChannelFactory( bossExecutor, workerExecutor)); // configure the client pipeline final ChannelPipeline channelPipeline = HTTPClientPipelineFactory.getPipeline( httpResponseHandler, x509SecurityInfo); clientBootstrap.setPipeline(channelPipeline); // start the connection attempt final ChannelFuture channelFuture = clientBootstrap.connect(inetSocketAddress); // wait until the connection attempt succeeds or fails final Channel channel = channelFuture.awaitUninterruptibly().getChannel(); if (!channelFuture.isSuccess()) { throw new TexaiException(channelFuture.getCause()); } LOGGER.info("HTTP client connected"); return channel; } }