/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.nash.streams; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.helios.apmrouter.nash.streams.StreamServer; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.local.DefaultLocalClientChannelFactory; import org.jboss.netty.channel.local.LocalAddress; import org.jboss.netty.channel.local.LocalClientChannelFactory; import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder; import org.jboss.netty.handler.codec.frame.Delimiters; import org.jboss.netty.handler.codec.string.StringDecoder; import org.jboss.netty.handler.execution.ExecutionHandler; import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; import org.jboss.netty.util.CharsetUtil; /** * <p>Title: StreamServer</p> * <p>Description: Local netty server that bridges streams from the ng client to handlers interested in processing STDIN from the client and sending STDOUT, STDERR and exit codes to the ng client.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.nash.streams.StreamServer</code></p> */ public class StreamServer implements ChannelPipelineFactory, ChannelFactory { /** The local address of the StreamServer */ public static final String LOCAL_STREAMS_ADDRESS = "NGStreamServer"; /** The singleton instance */ protected static volatile StreamServer instance = null; /** The singleton instance ctor lock */ protected static final Object lock = new Object(); /** The address impl for the StreamServer */ protected final LocalAddress localAddress = new LocalAddress(LOCAL_STREAMS_ADDRESS); /** The server bootstrap */ protected final ServerBootstrap serverBootstrap = new ServerBootstrap(this); /** The thread factory providing threads to the execution handler */ protected final ThreadFactory threadFactory = new ThreadFactory(){ public Thread newThread(Runnable r) { Thread t = new Thread(r, "StreamServerThread#" + serial.incrementAndGet()); t.setDaemon(true); return t; } }; /** A serial number factory for the executor threads */ protected final AtomicLong serial = new AtomicLong(0L); /** The execution handler to handle the incoming byte streams */ /* * Need to specify some configurability here. */ protected final ExecutionHandler executionHandler = new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(10, 0, 0, 60, TimeUnit.SECONDS, threadFactory)); /** The default channel pipeline factory instance */ protected final ChannelPipelineFactory DEFAULT_PIPELINE_FACTORY = new DefaultPipelineFactory(); /** The local channel factory */ protected final LocalClientChannelFactory channelFactory = new DefaultLocalClientChannelFactory(); /** * Acquires the stream server singleton instance * @return the stream server singleton instance */ public static StreamServer getInstance() { if(instance==null) { synchronized(lock) { if(instance==null) { instance = new StreamServer(); } } } return instance; } /** * Creates a new StreamServer */ private StreamServer() { } /** * <p>Returns the default pipeline. See {@link DefaultPipelineFactory}.</p> * {@inheritDoc} * @see org.jboss.netty.channel.ChannelPipelineFactory#getPipeline() */ @Override public ChannelPipeline getPipeline() throws Exception { return DEFAULT_PIPELINE_FACTORY.getPipeline(); } /** * Creates a new pipeline using the passed handlers to populate as well as the execution handler * @param handlers An array of handlers * @return a new pipeline * @throws Exception */ public ChannelPipeline getPipeline(ChannelHandler...handlers) throws Exception { if(handlers==null || handlers.length<1) { return DEFAULT_PIPELINE_FACTORY.getPipeline(); } ChannelPipeline pipeline = Channels.pipeline(handlers); pipeline.addFirst("executionHandler", executionHandler); return pipeline; } /** * Returns an empty pipeline excepting the execution handler. If unmodified, any added endpoint will simply receive ChannelBuffers. * @return an empty pipeline */ public ChannelPipeline getEmptyPipeline() { ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("executionHandler", executionHandler); return pipeline; } /** * <p>Creates a new channel with the default pipeline. * {@inheritDoc} * @see org.jboss.netty.channel.ChannelFactory#newChannel(org.jboss.netty.channel.ChannelPipeline) */ public Channel newChannel(ChannelPipeline pipeline) { try { return channelFactory.newChannel(getPipeline()); } catch (Exception e) { throw new RuntimeException("Failed to create new channel", e); } } /** * Creates a new channel with a pipeeline implementing the passed map of channel handlers * @param handlers a map of channel handlers that will be added to the pipeline * @return a channel connected to the stream handler specified pipeline */ public Channel newChannel(LinkedHashMap<String, ChannelHandler> handlers) { ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("executionHandler", executionHandler); for(Map.Entry<String, ChannelHandler> entry: handlers.entrySet()) { pipeline.addLast(entry.getKey(), entry.getValue()); } return channelFactory.newChannel(pipeline); } /** * Creates a new channel with a pipeeline implementing the passed channel handlers * @param handlers an array of channel handlers to add to the pipeline * @return a channel connected to the stream handler specified pipeline */ public Channel newChannel(ChannelHandler...handlers) { ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("executionHandler", executionHandler); int id = 1; if(handlers!=null) { for(ChannelHandler handler: handlers) { if(handler!=null) { pipeline.addFirst("handler#" + id, handler); id++; } } } return channelFactory.newChannel(pipeline); } /** * {@inheritDoc} * @see org.jboss.netty.channel.ChannelFactory#releaseExternalResources() */ @Override public void releaseExternalResources() { channelFactory.releaseExternalResources(); // stop executor } /** * <p>Title: DefaultPipelineFactory</p> * <p>Description: The default pipeline factory. The default pipeline contains:<ol> * <li>The execution handler so that streaming is handed off to a new thread</li> * <li>A frame decoder with a 1024 line length maximum and an EOL line delimeter</li> * <li>A string decoder for the UTF-8 character set</li> * </ol></p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.nash.streams.StreamServer.DefaultPipelineFactory</code></p> */ public class DefaultPipelineFactory implements ChannelPipelineFactory { @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("executionHandler", executionHandler); pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter())); pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8)); return pipeline; } } }