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 }