/**
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.helios.apmrouter.nash.codecs.NashRequestDecoder;
import org.helios.apmrouter.nash.streams.ConnectTimeoutPipedInputStream;
import org.helios.apmrouter.nash.util.Banner;
import org.helios.apmrouter.nash.DefaultNashRequestImpl;
import org.helios.apmrouter.nash.NashConstants;
import org.helios.apmrouter.nash.NashRequest;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.DownstreamMessageEvent;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
/**
* <p>Title: DefaultNashRequestImpl</p>
* <p>Description: Instances of this class are created by the {@link NashRequestDecoder} when a nash request is received. It represents the contents of the request.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.nash.DefaultNashRequestImpl</code></p>
*/
public class DefaultNashRequestImpl implements Serializable, NashRequest {
/** */
private static final long serialVersionUID = -3507580873475441885L;
/** Static class logger */
protected static final InternalLogger log = InternalLoggerFactory.getInstance(NashRequest.class);
/** The command or specified invocation target */
private String command;
/** The caller's command line working directory */
private String workingDirectory;
/** The caller's environment */
private final Properties environment = new Properties();
/** The caller's command line arguments */
private final List<String> arguments = new ArrayList<String>();
/** The netty channel through which the client is communicating */
private transient Channel channel = null;
/** The presumed exit code set based on the last output */
private transient int exitCode = 0;
/** The input stream used when the command handler wishes to read the nash input stream as an output stream */
protected transient PipedInputStream pipeIn = null;
/** The output stream used by the NashRequestDecoder to write to the STDIN inout stream read by the command handler */
protected transient PipedOutputStream pipeOut = null;
/** The latch that guards the STDIN readers when STDIN inout is requested until the nash client responds */
protected transient volatile CountDownLatch stdInLatch = null;
/** The name of the response encoding channel handler */
public static final String RESP_HANDLER = "response-encoder";
/** 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);
static {
// Prep the stream in readiness buffer
STREAM_IN_READY.writeInt(0);
STREAM_IN_READY.writeByte(NashConstants.CHUNKTYPE_STARTINPUT);
}
/**
* Creates a new DefaultNashRequestImpl
* @param command The command or specified invocation target
* @param workingDirectory The caller's command line working directory
* @param environment The caller's environment
* @param arguments The caller's command line arguments
* @return a new DefaultNashRequestImpl
*/
public static NashRequest newInstance(String command, String workingDirectory, Properties environment, String...arguments) {
return new DefaultNashRequestImpl(command, workingDirectory, environment, arguments);
}
/**
* Creates a new DefaultNashRequestImpl
* @param command The command or specified invocation target
* @param workingDirectory The caller's command line working directory
* @param environment The caller's environment
* @param arguments The caller's command line arguments
*/
public DefaultNashRequestImpl(String command, String workingDirectory, Properties environment, String...arguments) {
this.command = command;
this.workingDirectory = workingDirectory;
this.environment.putAll(environment);
for(String arg: arguments) {
if(arg!=null && !arg.isEmpty()) {
this.arguments.add(arg);
}
}
}
/**
* Creates a new DefaultNashRequestImpl
*/
public DefaultNashRequestImpl() {
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#getCommand()
*/
@Override
public String getCommand() {
return command;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#getWorkingDirectory()
*/
@Override
public String getWorkingDirectory() {
return workingDirectory;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#getEnvironment()
*/
@Override
public Properties getEnvironment() {
return environment;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#getArguments()
*/
@Override
public String[] getArguments() {
return arguments.toArray(new String[arguments.size()]);
}
/**
* Sets the command
* @param command the command to set
*/
public void setCommand(String command) {
this.command = command;
}
/**
* Sets the working directory
* @param workingDirectory the workingDirectory to set
*/
public void setWorkingDirectory(String workingDirectory) {
this.workingDirectory = workingDirectory;
}
/**
* Sets the environment
* @param environment the environment to set
*/
public void setEnvironment(Properties environment) {
this.environment.putAll(environment);
}
/**
* Adds a property to the remote environment
* @param key The property key
* @param value The property value
*/
public void addToEnvironment(String key, String value) {
this.environment.setProperty(key, value);
}
/**
* Adds a property to the remote environment
* @param line The property line (i.e. <code>KEY=VALUE</code>)
*/
public void addToEnvironment(String line) {
int equalsIndex = line.indexOf('=');
if (equalsIndex > 0) {
environment.setProperty(line.substring(0, equalsIndex),line.substring(equalsIndex + 1));
}
}
/**
* Sets the arguments
* @param arguments the arguments to set
*/
public void setArguments(String[] arguments) {
Collections.addAll(this.arguments, arguments);
}
/**
* Adds an argument to the request
* @param arg the argument to add
*/
public void addArgument(String arg) {
this.arguments.add(arg);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#getRemoteAddress()
*/
@Override
public InetAddress getRemoteAddress() {
return channel==null ? null : ((InetSocketAddress)channel.getRemoteAddress()).getAddress();
}
public String printEnvironment() {
StringBuilder b = new StringBuilder(environment.size()*30);
TreeMap<?, ?> env = new TreeMap<Object, Object>(environment);
for(Map.Entry<?, ?> entry: env.entrySet()) {
b.append(entry.getKey()).append("=").append(entry.getValue()).append("\n");
}
return b.toString();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#getRemotePort()
*/
@Override
public int getRemotePort() {
return channel==null ? -1: ((InetSocketAddress)channel.getRemoteAddress()).getPort();
}
/**
* Returns the channel
* @return the channel
*/
public Channel getChannel() {
return channel;
}
/**
* Sets the channel
* @param channel the channel to set
*/
public void setChannel(Channel channel) {
this.channel = channel;
}
/*
* OnInputStreamStart
* OnInoutStreamEOF
* getAvailableBytes()
*
*
*
* InputStream (reads from ng client), returns -1 on end of stream
* onChannelClosed
* -- How do we timeout when the ng client is not sending any input ?
*/
/*
* writeInt(len);
writeByte(streamCode);
out.write(b, offset, len);
*/
/**
* Initializes the STDIN stream processing pipe when the nash client starts streaming STDIN
* @param initialSize The initial size of the input stream pipe
* @return the output stream that the NashRequestDecoder will use to write STDIN
* @throws IOException thrown if the pipe connect setup fails
*/
public OutputStream setStdInStream(int initialSize) throws IOException {
if(stdInLatch.getCount()<1) {
return pipeOut;
}
pipeOut = new PipedOutputStream();
pipeIn = new PipedInputStream(pipeOut, initialSize);
stdInLatch.countDown();
log.debug(Banner.banner("Dropped Latch for STDIN Stream"));
return pipeOut;
}
/**
* Cancels STDIN processing if the nash client reports no STDIN or the STDIN stream is EOFed.
*/
public void cancelStdInStream() {
stdInLatch.countDown();
if(pipeOut!=null) {
log.debug("Closing PipeOut");
try { pipeOut.flush(); } catch (Exception e) {}
try { pipeOut.close(); } catch (Exception e) {}
log.debug("Closed PipeOut");
}
pipeOut = null;
if(pipeIn!=null) try {
log.debug("Closing PipeIn");
pipeIn.close();
log.debug("Closed PipeIn");
} catch (Exception e) {}
pipeIn = null;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#getInputStream(long, java.util.concurrent.TimeUnit)
*/
@Override
public InputStream getInputStream(long timeout, TimeUnit unit) {
if(stdInLatch == null) stdInLatch = new CountDownLatch(1);
sendStartStdInSignal();
try {
stdInLatch.await(timeout, unit);
return pipeIn;
} catch (Exception e) {
return null;
}
}
/**
* 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() {
DownstreamMessageEvent dme = new DownstreamMessageEvent(channel, Channels.future(channel), STREAM_IN_READY, channel.getRemoteAddress());
//channel.getPipeline().sendDownstream(dme);
System.out.println("Sending STDIN READY");
channel.write(STREAM_IN_READY).awaitUninterruptibly();
System.out.println("Sent STDIN READY");
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#out(java.lang.CharSequence)
*/
@Override
public NashRequest out(CharSequence message) {
exitCode = 0;
ChannelBuffer header = ChannelBuffers.buffer(5);
header.writeInt(message.length());
header.writeByte(NashConstants.CHUNKTYPE_STDOUT);
ChannelBuffer response = ChannelBuffers.wrappedBuffer(
header,
ChannelBuffers.copiedBuffer(message, Charset.defaultCharset())
);
channel.getPipeline().sendDownstream(new DownstreamMessageEvent(channel, Channels.future(channel), response, channel.getRemoteAddress()));
return this;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#err(java.lang.CharSequence)
*/
@Override
public NashRequest err(CharSequence message) {
exitCode = 1;
ChannelBuffer header = ChannelBuffers.buffer(5);
header.writeInt(message.length());
header.writeByte(NashConstants.CHUNKTYPE_STDERR);
ChannelBuffer response = ChannelBuffers.wrappedBuffer(
header,
ChannelBuffers.copiedBuffer(message, Charset.defaultCharset())
);
channel.getPipeline().sendDownstream(new DownstreamMessageEvent(channel, Channels.future(channel), response, channel.getRemoteAddress()));
return this;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#end()
*/
@Override
public void end() {
end(exitCode);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.nash.NashRequest#end(int)
*/
@Override
public void end(int exitCode) {
byte[] msg = ("" + exitCode + "\n").getBytes();
ChannelBuffer header = ChannelBuffers.buffer(msg.length + 5);
header.writeInt(msg.length);
header.writeByte(NashConstants.CHUNKTYPE_EXIT);
header.writeBytes(msg);
//channel.getPipeline().sendDownstream(new DownstreamMessageEvent(channel, Channels.future(channel), header, channel.getRemoteAddress()));
channel.write(header).addListener(ChannelFutureListener.CLOSE);
//channel.close();
}
/**
* Constructs a <code>String</code> with all attributes
* in name = value format.
*
* @return a <code>String</code> representation
* of this object.
*/
@Override
public String toString() {
final String TAB = "\n\t";
StringBuilder retValue = new StringBuilder("DefaultNashRequestImpl [")
.append(TAB).append("channel:").append(this.channel)
.append(TAB).append("command:").append(this.command)
.append(TAB).append("workingDirectory:").append(this.workingDirectory)
.append(TAB).append("environment:").append(this.environment.size()).append(" properties")
.append(TAB).append("arguments:").append(this.arguments)
.append(TAB).append("remoteAddress:").append(getRemoteAddress())
.append(TAB).append("remotePort:").append(getRemotePort())
.append("\n]");
return retValue.toString();
}
}