001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.util;
006:
007: import java.io.BufferedOutputStream;
008: import java.io.ByteArrayOutputStream;
009: import java.io.IOException;
010: import java.io.OutputStream;
011: import javax.servlet.http.HttpServletResponse;
012:
013: /**
014: * <b>PartialBufferedOutputStream</b><br> Oct 19, 2005<br>
015: * <b>Purpose:</b><br>
016: * I have modelled this class after the BufferedOutputStream. Several methods
017: * are synchronized as a result. The interior of this class uses a
018: * ByteArrayOutputStream when it starts and then a BufferedOutputStream. This
019: * is essentially a decorator. This class uses a temporary to-memory
020: * OutputStream for initial bufferring. This OutputStream is a
021: * ByteArrayOutputStream. Once a pre-defined BUFFER_SIZE has been reached, the
022: * output that is stored in the ByteArrayOutputStream is output to the real
023: * OutputStream to the response. Further data is then written to that response
024: * OutputStream. For the first run of this, we will write to memory for the
025: * buffered part. Writing to disk is another option. NOTE: If we switch to
026: * writing out to disk, we will have to clean up our temporary files with
027: * abort(). WARNING: IF you abuse the size of the buffer, you may leak memory
028: * if this OutputStream is not terminated. The ByteArrayOutputStream will hold
029: * onto its allocated memory even after you call reset() on it. So if you put
030: * in 60MB of buffer space, it will stay there until the object is no longer
031: * referenced and the garbage collector decides to pick it up. A solution to
032: * this would be to use a FileOutputStream instead of the
033: * ByteArrayOutputStream. CONTRACT: -close() will NOT flush remaining bytes to
034: * the output stream, as per the contract with OutputStream. Close will remove
035: * any references to OutputStreams and its HttpServletResponse. -fush() will
036: * NOT flush if the buffer is not full. -abort() will succeed if the buffer is
037: * not full yet. If the buffer has been filled and the information has been
038: * written out to response's OutputStream, the abort reports as having failed.
039: * The point of this PartialBufferedOutputStream is to allow an abort before
040: * the information has been written out to the real OutputStream. When abort
041: * is called the references to any OutputStreams and the HttpServletResponse
042: * are removed.
043: *
044: * @author Brent Owens (The Open Planning Project)
045: * @version
046: */
047: public class PartialBufferedOutputStream2 extends OutputStream {
048: public static final int DEFAULT_BUFFER_SIZE = 50;
049:
050: /** the number of bytes in a kilobyte */
051: private final int KILOBYTE = 1024;
052:
053: /** Buffer size for the temporary output stream */
054: private int BUFFER_SIZE = KILOBYTE;
055:
056: /** Temporary output stream, the buffered one */
057: private ByteArrayOutputStream out_buffer;
058:
059: /**
060: * Response output stream, the non-buffered one, this is passed in
061: * by the response
062: */
063: private OutputStream out_real;
064: private OutputStream currentStream;
065:
066: /**
067: * This contains the OutputStream that will be put in out_real when
068: * the buffer is full
069: */
070: private HttpServletResponse response;
071:
072: /** Set to true when close() is called to prevent further writing */
073: private boolean closed = false;
074:
075: /**
076: * Constructor Defaults buffer size to 50KB
077: * @param response
078: */
079: public PartialBufferedOutputStream2(HttpServletResponse response)
080: throws IOException {
081: this (response, DEFAULT_BUFFER_SIZE); // default to 50KB
082: }
083:
084: /**
085: * @param response the response with its output stream to write to once the buffer is full
086: * @param kilobytes size, in kilobytes, of the buffer
087: */
088: public PartialBufferedOutputStream2(HttpServletResponse response,
089: int kilobytes) throws IOException {
090: if (kilobytes < 1) {
091: throw new IllegalArgumentException(
092: "Buffer size not greater than 0: " + kilobytes);
093: }
094:
095: BUFFER_SIZE = KILOBYTE * kilobytes;
096: this .response = response;
097: out_buffer = new ByteArrayOutputStream(BUFFER_SIZE);
098: this .response = response;
099: currentStream = out_buffer;
100: }
101:
102: /**
103: * <b>bufferCapacity</b><br><br>
104: * <b>Description:</b><br>
105: * This will return the max capacity of the buffer in kilobytes.
106: *
107: * @return the capacity of the buffer in kilobytes
108: */
109: public int bufferCapacity() {
110: return BUFFER_SIZE / KILOBYTE;
111: }
112:
113: /**
114: * <b>bufferSize</b><br><br>
115: * <b>Description:</b><br>
116: * This will return the size of the buffer, in bytes.
117: *
118: * @return the size of the buffer in bytes
119: */
120: public int bufferSize() {
121: return out_buffer.size();
122: }
123:
124: /* (non-Javadoc)
125: * @see java.io.OutputStream#write(int)
126: *
127: * Remember that b is treated as a byte, the 8 low-order bits are read,
128: * and the 24 remaining high-order bits of b are ignored.
129: */
130: public synchronized void write(int b) throws IOException {
131: if (closed) {
132: return;
133: }
134:
135: checkBuffer(1);
136: currentStream.write(b);
137: }
138:
139: public void write(byte[] b) throws IOException {
140: if (closed) {
141: return;
142: }
143:
144: checkBuffer(b.length);
145: currentStream.write(b);
146: }
147:
148: public void write(byte[] b, int off, int len) throws IOException {
149: if (closed) {
150: return;
151: }
152:
153: checkBuffer(len);
154: currentStream.write(b, off, len);
155: }
156:
157: private void checkBuffer(int extraBytes) throws IOException {
158: if ((currentStream == out_buffer)
159: && ((out_buffer.size() + extraBytes) >= BUFFER_SIZE)) {
160: flushBuffer();
161: }
162: }
163:
164: private void flushBuffer() throws IOException {
165: out_real = response.getOutputStream();
166: out_buffer.writeTo(out_real);
167: out_buffer.reset();
168: currentStream = out_real;
169: }
170:
171: /* (non-Javadoc)
172: * @see java.io.OutputStream#flush()
173: *
174: * If the buffer is not maxed yet, it won't flush. If this is really needed,
175: * call forceFlush
176: */
177: public synchronized void flush() throws IOException {
178: if (closed) {
179: return;
180: }
181:
182: if (currentStream == out_real) {
183: currentStream.flush();
184: }
185: }
186:
187: public synchronized void forceFlush() throws IOException {
188: if (currentStream == out_buffer) {
189: flushBuffer();
190: }
191: flush();
192: }
193:
194: /**
195: * Closes the stream. If we're still working against the in memory buffer that
196: * will be written out before close occurs
197: */
198: public void close() throws IOException {
199: if (closed) {
200: return;
201: }
202:
203: closed = true;
204:
205: out_buffer.close();
206: out_buffer = null;
207: out_real = null; // get rid of our local pointer
208: response = null; // get rid of our local pointer
209: //if (out_real != null) // removed so the user has to close their stream
210: // out_real.close();
211: }
212:
213: /**
214: * <b>abort</b><br><br>
215: * <b>Description:</b><br>
216: * Abort is called when something bad has happened and we want to get out
217: * of writing out to the real stream. This is why we have a buffer. Abort
218: * will succeed if the buffer is not full yet. If that is true, then the
219: * buffer is cleared and closed. It does NOT close the response's
220: * OutputStream
221: *
222: * @return DOCUMENT ME!
223: *
224: * @throws IOException
225: */
226: public boolean abort() throws IOException {
227: if ((out_buffer != null) && (out_buffer.size() < BUFFER_SIZE)) {
228: out_buffer.close();
229: out_buffer = null;
230: out_real = null; // get rid of our local pointer
231: response = null; // get rid of our local pointer
232:
233: return true; // success
234: }
235:
236: out_real = null; // get rid of our local pointer
237: response = null; // get rid of our local pointer
238:
239: return false; // buffer already full, sorry
240: }
241: }
|