View Javadoc

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  import org.apache.commons.io.IOUtils;
25  
26  
27  /**
28   * An output stream which will retain data in memory until a specified
29   * threshold is reached, and only then commit it to disk. If the stream is
30   * closed before the threshold is reached, the data will not be written to
31   * disk at all.
32   * <p>
33   * This class originated in FileUpload processing. In this use case, you do
34   * not know in advance the size of the file being uploaded. If the file is small
35   * you want to store it in memory (for speed), but if the file is large you want
36   * to store it to file (to avoid memory issues).
37   *
38   * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
39   * @author gaxzerow
40   *
41   * @version $Id: DeferredFileOutputStream.java 437567 2006-08-28 06:39:07Z bayard $
42   */
43  public class DeferredFileOutputStream
44      extends ThresholdingOutputStream
45  {
46  
47      // ----------------------------------------------------------- Data members
48  
49  
50      /**
51       * The output stream to which data will be written prior to the theshold
52       * being reached.
53       */
54      private ByteArrayOutputStream memoryOutputStream;
55  
56  
57      /**
58       * The output stream to which data will be written at any given time. This
59       * will always be one of <code>memoryOutputStream</code> or
60       * <code>diskOutputStream</code>.
61       */
62      private OutputStream currentOutputStream;
63  
64  
65      /**
66       * The file to which output will be directed if the threshold is exceeded.
67       */
68      private File outputFile;
69  
70      
71      /**
72       * True when close() has been called successfully.
73       */
74      private boolean closed = false;
75  
76      // ----------------------------------------------------------- Constructors
77  
78  
79      /**
80       * Constructs an instance of this class which will trigger an event at the
81       * specified threshold, and save data to a file beyond that point.
82       *
83       * @param threshold  The number of bytes at which to trigger an event.
84       * @param outputFile The file to which data is saved beyond the threshold.
85       */
86      public DeferredFileOutputStream(int threshold, File outputFile)
87      {
88          super(threshold);
89          this.outputFile = outputFile;
90  
91          memoryOutputStream = new ByteArrayOutputStream();
92          currentOutputStream = memoryOutputStream;
93      }
94  
95  
96      // --------------------------------------- ThresholdingOutputStream methods
97  
98  
99      /**
100      * Returns the current output stream. This may be memory based or disk
101      * based, depending on the current state with respect to the threshold.
102      *
103      * @return The underlying output stream.
104      *
105      * @exception IOException if an error occurs.
106      */
107     protected OutputStream getStream() throws IOException
108     {
109         return currentOutputStream;
110     }
111 
112 
113     /**
114      * Switches the underlying output stream from a memory based stream to one
115      * that is backed by disk. This is the point at which we realise that too
116      * much data is being written to keep in memory, so we elect to switch to
117      * disk-based storage.
118      *
119      * @exception IOException if an error occurs.
120      */
121     protected void thresholdReached() throws IOException
122     {
123         FileOutputStream fos = new FileOutputStream(outputFile);
124         memoryOutputStream.writeTo(fos);
125         currentOutputStream = fos;
126         memoryOutputStream = null;
127     }
128 
129 
130     // --------------------------------------------------------- Public methods
131 
132 
133     /**
134      * Determines whether or not the data for this output stream has been
135      * retained in memory.
136      *
137      * @return <code>true</code> if the data is available in memory;
138      *         <code>false</code> otherwise.
139      */
140     public boolean isInMemory()
141     {
142         return (!isThresholdExceeded());
143     }
144 
145 
146     /**
147      * Returns the data for this output stream as an array of bytes, assuming
148      * that the data has been retained in memory. If the data was written to
149      * disk, this method returns <code>null</code>.
150      *
151      * @return The data for this output stream, or <code>null</code> if no such
152      *         data is available.
153      */
154     public byte[] getData()
155     {
156         if (memoryOutputStream != null)
157         {
158             return memoryOutputStream.toByteArray();
159         }
160         return null;
161     }
162 
163 
164     /**
165      * Returns the same output file specified in the constructor, even when
166      * threashold has not been reached.
167      *
168      * @return The file for this output stream, or <code>null</code> if no such
169      *         file exists.
170      */
171     public File getFile()
172     {
173         return outputFile;
174     }
175     
176         
177     /**
178      * Closes underlying output stream, and mark this as closed
179      *
180      * @exception IOException if an error occurs.
181      */
182     public void close() throws IOException
183     {
184         super.close();
185         closed = true;
186     }
187     
188     
189     /**
190      * Writes the data from this output stream to the specified output stream,
191      * after it has been closed.
192      *
193      * @param out output stream to write to.
194      * @exception IOException if this stream is not yet closed or an error occurs.
195      */
196     public void writeTo(OutputStream out) throws IOException 
197     {
198         // we may only need to check if this is closed if we are working with a file
199         // but we should force the habit of closing wether we are working with
200         // a file or memory.
201         if (!closed)
202         {
203             throw new IOException("Stream not closed");
204         }
205         
206         if(isInMemory())
207         {
208             memoryOutputStream.writeTo(out);
209         }
210         else
211         {
212             FileInputStream fis = new FileInputStream(outputFile);
213             try {
214                 IOUtils.copy(fis, out);
215             } finally {
216                 IOUtils.closeQuietly(fis);
217             }
218         }
219     }
220 }