1 /*
2 * Copyright 2001-2005 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.atlassian.core.spool;
18
19 import java.io.*;
20
21 import org.apache.commons.io.IOUtils;
22 import org.apache.commons.io.output.ThresholdingOutputStream;
23 import org.apache.commons.io.output.ByteArrayOutputStream;
24
25
26 /**
27 * <p>An output stream which will retain data in memory until a specified threshold is reached, and only then commit it
28 * to disk. If the stream is closed before the threshold is reached, the data will not be written to disk at all.</p>
29 * <p/>
30 * <p>This class originated in FileUpload processing. In this use case, you do not know in advance the size of the file
31 * being uploaded. If the file is small you want to store it in memory (for speed), but if the file is large you want to
32 * store it to file (to avoid memory issues).</p>
33 *
34 * <p>MODIFICATION: Made this class a bit more extensible and less stupid</p>
35 *
36 * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
37 * @author gaxzerow
38 * @author cowen
39 */
40 public class DeferredFileOutputStream
41 extends ThresholdingOutputStream
42 {
43
44 // ----------------------------------------------------------- Data members
45
46
47 /**
48 * The output stream to which data will be written prior to the theshold being reached.
49 */
50 private ByteArrayOutputStream memoryOutputStream;
51
52
53 /**
54 * The output stream to which data will be written at any given time. This will always be one of
55 * <code>memoryOutputStream</code> or <code>diskOutputStream</code>.
56 */
57 private OutputStream currentOutputStream;
58
59
60 /**
61 * The file to which output will be directed if the threshold is exceeded.
62 */
63 protected File outputFile;
64
65
66 /**
67 * True when close() has been called successfully.
68 */
69 protected boolean closed = false;
70
71 // ----------------------------------------------------------- Constructors
72
73
74 /**
75 * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a
76 * file beyond that point.
77 *
78 * @param threshold The number of bytes at which to trigger an event.
79 * @param outputFile The file to which data is saved beyond the threshold.
80 */
81 public DeferredFileOutputStream(int threshold, File outputFile)
82 {
83 super(threshold);
84 this.outputFile = outputFile;
85
86 memoryOutputStream = new ByteArrayOutputStream();
87 currentOutputStream = memoryOutputStream;
88 }
89
90
91 // --------------------------------------- ThresholdingOutputStream methods
92
93
94 /**
95 * Returns the current output stream. This may be memory based or disk based, depending on the current state with
96 * respect to the threshold.
97 *
98 * @return The underlying output stream.
99 * @throws IOException if an error occurs.
100 */
101 protected OutputStream getStream() throws IOException
102 {
103 return currentOutputStream;
104 }
105
106
107 /**
108 * Switches the underlying output stream from a memory based stream to one that is backed by disk. This is the point
109 * at which we realise that too much data is being written to keep in memory, so we elect to switch to disk-based
110 * storage.
111 *
112 * @throws IOException if an error occurs.
113 */
114 protected void thresholdReached() throws IOException
115 {
116 FileOutputStream fos = new FileOutputStream(getFile());
117 memoryOutputStream.writeTo(fos);
118 currentOutputStream = fos;
119 memoryOutputStream = null;
120 }
121
122
123 // --------------------------------------------------------- Public methods
124
125
126 /**
127 * Determines whether or not the data for this output stream has been retained in memory.
128 *
129 * @return <code>true</code> if the data is available in memory; <code>false</code> otherwise.
130 */
131 public boolean isInMemory()
132 {
133 return (!isThresholdExceeded());
134 }
135
136
137 /**
138 * Returns the data for this output stream as an array of bytes, assuming that the data has been retained in memory.
139 * If the data was written to disk, this method returns <code>null</code>.
140 *
141 * @return The data for this output stream, or <code>null</code> if no such data is available.
142 */
143 public byte[] getData()
144 {
145 if (memoryOutputStream != null)
146 {
147 return memoryOutputStream.toByteArray();
148 }
149 return null;
150 }
151
152
153 /**
154 * Returns the same output file specified in the constructor, even when threashold has not been reached.
155 *
156 * @return The file for this output stream, or <code>null</code> if no such file exists.
157 */
158 public File getFile()
159 {
160 return outputFile;
161 }
162
163
164 /**
165 * Closes underlying output stream, and mark this as closed
166 *
167 * @throws IOException if an error occurs.
168 */
169 public void close() throws IOException
170 {
171 super.close();
172 closed = true;
173 }
174
175 public InputStream getInputStream() throws IOException
176 {
177 // we may only need to check if this is closed if we are working with a file
178 // but we should force the habit of closing wether we are working with
179 // a file or memory.
180 if (!closed)
181 {
182 throw new IOException("Stream not closed");
183 }
184
185 if (isInMemory())
186 {
187 return new ByteArrayInputStream(getData());
188 }
189 else
190 {
191 return new FileInputStream(getFile());
192 }
193 }
194
195 /**
196 * Writes the data from this output stream to the specified output stream, after it has been closed.
197 *
198 * @param out output stream to write to.
199 * @throws IOException if this stream is not yet closed or an error occurs.
200 */
201 public void writeTo(OutputStream out) throws IOException
202 {
203 IOUtils.copy(getInputStream(), out);
204 }
205 }