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    }