001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone;
008:
009: import java.io.ByteArrayOutputStream;
010: import java.io.IOException;
011: import java.io.OutputStream;
012: import java.util.Iterator;
013: import java.util.Stack;
014:
015: import javax.servlet.http.Cookie;
016:
017: /**
018: * Matches the socket output stream to the servlet output.
019: *
020: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
021: * @version $Id: WinstoneOutputStream.java,v 1.19 2007/10/14 14:48:14 rickknowles Exp $
022: */
023: public class WinstoneOutputStream extends
024: javax.servlet.ServletOutputStream {
025: private static final int DEFAULT_BUFFER_SIZE = 8192;
026: private static final byte[] CR_LF = "\r\n".getBytes();
027: protected OutputStream outStream;
028: protected int bufferSize;
029: protected int bufferPosition;
030: protected int bytesCommitted;
031: protected ByteArrayOutputStream buffer;
032: protected boolean committed;
033: protected boolean bodyOnly;
034: protected WinstoneResponse owner;
035: protected boolean disregardMode = false;
036: protected boolean closed = false;
037: protected Stack includeByteStreams;
038:
039: /**
040: * Constructor
041: */
042: public WinstoneOutputStream(OutputStream out,
043: boolean bodyOnlyForInclude) {
044: this .outStream = out;
045: this .bodyOnly = bodyOnlyForInclude;
046: this .bufferSize = DEFAULT_BUFFER_SIZE;
047: this .committed = false;
048: // this.headersWritten = false;
049: this .buffer = new ByteArrayOutputStream();
050: }
051:
052: public void setResponse(WinstoneResponse response) {
053: this .owner = response;
054: }
055:
056: public int getBufferSize() {
057: return this .bufferSize;
058: }
059:
060: public void setBufferSize(int bufferSize) {
061: if (this .owner.isCommitted()) {
062: throw new IllegalStateException(Launcher.RESOURCES
063: .getString("WinstoneOutputStream.AlreadyCommitted"));
064: }
065: this .bufferSize = bufferSize;
066: }
067:
068: public boolean isCommitted() {
069: return this .committed;
070: }
071:
072: public int getOutputStreamLength() {
073: return this .bytesCommitted + this .bufferPosition;
074: }
075:
076: public int getBytesCommitted() {
077: return this .bytesCommitted;
078: }
079:
080: public void setDisregardMode(boolean disregard) {
081: this .disregardMode = disregard;
082: }
083:
084: public void setClosed(boolean closed) {
085: this .closed = closed;
086: }
087:
088: public void write(int oneChar) throws IOException {
089: if (this .disregardMode || this .closed) {
090: return;
091: }
092: String contentLengthHeader = this .owner
093: .getHeader(WinstoneResponse.CONTENT_LENGTH_HEADER);
094: if ((contentLengthHeader != null)
095: && (this .bytesCommitted >= Integer
096: .parseInt(contentLengthHeader))) {
097: return;
098: }
099: // System.out.println("Out: " + this.bufferPosition + " char=" + (char)oneChar);
100: this .buffer.write(oneChar);
101: this .bufferPosition++;
102: // if (this.headersWritten)
103: if (this .bufferPosition >= this .bufferSize) {
104: commit();
105: } else if ((contentLengthHeader != null)
106: && ((this .bufferPosition + this .bytesCommitted) >= Integer
107: .parseInt(contentLengthHeader))) {
108: commit();
109: }
110: }
111:
112: public void commit() throws IOException {
113: this .buffer.flush();
114:
115: // If we haven't written the headers yet, write them out
116: if (!this .committed && !this .bodyOnly) {
117: this .owner.validateHeaders();
118: this .committed = true;
119:
120: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
121: "WinstoneOutputStream.CommittingOutputStream");
122:
123: int statusCode = this .owner.getStatus();
124: String reason = Launcher.RESOURCES
125: .getString("WinstoneOutputStream.reasonPhrase."
126: + statusCode);
127: String statusLine = this .owner.getProtocol() + " "
128: + statusCode + " "
129: + (reason == null ? "No reason" : reason);
130: this .outStream.write(statusLine.getBytes("8859_1"));
131: this .outStream.write(CR_LF);
132: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
133: "WinstoneOutputStream.ResponseStatus", statusLine);
134:
135: // Write headers and cookies
136: for (Iterator i = this .owner.getHeaders().iterator(); i
137: .hasNext();) {
138: String header = (String) i.next();
139: this .outStream.write(header.getBytes("8859_1"));
140: this .outStream.write(CR_LF);
141: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
142: "WinstoneOutputStream.Header", header);
143: }
144:
145: if (!this .owner.getHeaders().isEmpty()) {
146: for (Iterator i = this .owner.getCookies().iterator(); i
147: .hasNext();) {
148: Cookie cookie = (Cookie) i.next();
149: String cookieText = this .owner.writeCookie(cookie);
150: this .outStream.write(cookieText.getBytes("8859_1"));
151: this .outStream.write(CR_LF);
152: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
153: "WinstoneOutputStream.Header", cookieText);
154: }
155: }
156: this .outStream.write(CR_LF);
157: this .outStream.flush();
158: // Logger.log(Logger.FULL_DEBUG,
159: // Launcher.RESOURCES.getString("HttpProtocol.OutHeaders") + out.toString());
160: }
161: byte content[] = this .buffer.toByteArray();
162: // winstone.ajp13.Ajp13Listener.packetDump(content, content.length);
163: // this.buffer.writeTo(this.outStream);
164: int commitLength = content.length;
165: String contentLengthHeader = this .owner
166: .getHeader(WinstoneResponse.CONTENT_LENGTH_HEADER);
167: if (contentLengthHeader != null) {
168: commitLength = Math.min(Integer
169: .parseInt(contentLengthHeader)
170: - this .bytesCommitted, content.length);
171: }
172: if (commitLength > 0) {
173: this .outStream.write(content, 0, commitLength);
174: }
175: this .outStream.flush();
176:
177: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
178: "WinstoneOutputStream.CommittedBytes", ""
179: + (this .bytesCommitted + commitLength));
180:
181: this .bytesCommitted += commitLength;
182: this .buffer.reset();
183: this .bufferPosition = 0;
184: }
185:
186: public void reset() {
187: if (isCommitted())
188: throw new IllegalStateException(Launcher.RESOURCES
189: .getString("WinstoneOutputStream.AlreadyCommitted"));
190: else {
191: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
192: "WinstoneOutputStream.ResetBuffer",
193: this .bufferPosition + "");
194: this .buffer.reset();
195: this .bufferPosition = 0;
196: this .bytesCommitted = 0;
197: }
198: }
199:
200: public void finishResponse() throws IOException {
201: this .outStream.flush();
202: this .outStream = null;
203: }
204:
205: public void flush() throws IOException {
206: if (this .disregardMode) {
207: return;
208: }
209: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
210: "WinstoneOutputStream.Flushing");
211: this .buffer.flush();
212: this .commit();
213: }
214:
215: public void close() throws IOException {
216: if (!isCommitted()
217: && !this .disregardMode
218: && !this .closed
219: && (this .owner
220: .getHeader(WinstoneResponse.CONTENT_LENGTH_HEADER) == null)) {
221: if ((this .owner != null) && !this .bodyOnly) {
222: this .owner.setContentLength(getOutputStreamLength());
223: }
224: }
225: flush();
226: }
227:
228: // Include related buffering
229: public boolean isIncluding() {
230: return (this .includeByteStreams != null && !this .includeByteStreams
231: .isEmpty());
232: }
233:
234: public void startIncludeBuffer() {
235: synchronized (this .buffer) {
236: if (this .includeByteStreams == null) {
237: this .includeByteStreams = new Stack();
238: }
239: }
240: this .includeByteStreams.push(new ByteArrayOutputStream());
241: }
242:
243: public void finishIncludeBuffer() throws IOException {
244: if (isIncluding()) {
245: ByteArrayOutputStream body = (ByteArrayOutputStream) this .includeByteStreams
246: .pop();
247: OutputStream topStream = this .outStream;
248: if (!this .includeByteStreams.isEmpty()) {
249: topStream = (OutputStream) this .includeByteStreams
250: .peek();
251: }
252: byte bodyArr[] = body.toByteArray();
253: if (bodyArr.length > 0) {
254: topStream.write(bodyArr);
255: }
256: body.close();
257: }
258: }
259:
260: public void clearIncludeStackForForward() throws IOException {
261: if (isIncluding()) {
262: for (Iterator i = this .includeByteStreams.iterator(); i
263: .hasNext();) {
264: ((ByteArrayOutputStream) i.next()).close();
265: }
266: this.includeByteStreams.clear();
267: }
268: }
269: }
|