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 */
017package org.apache.commons.io.output;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.FileOutputStream;
022import java.io.IOException;
023import java.io.OutputStream;
024
025import 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 * @version $Id: DeferredFileOutputStream.java 1686747 2015-06-21 18:44:49Z krosenvold $
040 */
041public class DeferredFileOutputStream
042    extends ThresholdingOutputStream
043{
044
045    // ----------------------------------------------------------- Data members
046
047
048    /**
049     * The output stream to which data will be written prior to the theshold
050     * being reached.
051     */
052    private ByteArrayOutputStream memoryOutputStream;
053
054
055    /**
056     * The output stream to which data will be written at any given time. This
057     * will always be one of <code>memoryOutputStream</code> or
058     * <code>diskOutputStream</code>.
059     */
060    private OutputStream currentOutputStream;
061
062
063    /**
064     * The file to which output will be directed if the threshold is exceeded.
065     */
066    private File outputFile;
067
068    /**
069     * The temporary file prefix.
070     */
071    private final String prefix;
072
073    /**
074     * The temporary file suffix.
075     */
076    private final String suffix;
077
078    /**
079     * The directory to use for temporary files.
080     */
081    private final File directory;
082
083
084    /**
085     * True when close() has been called successfully.
086     */
087    private boolean closed = false;
088
089    // ----------------------------------------------------------- Constructors
090
091
092    /**
093     * Constructs an instance of this class which will trigger an event at the
094     * specified threshold, and save data to a file beyond that point.
095     *
096     * @param threshold  The number of bytes at which to trigger an event.
097     * @param outputFile The file to which data is saved beyond the threshold.
098     */
099    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}