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 1686747 2015-06-21 18:44:49Z krosenvold $
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,
135                                      final String suffix, final File directory) {
136         super(threshold);
137         this.outputFile = outputFile;
138 
139         memoryOutputStream = new ByteArrayOutputStream();
140         currentOutputStream = memoryOutputStream;
141         this.prefix = prefix;
142         this.suffix = suffix;
143         this.directory = directory;
144     }
145 
146 
147     // --------------------------------------- ThresholdingOutputStream methods
148 
149 
150     /**
151      * Returns the current output stream. This may be memory based or disk
152      * based, depending on the current state with respect to the threshold.
153      *
154      * @return The underlying output stream.
155      *
156      * @exception IOException if an error occurs.
157      */
158     @Override
159     protected OutputStream getStream() throws IOException
160     {
161         return currentOutputStream;
162     }
163 
164 
165     /**
166      * Switches the underlying output stream from a memory based stream to one
167      * that is backed by disk. This is the point at which we realise that too
168      * much data is being written to keep in memory, so we elect to switch to
169      * disk-based storage.
170      *
171      * @exception IOException if an error occurs.
172      */
173     @Override
174     protected void thresholdReached() throws IOException
175     {
176         if (prefix != null) {
177             outputFile = File.createTempFile(prefix, suffix, directory);
178         }
179         final FileOutputStream fos = new FileOutputStream(outputFile);
180         try {
181             memoryOutputStream.writeTo(fos);
182         } catch (IOException e){
183             fos.close();
184             throw e;
185         }
186         currentOutputStream = fos;
187         memoryOutputStream = null;
188     }
189 
190 
191     // --------------------------------------------------------- Public methods
192 
193 
194     /**
195      * Determines whether or not the data for this output stream has been
196      * retained in memory.
197      *
198      * @return {@code true} if the data is available in memory;
199      *         {@code false} otherwise.
200      */
201     public boolean isInMemory()
202     {
203         return !isThresholdExceeded();
204     }
205 
206 
207     /**
208      * Returns the data for this output stream as an array of bytes, assuming
209      * that the data has been retained in memory. If the data was written to
210      * disk, this method returns {@code null}.
211      *
212      * @return The data for this output stream, or {@code null} if no such
213      *         data is available.
214      */
215     public byte[] getData()
216     {
217         if (memoryOutputStream != null)
218         {
219             return memoryOutputStream.toByteArray();
220         }
221         return null;
222     }
223 
224 
225     /**
226      * Returns either the output file specified in the constructor or
227      * the temporary file created or null.
228      * <p>
229      * If the constructor specifying the file is used then it returns that
230      * same output file, even when threshold has not been reached.
231      * <p>
232      * If constructor specifying a temporary file prefix/suffix is used
233      * then the temporary file created once the threshold is reached is returned
234      * If the threshold was not reached then {@code null} is returned.
235      *
236      * @return The file for this output stream, or {@code null} if no such
237      *         file exists.
238      */
239     public File getFile()
240     {
241         return outputFile;
242     }
243 
244 
245     /**
246      * Closes underlying output stream, and mark this as closed
247      *
248      * @exception IOException if an error occurs.
249      */
250     @Override
251     public void close() throws IOException
252     {
253         super.close();
254         closed = true;
255     }
256 
257 
258     /**
259      * Writes the data from this output stream to the specified output stream,
260      * after it has been closed.
261      *
262      * @param out output stream to write to.
263      * @exception IOException if this stream is not yet closed or an error occurs.
264      */
265     public void writeTo(final OutputStream out) throws IOException
266     {
267         // we may only need to check if this is closed if we are working with a file
268         // but we should force the habit of closing wether we are working with
269         // a file or memory.
270         if (!closed)
271         {
272             throw new IOException("Stream not closed");
273         }
274 
275         if(isInMemory())
276         {
277             memoryOutputStream.writeTo(out);
278         }
279         else
280         {
281             final FileInputStream fis = new FileInputStream(outputFile);
282             try {
283                 IOUtils.copy(fis, out);
284             } finally {
285                 IOUtils.closeQuietly(fis);
286             }
287         }
288     }
289 }