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>&copy; 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    }