001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.io.output;
018
019 import java.io.File;
020 import java.io.FileInputStream;
021 import java.io.FileOutputStream;
022 import java.io.IOException;
023 import java.io.OutputStream;
024
025 import org.apache.commons.io.IOUtils;
026
027
028 /**
029 * An output stream which will retain data in memory until a specified
030 * threshold is reached, and only then commit it to disk. If the stream is
031 * closed before the threshold is reached, the data will not be written to
032 * disk at all.
033 * <p>
034 * This class originated in FileUpload processing. In this use case, you do
035 * not know in advance the size of the file being uploaded. If the file is small
036 * you want to store it in memory (for speed), but if the file is large you want
037 * to store it to file (to avoid memory issues).
038 *
039 * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
040 * @author gaxzerow
041 *
042 * @version $Id: DeferredFileOutputStream.java 606381 2007-12-22 02:03:16Z ggregory $
043 */
044 public class DeferredFileOutputStream
045 extends ThresholdingOutputStream
046 {
047
048 // ----------------------------------------------------------- Data members
049
050
051 /**
052 * The output stream to which data will be written prior to the theshold
053 * being reached.
054 */
055 private ByteArrayOutputStream memoryOutputStream;
056
057
058 /**
059 * The output stream to which data will be written at any given time. This
060 * will always be one of <code>memoryOutputStream</code> or
061 * <code>diskOutputStream</code>.
062 */
063 private OutputStream currentOutputStream;
064
065
066 /**
067 * The file to which output will be directed if the threshold is exceeded.
068 */
069 private File outputFile;
070
071 /**
072 * The temporary file prefix.
073 */
074 private String prefix;
075
076 /**
077 * The temporary file suffix.
078 */
079 private String suffix;
080
081 /**
082 * The directory to use for temporary files.
083 */
084 private File directory;
085
086
087 /**
088 * True when close() has been called successfully.
089 */
090 private boolean closed = false;
091
092 // ----------------------------------------------------------- Constructors
093
094
095 /**
096 * Constructs an instance of this class which will trigger an event at the
097 * specified threshold, and save data to a file beyond that point.
098 *
099 * @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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 }