/**
* 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.codecs;
import java.io.OutputStream;
import org.helios.apmrouter.nash.NashConstants;
import org.helios.apmrouter.nash.NashRequest;
import org.helios.apmrouter.nash.codecs.DecodingState;
import org.helios.apmrouter.nash.codecs.NashContextState;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.DownstreamMessageEvent;
import org.jboss.netty.channel.UpstreamMessageEvent;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
/**
* <p>Title: NashRequestDecoder</p>
* <p>Description: The Netty channel handler that decodes a nash request</p>
* <p>Based in great part on:<ol>
* <li><b><code>com.martiansoftware.nailgun.NGSession</code></b> by <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a></li>
* <li><b><code>com.biasedbit.nettytutorials.customcodecs.common.Decoder</code></b> by <a href="http://biasedbit.com/about/">Bruno Decarvalho</a></li>
* </ol>
* <p>The nash stream processor does not rely on a particular in the way that requests are sent, but as of this writing, the order is this:<ol>
* <li><b>ARGUMENTS</b></li>
* <li><b>ENVIRONMENT</b></li>
* <li><b>WORKING_DIR</b></li>
* <li><b>COMMAND</b></li>
* </ol></p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.nash.codecs.NashRequestDecoder</code></p>
*/
public class NashRequestDecoder extends ReplayingDecoder<DecodingState> {
/** The internal logger */
protected final InternalLogger log = InternalLoggerFactory.getInstance(getClass());
/** The request context */
//protected NashContextState ngcontext = new NashContextState();
/** The signal to send the client when we're ready to handle the stream input */
private static final ChannelBuffer STREAM_IN_READY = ChannelBuffers.buffer(5);
/** The signal to send the client when the stream input procesing is complete */
private static final ChannelBuffer STREAM_IN_DONE = ChannelBuffers.buffer(5);
/** THe channel group where all active channels are maintained */
private final ChannelGroup channelGroup = new DefaultChannelGroup("NashServer");
static {
// Prep the stream in readiness buffer
STREAM_IN_READY.writeInt(0);
STREAM_IN_READY.writeByte(NashConstants.CHUNKTYPE_STARTINPUT);
// Prep the stream in complete buffer
STREAM_IN_DONE.writeInt(0);
STREAM_IN_DONE.writeByte(NashConstants.CHUNKTYPE_EXIT);
}
/**
* Returns the current NashContextState
* @param ctx The channel handler context which might have a current context as an attachment
* @return the current NashContextState
*/
protected NashContextState getContext(ChannelHandlerContext ctx) {
NashContextState ncs = (NashContextState) ctx.getAttachment();
if(ncs==null) {
ncs = new NashContextState();
ctx.setAttachment(ncs);
}
return ncs;
}
/**
* Creates a new NashRequestDecoder
*/
public NashRequestDecoder() {
if(log.isDebugEnabled()) log.debug("Created NashRequestDecoder Instance");
checkpoint(DecodingState.BYTES);
//System.out.println("Created NashRequestDecoder Instance");
//reset();
}
/**
* Resets the decoder
* @param ctx The channel handler context
*/
private void reset(ChannelHandlerContext ctx) {
if(log.isDebugEnabled()) log.debug("NashRequestDecoder Reset");
getContext(ctx).cleanup();
checkpoint(DecodingState.BYTES);
}
/**
* Copies the decoding state into the current context and delegates the checkpoint to the super.
* @param ctx The channel handler context
* @param state The current decoding state
*/
protected void checkpoint(ChannelHandlerContext ctx, DecodingState state) {
getContext(ctx).setState(state);
checkpoint(state);
if(log.isDebugEnabled()) log.debug("checkpoint:" + state);
}
@Override
protected void checkpoint(DecodingState state) {
super.checkpoint(state);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.handler.codec.replay.ReplayingDecoder#decode(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, org.jboss.netty.buffer.ChannelBuffer, java.lang.Enum)
*/
@Override
protected NashRequest decode(final ChannelHandlerContext ctx, final Channel channel, ChannelBuffer buffer, DecodingState decodeState) throws Exception {
channel.getCloseFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
reset(ctx);
}
});
if(decodeState==null) return null;
NashContextState context = getContext(ctx);
context.setState(decodeState);
if(context.getMessage().getRemoteAddress()==null) {
channelGroup.add(channel);
channel.getCloseFuture().addListener(new ChannelFutureListener(){
@Override
public void operationComplete(ChannelFuture future) throws Exception {
channelGroup.remove(future.getChannel());
}
});
context.getMessage().setChannel(channel);
}
while(true) {
switch(context.getState()) {
case BYTES:
// set the output stream in the nash request,
// create the piped input stream and connect it to the output stream
// and send the request upstream
int bytesToRead = buffer.readInt();
if(log.isDebugEnabled()) log.debug("NG Chunk [BYTES]:" + bytesToRead);
context.setBytesToRead(bytesToRead);
// if(context.isStdInReady()) {
// context.initOutputStream(bytesToRead);
// ctx.sendUpstream(new UpstreamMessageEvent(channel, context.getMessage(), channel.getRemoteAddress()));
// }
checkpoint(ctx, DecodingState.TYPE);
break;
case TYPE:
byte type = buffer.readByte();
context.setChunkType(type);
checkpoint(ctx, context.getState());
if(log.isDebugEnabled()) log.debug("NG Chunk [" + type + "]:" + NashConstants.decode(type));
break;
case WORKING_DIR:
context.readBytes(buffer);
context.getMessage().setWorkingDirectory(new String(context.getBytes()));
checkpoint(ctx, DecodingState.BYTES);
if(log.isDebugEnabled()) log.debug("NG Chunk [WORKING_DIR]:" + context.getMessage().getWorkingDirectory());
break;
case ENVIRONMENT:
context.readBytes(buffer);
context.getMessage().addToEnvironment(new String(context.getBytes()));
checkpoint(ctx, DecodingState.BYTES);
//if(log.isDebugEnabled()) log.debug("NG Chunk [ENVIRONMENT]:" + new String(bytes));
break;
case ARGUMENTS:
context.readBytes(buffer);
context.getMessage().addArgument(new String(context.getBytes()));
checkpoint(ctx, DecodingState.BYTES);
if(log.isDebugEnabled()) log.debug("NG Chunk [ARGUMENTS]:" + new String(context.getBytes()));
break;
case COMMAND:
context.readBytes(buffer);
context.getMessage().setCommand(new String(context.getBytes()));
if(log.isDebugEnabled()) log.debug("NG Chunk [COMMAND]:" + context.getMessage().getCommand());
// at this point, we can complete the nash request
// and send it for dispatch. However, since we have to kep processing
// a possible input stream from the client, we will keep the decoder looping
// on this request and simply send the NashRequest upstream.
if(log.isDebugEnabled()) log.debug("nash Client Complete:\n" + context.getMessage());
ctx.sendUpstream(new UpstreamMessageEvent(channel, context.getMessage(), channel.getRemoteAddress()));
checkpoint(ctx, DecodingState.BYTES);
context.setStdInReady(true);
// sendStartStdInSignal(ctx, channel);
// If the client has input to send:
// 1. It will send a DecodingState.BYTES with the number of bytes to read
// 2. It will send a DecodingState.TYPE which will be either:
// 2a. STDIN the bytes to be read
// 2b. STDIN_EOF indicating that the input stream is ended and no further bytes will be sent.
break;
case STDIN:
log.info("STDIN Bytes To Read:" + context.getBytesToRead());
OutputStream os = context.getMessage().setStdInStream(context.getBytesToRead());
byte[] stdin = new byte[context.getBytesToRead()-1];
buffer.readBytes(stdin);
buffer.readByte(); buffer.readByte();
os.write(stdin);
log.debug("Wrote [" + stdin.length + "] bytes to Message InputStream");
checkpoint(ctx, DecodingState.TYPE);
case STDIN_EOF:
// this means that the std in has completed
if(log.isDebugEnabled()) log.debug("STDIN EOF: NG Chunk [" + context.getState() + "]");
context.getMessage().cancelStdInStream();
reset(ctx);
//sendCompletedStdInSignal(ctx, channel);
return null;
// case STARTINPUT:
// channel.write(NashConstants.CHUNKTYPE_STARTINPUT).awaitUninterruptibly();
// checkpoint(DecodingState.DEBUG);
// break;
// case STDIN_EOF:
// // the request is complete
// System.out.println(state);
// case STDIN:
// // starting to stream input
// System.out.println(state);
// case EXIT:
// // exit code from client
// System.out.println(state);
// case STARTINPUT:
// System.out.println(state);
// case STDERR:
// System.out.println(state);
// case STDOUT:
// System.out.println(state);
default:
System.out.println("DEBUG:" + (char)buffer.readByte());
// while(true) {
// StringBuilder b = new StringBuilder();
// try {
// b.append((char)buffer.readByte());
// System.out.println(b);
// } catch (Throwable t) {
// System.out.println(b);
// done = true;
// break;
// }
//
// }
// done = true;
break;
// throw new IllegalStateException("Unhandled Decoding State: [" + state + "]", new Throwable());
}
}
}
/**
CHUNKTYPE_ARGUMENT:65:A
CHUNKTYPE_ENVIRONMENT:69:E
CHUNKTYPE_COMMAND:67:C
CHUNKTYPE_WORKINGDIRECTORY:68:D
CHUNKTYPE_STDIN:48:0
CHUNKTYPE_STDIN_EOF:46:.
CHUNKTYPE_STDOUT:49:1
CHUNKTYPE_STDERR:50:2
CHUNKTYPE_EXIT:88:X
CHUNKTYPE_STARTINPUT:83:S
*/
/**
* Sends a signal back to the nash client indicating that we're ready to accept the input stream
* @param ctx The ChannelHandlerContext
* @param channel The channel
*/
protected void sendStartStdInSignal(ChannelHandlerContext ctx, Channel channel) {
DownstreamMessageEvent dme = new DownstreamMessageEvent(channel, Channels.future(channel), STREAM_IN_READY, channel.getRemoteAddress());
ctx.sendDownstream(dme);
}
/**
* Sends a signal back to the nash client indicating that we've completed reading the input stream
* @param ctx The ChannelHandlerContext
* @param channel The channel
*/
protected void sendCompletedStdInSignal(ChannelHandlerContext ctx, Channel channel) {
DownstreamMessageEvent dme = new DownstreamMessageEvent(channel, Channels.future(channel), STREAM_IN_DONE, channel.getRemoteAddress());
ctx.sendDownstream(dme);
channel.close();
}
}