View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.output;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  
25  import org.apache.commons.io.IOUtils;
26  
27  
28  /**
29   * An output stream which will retain data in memory until a specified
30   * threshold is reached, and only then commit it to disk. If the stream is
31   * closed before the threshold is reached, the data will not be written to
32   * disk at all.
33   * <p>
34   * This class originated in FileUpload processing. In this use case, you do
35   * not know in advance the size of the file being uploaded. If the file is small
36   * you want to store it in memory (for speed), but if the file is large you want
37   * to store it to file (to avoid memory issues).
38   *
39   * @version $Id: DeferredFileOutputStream.java 1471767 2013-04-24 23:24:19Z sebb $
40   */
41  public class DeferredFileOutputStream
42      extends ThresholdingOutputStream
43  {
44  
45      // ----------------------------------------------------------- Data members
46  
47  
48      /**
49       * The output stream to which data will be written prior to the theshold
50       * being reached.
51       */
52      private ByteArrayOutputStream memoryOutputStream;
53  
54  
55      /**
56       * The output stream to which data will be written at any given time. This
57       * will always be one of <code>memoryOutputStream</code> or
58       * <code>diskOutputStream</code>.
59       */
60      private OutputStream currentOutputStream;
61  
62  
63      /**
64       * The file to which output will be directed if the threshold is exceeded.
65       */
66      private File outputFile;
67  
68      /**
69       * The temporary file prefix.
70       */
71      private final String prefix;
72  
73      /**
74       * The temporary file suffix.
75       */
76      private final String suffix;
77  
78      /**
79       * The directory to use for temporary files.
80       */
81      private final File directory;
82  
83  
84      /**
85       * True when close() has been called successfully.
86       */
87      private boolean closed = false;
88  
89      // ----------------------------------------------------------- Constructors
90  
91  
92      /**
93       * Constructs an instance of this class which will trigger an event at the
94       * specified threshold, and save data to a file beyond that point.
95       *
96       * @param threshold  The number of bytes at which to trigger an event.
97       * @param outputFile The file to which data is saved beyond the threshold.
98       */
99      public DeferredFileOutputStream(final int threshold, final File outputFile)
100     {
101         this(threshold,  outputFile, null, null, null);
102     }
103 
104 
105     /**
106      * Constructs an instance of this class which will trigger an event at the
107      * specified threshold, and save data to a temporary file beyond that point.
108      *
109      * @param threshold  The number of bytes at which to trigger an event.
110      * @param prefix Prefix to use for the temporary file.
111      * @param suffix Suffix to use for the temporary file.
112      * @param directory Temporary file directory.
113      *
114      * @since 1.4
115      */
116     public DeferredFileOutputStream(final int threshold, final String prefix, final String suffix, final File directory)
117     {
118         this(threshold, null, prefix, suffix, directory);
119         if (prefix == null) {
120             throw new IllegalArgumentException("Temporary file prefix is missing");
121         }
122     }
123 
124     /**
125      * Constructs an instance of this class which will trigger an event at the
126      * specified threshold, and save data either to a file beyond that point.
127      *
128      * @param threshold  The number of bytes at which to trigger an event.
129      * @param outputFile The file to which data is saved beyond the threshold.
130      * @param prefix Prefix to use for the temporary file.
131      * @param suffix Suffix to use for the temporary file.
132      * @param directory Temporary file directory.
133      */
134     private DeferredFileOutputStream(final int threshold, final File outputFile, final String prefix, final String suffix, final File directory) {
135         super(threshold);
136         this.outputFile = outputFile;
137 
138         memoryOutputStream = new ByteArrayOutputStream();
139         currentOutputStream = memoryOutputStream;
140         this.prefix = prefix;
141         this.suffix = suffix;
142         this.directory = directory;
143     }
144 
145 
146     // --------------------------------------- ThresholdingOutputStream methods
147 
148 
149     /**
150      * Returns the current output stream. This may be memory based or disk
151      * based, depending on the current state with respect to the threshold.
152      *
153      * @return The underlying output stream.
154      *
155      * @exception IOException if an error occurs.
156      */
157     @Override
158     protected OutputStream getStream() throws IOException
159     {
160         return currentOutputStream;
161     }
162 
163 
164     /**
165      * Switches the underlying output stream from a memory based stream to one
166      * that is backed by disk. This is the point at which we realise that too
167      * much data is being written to keep in memory, so we elect to switch to
168      * disk-based storage.
169      *
170      * @exception IOException if an error occurs.
171      */
172     @Override
173     protected void thresholdReached() throws IOException
174     {
175         if (prefix != null) {
176             outputFile = File.createTempFile(prefix, suffix, directory);
177         }
178         final FileOutputStream fos = new FileOutputStream(outputFile);
179         memoryOutputStream.writeTo(fos);
180         currentOutputStream = fos;
181         memoryOutputStream = null;
182     }
183 
184 
185     // --------------------------------------------------------- Public methods
186 
187 
188     /**
189      * Determines whether or not the data for this output stream has been
190      * retained in memory.
191      *
192      * @return {@code true} if the data is available in memory;
193      *         {@code false} otherwise.
194      */
195     public boolean isInMemory()
196     {
197         return !isThresholdExceeded();
198     }
199 
200 
201     /**
202      * Returns the data for this output stream as an array of bytes, assuming
203      * that the data has been retained in memory. If the data was written to
204      * disk, this method returns {@code null}.
205      *
206      * @return The data for this output stream, or {@code null} if no such
207      *         data is available.
208      */
209     public byte[] getData()
210     {
211         if (memoryOutputStream != null)
212         {
213             return memoryOutputStream.toByteArray();
214         }
215         return null;
216     }
217 
218 
219     /**
220      * Returns either the output file specified in the constructor or
221      * the temporary file created or null.
222      * <p>
223      * If the constructor specifying the file is used then it returns that
224      * same output file, even when threshold has not been reached.
225      * <p>
226      * If constructor specifying a temporary file prefix/suffix is used
227      * then the temporary file created once the threshold is reached is returned
228      * If the threshold was not reached then {@code null} is returned.
229      *
230      * @return The file for this output stream, or {@code null} if no such
231      *         file exists.
232      */
233     public File getFile()
234     {
235         return outputFile;
236     }
237 
238 
239     /**
240      * Closes underlying output stream, and mark this as closed
241      *
242      * @exception IOException if an error occurs.
243      */
244     @Override
245     public void close() throws IOException
246     {
247         super.close();
248         closed = true;
249     }
250 
251 
252     /**
253      * Writes the data from this output stream to the specified output stream,
254      * after it has been closed.
255      *
256      * @param out output stream to write to.
257      * @exception IOException if this stream is not yet closed or an error occurs.
258      */
259     public void writeTo(final OutputStream out) throws IOException
260     {
261         // we may only need to check if this is closed if we are working with a file
262         // but we should force the habit of closing wether we are working with
263         // a file or memory.
264         if (!closed)
265         {
266             throw new IOException("Stream not closed");
267         }
268 
269         if(isInMemory())
270         {
271             memoryOutputStream.writeTo(out);
272         }
273         else
274         {
275             final FileInputStream fis = new FileInputStream(outputFile);
276             try {
277                 IOUtils.copy(fis, out);
278             } finally {
279                 IOUtils.closeQuietly(fis);
280             }
281         }
282     }
283 }