/* * Copyright (C) 2011 Christopher Probst * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the 'FoxNet RMI' nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.indyforge.foxnet.rmi.transport.network; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandler.Sharable; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ServerChannel; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.handler.codec.serialization.ClassResolvers; import org.jboss.netty.handler.codec.serialization.ObjectDecoder; import org.jboss.netty.handler.codec.serialization.ObjectEncoder; import org.jboss.netty.util.internal.ExecutorUtil; import com.indyforge.foxnet.rmi.InvokerManager; import com.indyforge.foxnet.rmi.binding.registry.StaticRegistry; import com.indyforge.foxnet.rmi.transport.network.handler.invocation.InvokerHandler; import com.indyforge.foxnet.rmi.transport.network.handler.lookup.LookupHandler; import com.indyforge.foxnet.rmi.transport.network.handler.reqres.ReqResHandler; import com.indyforge.foxnet.rmi.transport.network.handler.setup.SetupHandler; /** * @author Christopher Probst */ public final class ConnectionManager implements ChannelPipelineFactory { /** * @param channel * The channel. * @return the connection manager of the given channel. */ public static ConnectionManager of(Channel channel) { if (channel == null) { throw new NullPointerException("channel"); } // Get the channel handler context ChannelHandlerContext ctx = channel.getPipeline().getContext( IdentificationHandler.class); return ctx != null ? (ConnectionManager) ctx.getAttachment() : null; } /* * Used to identify channels. */ @Sharable private final class IdentificationHandler extends SimpleChannelUpstreamHandler { /* * (non-Javadoc) * * @see * org.jboss.netty.channel.SimpleChannelUpstreamHandler#channelOpen( * org.jboss.netty.channel.ChannelHandlerContext, * org.jboss.netty.channel.ChannelStateEvent) */ @Override public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { // Set attachment ctx.setAttachment(ConnectionManager.this); // Add server channel! channels.add(e.getChannel()); super.channelOpen(ctx, e); } } // The bootstraps of this connection manager private final ServerBootstrap serverBootstrap; private final ClientBootstrap clientBootstrap; // The identification handler of this connection manager private final IdentificationHandler identificationHandler = new IdentificationHandler(); // Used to bind targets statically private final StaticRegistry staticRegistry = new StaticRegistry(); // Cached network executor private final Executor networkExecutor = Executors.newCachedThreadPool(); // The channel factories which are used to create channels private final ChannelFactory serverChannelFactory, clientChannelFactory; // Used to invoke methods private final Executor methodInvocator; // Used to limit thread usage private final ThreadUsage threadUsage; // Create a channel group to store connections private final ChannelGroup channels = new DefaultChannelGroup(); // The disposed flag private final AtomicBoolean disposed = new AtomicBoolean(false); public ConnectionManager(boolean serversOnly) { this(null, serversOnly, !serversOnly); } public ConnectionManager(boolean supportServers, boolean supportClients) { this(null, supportServers, supportClients); } public ConnectionManager(ThreadUsage threadUsage, boolean supportServers, boolean supportClients) { if (!supportServers && !supportClients) { throw new IllegalArgumentException("You must provide " + "at least one component"); } // Check thread usage if (threadUsage == null) { threadUsage = ThreadUsage.DEFAULT; } // Save the thread usage this.threadUsage = threadUsage; // The default method executor methodInvocator = Executors .newFixedThreadPool(threadUsage.invocationThreads); // If this manager should handle servers if (supportServers) { // Create the channel factoriy serverChannelFactory = new NioServerSocketChannelFactory( networkExecutor, networkExecutor, threadUsage.networkThreads); // Create the bootstrap serverBootstrap = new ServerBootstrap(serverChannelFactory); // Setup the pipeline factory serverBootstrap.setPipelineFactory(this); // Add the parent handler serverBootstrap.setParentHandler(identificationHandler); } else { serverChannelFactory = null; serverBootstrap = null; } // If this manager should handle clients if (supportClients) { // Create the channel factoriy clientChannelFactory = new NioClientSocketChannelFactory( networkExecutor, networkExecutor, threadUsage.networkThreads); // Create the bootstrap clientBootstrap = new ClientBootstrap(clientChannelFactory); // Setup the pipeline factory clientBootstrap.setPipelineFactory(this); } else { clientChannelFactory = null; clientBootstrap = null; } } public InvokerManager openClient(SocketAddress socketAddress) throws IOException { // Get future ChannelFuture connectFuture = openClientAsync(socketAddress); // Await! connectFuture.awaitUninterruptibly(); if (!connectFuture.isSuccess()) { throw new IOException("Connection failed", connectFuture.getCause()); } // Extract handler return InvokerHandler.of(connectFuture.getChannel()); } public InvokerManager openClient(String host, int port) throws IOException { return openClient(new InetSocketAddress(host, port)); } public ChannelFuture openClientAsync(SocketAddress socketAddress) { if (!isSupportingClients()) { throw new IllegalStateException("This connection manager " + "does not support clients"); } // Connect to server return clientBootstrap.connect(socketAddress); } public ServerChannel openServer(int port) { return openServer(new InetSocketAddress(port)); } public ServerChannel openServer(SocketAddress socketAddress) { if (!isSupportingServers()) { throw new IllegalStateException("This connection manager " + "does not support servers"); } return (ServerChannel) serverBootstrap.bind(socketAddress); } @Override public ChannelPipeline getPipeline() throws Exception { // Setup the pipeline ChannelPipeline channelPipeline = Channels.pipeline(); // channelPipeline.addLast("t", new SimpleChannelHandler() { // // long d = System.currentTimeMillis(); // int count = 0; // // @Override // public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) // throws Exception { // // synchronized (ctx) { // count += ((ChannelBuffer) e.getMessage()).readableBytes(); // // if (System.currentTimeMillis() - d > 1000) { // System.out.println(count + " Bytes/s"); // count = 0; // d = System.currentTimeMillis(); // } // } // // super.writeRequested(ctx, e); // } // }); // Used to identify the channel channelPipeline.addLast("id_handler", identificationHandler); // Add good compression // channelPipeline.addLast("in_com", new ZlibDecoder()); // channelPipeline.addLast("out_com", new ZlibEncoder(1)); // Add config manager channelPipeline.addLast("cfg", SetupHandler.INSTANCE); // Use the default decoder channelPipeline.addLast( "obj_decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers .weakCachingResolver(Thread.currentThread() .getContextClassLoader()))); // Use the default encoder channelPipeline.addLast("obj_encoder", new ObjectEncoder(0xFFFF)); // The request response handler channelPipeline.addLast("reqres", ReqResHandler.INSTANCE); // Support lookup channelPipeline.addLast("lookup", LookupHandler.INSTANCE); // Handle invocations channelPipeline.addLast("invocator", new InvokerHandler()); return channelPipeline; } public boolean isSupportingServers() { return serverBootstrap != null && serverChannelFactory != null; } public boolean isSupportingClients() { return clientBootstrap != null && clientChannelFactory != null; } public ThreadUsage threadUsage() { return threadUsage; } public Executor networkExecutor() { return networkExecutor; } public Executor methodInvocator() { return methodInvocator; } public ChannelGroup channels() { return channels; } public StaticRegistry staticReg() { return staticRegistry; } public boolean isDisposed() { return disposed.get(); } public ConnectionManager dispose() { /* * Only dispose once. */ if (!disposed.getAndSet(true)) { // Close all channels and wait uninterruptibly channels.close().awaitUninterruptibly(); // Release resources ExecutorUtil.terminate(methodInvocator); if (isSupportingServers()) { serverBootstrap.releaseExternalResources(); } if (isSupportingClients()) { clientBootstrap.releaseExternalResources(); } } return this; } }