/*
* Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
* Distributed under the terms of either:
* - the common development and distribution license (CDDL), v1.0; or
* - the GNU Lesser General Public License, v2.1 or later
*/
package winstone;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Stack;
import javax.servlet.http.Cookie;
/**
* Matches the socket output stream to the servlet output.
*
* @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
* @version $Id: WinstoneOutputStream.java,v 1.19 2007/10/14 14:48:14 rickknowles Exp $
*/
public class WinstoneOutputStream extends javax.servlet.ServletOutputStream {
private static final int DEFAULT_BUFFER_SIZE = 8192;
private static final byte[] CR_LF = "\r\n".getBytes();
protected OutputStream outStream;
protected int bufferSize;
protected int bufferPosition;
protected int bytesCommitted;
protected ByteArrayOutputStream buffer;
protected boolean committed;
protected boolean bodyOnly;
protected WinstoneResponse owner;
protected boolean disregardMode = false;
protected boolean closed = false;
protected Stack includeByteStreams;
/**
* Constructor
*/
public WinstoneOutputStream(OutputStream out, boolean bodyOnlyForInclude) {
this.outStream = out;
this.bodyOnly = bodyOnlyForInclude;
this.bufferSize = DEFAULT_BUFFER_SIZE;
this.committed = false;
// this.headersWritten = false;
this.buffer = new ByteArrayOutputStream();
}
public void setResponse(WinstoneResponse response) {
this.owner = response;
}
public int getBufferSize() {
return this.bufferSize;
}
public void setBufferSize(int bufferSize) {
if (this.owner.isCommitted()) {
throw new IllegalStateException(Launcher.RESOURCES.getString(
"WinstoneOutputStream.AlreadyCommitted"));
}
this.bufferSize = bufferSize;
}
public boolean isCommitted() {
return this.committed;
}
public int getOutputStreamLength() {
return this.bytesCommitted + this.bufferPosition;
}
public int getBytesCommitted() {
return this.bytesCommitted;
}
public void setDisregardMode(boolean disregard) {
this.disregardMode = disregard;
}
public void setClosed(boolean closed) {
this.closed = closed;
}
public void write(int oneChar) throws IOException {
if (this.disregardMode || this.closed) {
return;
}
String contentLengthHeader = this.owner.getHeader(WinstoneResponse.CONTENT_LENGTH_HEADER);
if ((contentLengthHeader != null) &&
(this.bytesCommitted >= Integer.parseInt(contentLengthHeader))) {
return;
}
// System.out.println("Out: " + this.bufferPosition + " char=" + (char)oneChar);
this.buffer.write(oneChar);
this.bufferPosition++;
// if (this.headersWritten)
if (this.bufferPosition >= this.bufferSize) {
commit();
} else if ((contentLengthHeader != null) &&
((this.bufferPosition + this.bytesCommitted)
>= Integer.parseInt(contentLengthHeader))) {
commit();
}
}
public void commit() throws IOException {
this.buffer.flush();
// If we haven't written the headers yet, write them out
if (!this.committed && !this.bodyOnly) {
this.owner.validateHeaders();
this.committed = true;
Logger.log(Logger.DEBUG, Launcher.RESOURCES, "WinstoneOutputStream.CommittingOutputStream");
int statusCode = this.owner.getStatus();
String reason = Launcher.RESOURCES.getString("WinstoneOutputStream.reasonPhrase." + statusCode);
String statusLine = this.owner.getProtocol() + " " + statusCode + " " +
(reason == null ? "No reason" : reason);
this.outStream.write(statusLine.getBytes("8859_1"));
this.outStream.write(CR_LF);
Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
"WinstoneOutputStream.ResponseStatus", statusLine);
// Write headers and cookies
for (Iterator i = this.owner.getHeaders().iterator(); i.hasNext();) {
String header = (String) i.next();
this.outStream.write(header.getBytes("8859_1"));
this.outStream.write(CR_LF);
Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
"WinstoneOutputStream.Header", header);
}
if (!this.owner.getHeaders().isEmpty()) {
for (Iterator i = this.owner.getCookies().iterator(); i.hasNext();) {
Cookie cookie = (Cookie) i.next();
String cookieText = this.owner.writeCookie(cookie);
this.outStream.write(cookieText.getBytes("8859_1"));
this.outStream.write(CR_LF);
Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
"WinstoneOutputStream.Header", cookieText);
}
}
this.outStream.write(CR_LF);
this.outStream.flush();
// Logger.log(Logger.FULL_DEBUG,
// Launcher.RESOURCES.getString("HttpProtocol.OutHeaders") + out.toString());
}
byte content[] = this.buffer.toByteArray();
// winstone.ajp13.Ajp13Listener.packetDump(content, content.length);
// this.buffer.writeTo(this.outStream);
int commitLength = content.length;
String contentLengthHeader = this.owner.getHeader(WinstoneResponse.CONTENT_LENGTH_HEADER);
if (contentLengthHeader != null) {
commitLength = Math.min(Integer.parseInt(contentLengthHeader)
- this.bytesCommitted, content.length);
}
if (commitLength > 0) {
this.outStream.write(content, 0, commitLength);
}
this.outStream.flush();
Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
"WinstoneOutputStream.CommittedBytes",
"" + (this.bytesCommitted + commitLength));
this.bytesCommitted += commitLength;
this.buffer.reset();
this.bufferPosition = 0;
}
public void reset() {
if (isCommitted())
throw new IllegalStateException(Launcher.RESOURCES
.getString("WinstoneOutputStream.AlreadyCommitted"));
else {
Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
"WinstoneOutputStream.ResetBuffer", this.bufferPosition
+ "");
this.buffer.reset();
this.bufferPosition = 0;
this.bytesCommitted = 0;
}
}
public void finishResponse() throws IOException {
this.outStream.flush();
this.outStream = null;
}
public void flush() throws IOException {
if (this.disregardMode) {
return;
}
Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "WinstoneOutputStream.Flushing");
this.buffer.flush();
this.commit();
}
public void close() throws IOException {
if (!isCommitted() && !this.disregardMode && !this.closed &&
(this.owner.getHeader(WinstoneResponse.CONTENT_LENGTH_HEADER) == null)) {
if ((this.owner != null) && !this.bodyOnly) {
this.owner.setContentLength(getOutputStreamLength());
}
}
flush();
}
// Include related buffering
public boolean isIncluding() {
return (this.includeByteStreams != null && !this.includeByteStreams.isEmpty());
}
public void startIncludeBuffer() {
synchronized (this.buffer) {
if (this.includeByteStreams == null) {
this.includeByteStreams = new Stack();
}
}
this.includeByteStreams.push(new ByteArrayOutputStream());
}
public void finishIncludeBuffer() throws IOException {
if (isIncluding()) {
ByteArrayOutputStream body = (ByteArrayOutputStream) this.includeByteStreams.pop();
OutputStream topStream = this.outStream;
if (!this.includeByteStreams.isEmpty()) {
topStream = (OutputStream) this.includeByteStreams.peek();
}
byte bodyArr[] = body.toByteArray();
if (bodyArr.length > 0) {
topStream.write(bodyArr);
}
body.close();
}
}
public void clearIncludeStackForForward() throws IOException {
if (isIncluding()) {
for (Iterator i = this.includeByteStreams.iterator(); i.hasNext(); ) {
((ByteArrayOutputStream) i.next()).close();
}
this.includeByteStreams.clear();
}
}
}