Home | History | Annotate | Download | only in output
      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 (at) 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      */
    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 }
    270