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  * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
40  * @author gaxzerow
41  *
42  * @version $Id: DeferredFileOutputStream.java 606381 2007-12-22 02:03:16Z ggregory $
43  */
44 public class DeferredFileOutputStream
45     extends ThresholdingOutputStream
46 {
47 
48     // ----------------------------------------------------------- Data members
49 
50 
51     /**
52      * The output stream to which data will be written prior to the theshold
53      * being reached.
54      */
55     private ByteArrayOutputStream memoryOutputStream;
56 
57 
58     /**
59      * The output stream to which data will be written at any given time. This
60      * will always be one of <code>memoryOutputStream</code> or
61      * <code>diskOutputStream</code>.
62      */
63     private OutputStream currentOutputStream;
64 
65 
66     /**
67      * The file to which output will be directed if the threshold is exceeded.
68      */
69     private File outputFile;
70 
71     /**
72      * The temporary file prefix.
73      */
74     private String prefix;
75 
76     /**
77      * The temporary file suffix.
78      */
79     private String suffix;
80 
81     /**
82      * The directory to use for temporary files.
83      */
84     private File directory;
85 
86 
87     /**
88      * True when close() has been called successfully.
89      */
90     private boolean closed = false;
91 
92     // ----------------------------------------------------------- Constructors
93 
94 
95     /**
96      * Constructs an instance of this class which will trigger an event at the
97      * specified threshold, and save data to a file beyond that point.
98      *
99      * @param threshold  The number of bytes at which to trigger an event.
100      * @param outputFile The file to which data is saved beyond the threshold.
101      */
DeferredFileOutputStream(int threshold, File outputFile)102     public DeferredFileOutputStream(int threshold, File outputFile)
103     {
104         super(threshold);
105         this.outputFile = outputFile;
106 
107         memoryOutputStream = new ByteArrayOutputStream();
108         currentOutputStream = memoryOutputStream;
109     }
110 
111 
112     /**
113      * Constructs an instance of this class which will trigger an event at the
114      * specified threshold, and save data to a temporary file beyond that point.
115      *
116      * @param threshold  The number of bytes at which to trigger an event.
117      * @param prefix Prefix to use for the temporary file.
118      * @param suffix Suffix to use for the temporary file.
119      * @param directory Temporary file directory.
120      *
121      * @since Commons IO 1.4
122      */
DeferredFileOutputStream(int threshold, String prefix, String suffix, File directory)123     public DeferredFileOutputStream(int threshold, String prefix, String suffix, File directory)
124     {
125         this(threshold, (File)null);
126         if (prefix == null) {
127             throw new IllegalArgumentException("Temporary file prefix is missing");
128         }
129         this.prefix = prefix;
130         this.suffix = suffix;
131         this.directory = directory;
132     }
133 
134 
135     // --------------------------------------- ThresholdingOutputStream methods
136 
137 
138     /**
139      * Returns the current output stream. This may be memory based or disk
140      * based, depending on the current state with respect to the threshold.
141      *
142      * @return The underlying output stream.
143      *
144      * @exception IOException if an error occurs.
145      */
getStream()146     protected OutputStream getStream() throws IOException
147     {
148         return currentOutputStream;
149     }
150 
151 
152     /**
153      * Switches the underlying output stream from a memory based stream to one
154      * that is backed by disk. This is the point at which we realise that too
155      * much data is being written to keep in memory, so we elect to switch to
156      * disk-based storage.
157      *
158      * @exception IOException if an error occurs.
159      */
thresholdReached()160     protected void thresholdReached() throws IOException
161     {
162         if (prefix != null) {
163             outputFile = File.createTempFile(prefix, suffix, directory);
164         }
165         FileOutputStream fos = new FileOutputStream(outputFile);
166         memoryOutputStream.writeTo(fos);
167         currentOutputStream = fos;
168         memoryOutputStream = null;
169     }
170 
171 
172     // --------------------------------------------------------- Public methods
173 
174 
175     /**
176      * Determines whether or not the data for this output stream has been
177      * retained in memory.
178      *
179      * @return <code>true</code> if the data is available in memory;
180      *         <code>false</code> otherwise.
181      */
isInMemory()182     public boolean isInMemory()
183     {
184         return (!isThresholdExceeded());
185     }
186 
187 
188     /**
189      * Returns the data for this output stream as an array of bytes, assuming
190      * that the data has been retained in memory. If the data was written to
191      * disk, this method returns <code>null</code>.
192      *
193      * @return The data for this output stream, or <code>null</code> if no such
194      *         data is available.
195      */
getData()196     public byte[] getData()
197     {
198         if (memoryOutputStream != null)
199         {
200             return memoryOutputStream.toByteArray();
201         }
202         return null;
203     }
204 
205 
206     /**
207      * Returns either the output file specified in the constructor or
208      * the temporary file created or null.
209      * <p>
210      * If the constructor specifying the file is used then it returns that
211      * same output file, even when threashold has not been reached.
212      * <p>
213      * If constructor specifying a temporary file prefix/suffix is used
214      * then the temporary file created once the threashold is reached is returned
215      * If the threshold was not reached then <code>null</code> is returned.
216      *
217      * @return The file for this output stream, or <code>null</code> if no such
218      *         file exists.
219      */
getFile()220     public File getFile()
221     {
222         return outputFile;
223     }
224 
225 
226     /**
227      * Closes underlying output stream, and mark this as closed
228      *
229      * @exception IOException if an error occurs.
230      */
close()231     public void close() throws IOException
232     {
233         super.close();
234         closed = true;
235     }
236 
237 
238     /**
239      * Writes the data from this output stream to the specified output stream,
240      * after it has been closed.
241      *
242      * @param out output stream to write to.
243      * @exception IOException if this stream is not yet closed or an error occurs.
244      */
writeTo(OutputStream out)245     public void writeTo(OutputStream out) throws IOException
246     {
247         // we may only need to check if this is closed if we are working with a file
248         // but we should force the habit of closing wether we are working with
249         // a file or memory.
250         if (!closed)
251         {
252             throw new IOException("Stream not closed");
253         }
254 
255         if(isInMemory())
256         {
257             memoryOutputStream.writeTo(out);
258         }
259         else
260         {
261             FileInputStream fis = new FileInputStream(outputFile);
262             try {
263                 IOUtils.copy(fis, out);
264             } finally {
265                 IOUtils.closeQuietly(fis);
266             }
267         }
268     }
269 }
270