package echosign.api.proxy;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import echosign.api.proxy.SoapProxy.Direction;
/**
* A CopyStream is a runnable that copies an input stream to an output stream and a logging
* stream.
*/
public class CopyStream implements Runnable
{
private Direction direction;
private String message;
private PrintStream logPrintStream;
private BufferedHttpStreamReader inputReader;
private OutputStream outputStream;
private boolean prettyPrint;
private DateFormat printableDateFormat;
public CopyStream(Direction direction, String serverHost, InputStream inputStream, OutputStream outputStream, PrintStream logPrintStream, boolean prettyPrint) throws UnsupportedEncodingException
{
this.direction = direction;
this.message = (direction == Direction.CLIENT2SERVER) ? SoapProxy.FROM_CLIENT_TO_SERVER : SoapProxy.FROM_SERVER_TO_CLIENT;
this.inputReader = new BufferedHttpStreamReader(serverHost, inputStream);
this.outputStream = outputStream;
this.logPrintStream = logPrintStream;
this.prettyPrint = prettyPrint;
this.printableDateFormat = new SimpleDateFormat(SoapProxy.PRINTABLE_DATE_FORMAT);
}
/**
* Read data from an input stream and write it to an output stream and a logging stream until
* any reason to stop reading occurs.
*/
public void run()
{
try
{
while (true)
{
if (!readDataFromInput())
return;
writeDataToOutput();
}
}
catch (Exception e)
{
e.printStackTrace();
}
SoapProxy.stopStreaming();
}
private boolean readDataFromInput() throws IOException
{
inputReader.reset();
while (true)
{
if (!readData())
return false; // stop streaming
// return if reading from the server or the request content
if (direction == Direction.SERVER2CLIENT || inputReader.isReadingContent())
return true;
// return if the blank line is found
if (inputReader.hasBlankLine())
{
// start tracking the content length
inputReader.trackContentLength();
// coerce the Host header to have the EchoSign domain name
inputReader.coerceHostHeader();
return true;
}
}
}
/**
* Monitoring the two global states, straming and requesting, and making a decision to stop
* streaming is centralized in this method.
*/
private boolean readData() throws IOException
{
do
{
try
{
if (!SoapProxy.isStreaming())
return false;
inputReader.read();
if (inputReader.eof())
{
SoapProxy.stopStreaming();
return false;
}
}
catch (SocketTimeoutException e)
{
// continue reading unless the client says no more requests
if (!SoapProxy.isRequesting())
{
SoapProxy.stopStreaming();
return false;
}
}
catch (SocketException e)
{
e.printStackTrace();
SoapProxy.stopStreaming();
return false;
}
}
while (inputReader.empty());
return true;
}
private void writeDataToOutput() throws IOException, JDOMException
{
// print data
System.out.println(">> " + printableDateFormat.format(new Date()) + " " + message);
String data = inputReader.getBufferString();
if (prettyPrint)
prettyPrint(data);
else
println(data);
// write data to the output stream
if (outputStream != null)
{
byte[] bytes = inputReader.toByteArray();
outputStream.write(bytes, 0, bytes.length);
}
}
/**
* Prints to both system.out and to the log if we are logging.
*/
private void print(String s)
{
StringBuilder sb = new StringBuilder();
for (int i = 0 ; i < s.length(); i++)
{
char c = s.charAt(i);
if (c != '\n' && c != '\r' && c != '\t' && (c < 32 || c > 126))
{
sb.append("\\" + (int) c);
}
else
{
sb.append(c);
}
}
System.out.print(sb);
if (logPrintStream != null)
logPrintStream.print(s);
}
private void println(String s)
{
print(s);
System.out.println();
}
/**
* Pretty-prints the soap stuff using jdom.
*/
private void prettyPrint(String string) throws JDOMException, IOException
{
int start = string.indexOf(SoapProxy.SOAP_ENVELOPE_START);
int end = string.indexOf(SoapProxy.SOAP_ENVELOPE_END, start);
if (start >= 0 && end >= 0)
{
end += SoapProxy.SOAP_ENVELOPE_END.length();
if (start != 0)
print(string.substring(0, start));
StringReader stringReader = new StringReader(string.substring(start, end));
SAXBuilder saxBuilder = new SAXBuilder();
Document document = saxBuilder.build(stringReader);
XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat());
CharArrayWriter charArrayWriter = new CharArrayWriter();
xmlOutputter.output(document, charArrayWriter);
String result = charArrayWriter.toString();
// the XMLOutputter adds an additional first line saying
// <?xml version="1.0" encoding="UTF-8"?>
// as this line is not in the input, we don't want it, so we remove it
result = result.substring(result.indexOf("\n") + 1);
println(result);
if (end != string.length())
println(string.substring(end));
}
else
{
println(string);
}
}
}