001 package org.rakeshv.filters;
002
003 import java.io.BufferedOutputStream;
004 import java.io.File;
005 import java.io.FileOutputStream;
006 import java.io.IOException;
007 import java.io.OutputStream;
008 import javax.servlet.ServletOutputStream;
009 import javax.servlet.http.HttpServletResponse;
010
011 import org.rakeshv.io.TeeOutputStream;
012
013 /**
014 * A sub-class of <code>ServletOutputStream</code> that is used to
015 * cache the response sent by the server to a client request. This
016 * stream writes every byte that is send to the client to the cache
017 * file also.
018 *
019 * <p>© Copyright 2005, Rakesh Vidyadharan</p>
020 * @author Rakesh Vidyadharan 15<sup><small>th</small></sup> September 2005
021 *
022 * @version $Id: CachingResponseStream.java,v 1.6 2005/10/15 01:42:32 rakesh Exp $
023 */
024 public class CachingResponseStream extends ServletOutputStream
025 {
026 /**
027 * Has this stream been closed?
028 */
029 protected boolean closed = false;
030
031 /**
032 * The response with which this servlet output stream is associated.
033 */
034 protected HttpServletResponse response = null;
035
036 /**
037 * The underlying servlet output stream to which we should write data.
038 */
039 protected ServletOutputStream output = null;
040
041 /**
042 * The file to which the responses are to be cached.
043 */
044 protected BufferedOutputStream cacheFile;
045
046 /**
047 * The <code>tee</code> that represents the {@link #output} and
048 * {@link #cacheFile}.
049 */
050 private TeeOutputStream tee;
051
052 /**
053 * Initialise a new instance of this class with the specified
054 * servlet response. Initialises the {@link #tee}.
055 *
056 * @param response The response to which the contents are to be
057 * sent.
058 * @param file The file to which the response is to be cached.
059 * @throws IOException If errors are encountered while writing
060 * to the streams.
061 */
062 public CachingResponseStream( HttpServletResponse response,
063 File file ) throws IOException
064 {
065 super();
066 this.response = response;
067 this.output = response.getOutputStream();
068
069 cacheFile =
070 new BufferedOutputStream( new FileOutputStream( file ) );
071 tee = new TeeOutputStream( new OutputStream[]{ output, cacheFile } );
072 }
073
074 /**
075 * Over-ridden form of the method. Writes the specified int to the
076 * {@link #output} and {@link #cacheFile} if the stream has not been
077 * closed.
078 *
079 * @param value - The integer value that is being written.
080 * @throws IOException - If the stream has already been closed.
081 */
082 public void write( int value ) throws IOException
083 {
084 if ( closed )
085 {
086 throw new IOException( "Cannot write to a closed output stream" );
087 }
088
089 tee.write( value );
090 }
091
092 /**
093 * Over-ridden to ensure that the method just invokes the {@link
094 * #write( byte[], int, int )} method.
095 *
096 * @param buf - The byte buffer from which the data is to
097 * read and written to the {@link #output} and {@link #cacheFile}.
098 * @throws IOException - If errors are encountered while writing
099 * to the stream.
100 */
101 public void write( byte[] buf ) throws IOException
102 {
103 write( buf, 0, buf.length );
104 }
105
106 /**
107 * Over-ridden form of the method. Writes the <code>byte array</code>
108 * to the {@link #output} and {@link #cacheFile}.
109 *
110 * @param buf - The byte buffer from which the data is to
111 * read and written to the {@link #output} and {@link #cacheFile}.
112 * @param off - The offset from the start of the buffer from
113 * which to start reading data.
114 * @param len - The length of data that is to be read from the
115 * array.
116 * @throws IOException - If errors are encountered while writing
117 * to the stream.
118 */
119 public void write( byte[] buf, int off, int len ) throws IOException
120 {
121 if ( closed )
122 {
123 throw new IOException( "Cannot write to a closed output stream" );
124 }
125
126 tee.write( buf, off, len );
127 }
128
129 /**
130 * Close this output stream, causing any buffered data to be flushed
131 * and any further output data to throw an IOException.
132 *
133 * @throws IOException - If the stream has already been closed.
134 */
135 public void close() throws IOException
136 {
137 if ( closed )
138 {
139 throw new IOException("This output stream has already been closed");
140 }
141
142 flush();
143
144 tee.close();
145 closed = true;
146 }
147
148 /**
149 * Flushes the underlying servlet output stream. Flushes any
150 * unwritten content in the buffer to the servlet output
151 * stream.
152 *
153 * @throws IOException - If the stream has already been closed.
154 */
155 public void flush() throws IOException
156 {
157 if ( closed )
158 {
159 throw new IOException( "Cannot flush a closed output stream" );
160 }
161
162 tee.flush();
163 }
164
165 /**
166 * Closes the {@link #cacheFile} without closing the {@link #output}.
167 *
168 * <p><b>Note:</b> This method must be invoked by to ensure that the
169 * cache file that was generated is properly closed after the actual
170 * response was generated.</p>
171 *
172 * @throws IOException - If errors are encountered while closing
173 * the file.
174 */
175 public void closeCacheFile() throws IOException
176 {
177 cacheFile.close();
178 }
179
180 /**
181 * Returns {@link #closed}.
182 *
183 * @return boolean - The value/reference of/to closed.
184 */
185 public final boolean getClosed()
186 {
187 return closed;
188 }
189 }