View Javadoc

1   /*
2    * Copyright 2001-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.atlassian.core.spool;
18  
19  import java.io.*;
20  
21  import org.apache.commons.io.IOUtils;
22  import org.apache.commons.io.output.ThresholdingOutputStream;
23  import org.apache.commons.io.output.ByteArrayOutputStream;
24  
25  
26  /**
27   * <p>An output stream which will retain data in memory until a specified threshold is reached, and only then commit it
28   * to disk. If the stream is closed before the threshold is reached, the data will not be written to disk at all.</p>
29   * <p/>
30   * <p>This class originated in FileUpload processing. In this use case, you do not know in advance the size of the file
31   * being uploaded. If the file is small you want to store it in memory (for speed), but if the file is large you want to
32   * store it to file (to avoid memory issues).</p>
33   *
34   * <p>MODIFICATION: Made this class a bit more extensible and less stupid</p>
35   *
36   * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
37   * @author gaxzerow
38   * @author cowen
39   */
40  public class DeferredFileOutputStream
41          extends ThresholdingOutputStream
42  {
43  
44      // ----------------------------------------------------------- Data members
45  
46  
47      /**
48       * The output stream to which data will be written prior to the theshold being reached.
49       */
50      private ByteArrayOutputStream memoryOutputStream;
51  
52  
53      /**
54       * The output stream to which data will be written at any given time. This will always be one of
55       * <code>memoryOutputStream</code> or <code>diskOutputStream</code>.
56       */
57      private OutputStream currentOutputStream;
58  
59  
60      /**
61       * The file to which output will be directed if the threshold is exceeded.
62       */
63      protected File outputFile;
64  
65  
66      /**
67       * True when close() has been called successfully.
68       */
69      protected boolean closed = false;
70  
71      // ----------------------------------------------------------- Constructors
72  
73  
74      /**
75       * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a
76       * file beyond that point.
77       *
78       * @param threshold  The number of bytes at which to trigger an event.
79       * @param outputFile The file to which data is saved beyond the threshold.
80       */
81      public DeferredFileOutputStream(int threshold, File outputFile)
82      {
83          super(threshold);
84          this.outputFile = outputFile;
85  
86          memoryOutputStream = new ByteArrayOutputStream();
87          currentOutputStream = memoryOutputStream;
88      }
89  
90  
91      // --------------------------------------- ThresholdingOutputStream methods
92  
93  
94      /**
95       * Returns the current output stream. This may be memory based or disk based, depending on the current state with
96       * respect to the threshold.
97       *
98       * @return The underlying output stream.
99       * @throws IOException if an error occurs.
100      */
101     protected OutputStream getStream() throws IOException
102     {
103         return currentOutputStream;
104     }
105 
106 
107     /**
108      * Switches the underlying output stream from a memory based stream to one that is backed by disk. This is the point
109      * at which we realise that too much data is being written to keep in memory, so we elect to switch to disk-based
110      * storage.
111      *
112      * @throws IOException if an error occurs.
113      */
114     protected void thresholdReached() throws IOException
115     {
116         FileOutputStream fos = new FileOutputStream(getFile());
117         memoryOutputStream.writeTo(fos);
118         currentOutputStream = fos;
119         memoryOutputStream = null;
120     }
121 
122 
123     // --------------------------------------------------------- Public methods
124 
125 
126     /**
127      * Determines whether or not the data for this output stream has been retained in memory.
128      *
129      * @return <code>true</code> if the data is available in memory; <code>false</code> otherwise.
130      */
131     public boolean isInMemory()
132     {
133         return (!isThresholdExceeded());
134     }
135 
136 
137     /**
138      * Returns the data for this output stream as an array of bytes, assuming that the data has been retained in memory.
139      * If the data was written to disk, this method returns <code>null</code>.
140      *
141      * @return The data for this output stream, or <code>null</code> if no such data is available.
142      */
143     public byte[] getData()
144     {
145         if (memoryOutputStream != null)
146         {
147             return memoryOutputStream.toByteArray();
148         }
149         return null;
150     }
151 
152 
153     /**
154      * Returns the same output file specified in the constructor, even when threashold has not been reached.
155      *
156      * @return The file for this output stream, or <code>null</code> if no such file exists.
157      */
158     public File getFile()
159     {
160         return outputFile;
161     }
162 
163 
164     /**
165      * Closes underlying output stream, and mark this as closed
166      *
167      * @throws IOException if an error occurs.
168      */
169     public void close() throws IOException
170     {
171         super.close();
172         closed = true;
173     }
174     
175     public InputStream getInputStream() throws IOException
176     {
177         // we may only need to check if this is closed if we are working with a file
178         // but we should force the habit of closing wether we are working with
179         // a file or memory.
180         if (!closed)
181         {
182             throw new IOException("Stream not closed");
183         }
184 
185         if (isInMemory())
186         {
187             return new ByteArrayInputStream(getData());
188         }
189         else
190         {
191             return new FileInputStream(getFile());
192         }
193     }
194 
195     /**
196      * Writes the data from this output stream to the specified output stream, after it has been closed.
197      *
198      * @param out output stream to write to.
199      * @throws IOException if this stream is not yet closed or an error occurs.
200      */
201     public void writeTo(OutputStream out) throws IOException
202     {
203         IOUtils.copy(getInputStream(), out);
204     }
205 }