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 736890 2009-01-23 02:02:22Z niallp $ 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 final String prefix; 075 076 /** 077 * The temporary file suffix. 078 */ 079 private final String suffix; 080 081 /** 082 * The directory to use for temporary files. 083 */ 084 private final 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 this(threshold, outputFile, null, null, null); 105 } 106 107 108 /** 109 * Constructs an instance of this class which will trigger an event at the 110 * specified threshold, and save data to a temporary file beyond that point. 111 * 112 * @param threshold The number of bytes at which to trigger an event. 113 * @param prefix Prefix to use for the temporary file. 114 * @param suffix Suffix to use for the temporary file. 115 * @param directory Temporary file directory. 116 * 117 * @since Commons IO 1.4 118 */ 119 public DeferredFileOutputStream(int threshold, String prefix, String suffix, File directory) 120 { 121 this(threshold, null, prefix, suffix, directory); 122 if (prefix == null) { 123 throw new IllegalArgumentException("Temporary file prefix is missing"); 124 } 125 } 126 127 /** 128 * Constructs an instance of this class which will trigger an event at the 129 * specified threshold, and save data either to a file beyond that point. 130 * 131 * @param threshold The number of bytes at which to trigger an event. 132 * @param outputFile The file to which data is saved beyond the threshold. 133 * @param prefix Prefix to use for the temporary file. 134 * @param suffix Suffix to use for the temporary file. 135 * @param directory Temporary file directory. 136 */ 137 private DeferredFileOutputStream(int threshold, File outputFile, String prefix, String suffix, File directory) { 138 super(threshold); 139 this.outputFile = outputFile; 140 141 memoryOutputStream = new ByteArrayOutputStream(); 142 currentOutputStream = memoryOutputStream; 143 this.prefix = prefix; 144 this.suffix = suffix; 145 this.directory = directory; 146 } 147 148 149 // --------------------------------------- ThresholdingOutputStream methods 150 151 152 /** 153 * Returns the current output stream. This may be memory based or disk 154 * based, depending on the current state with respect to the threshold. 155 * 156 * @return The underlying output stream. 157 * 158 * @exception IOException if an error occurs. 159 */ 160 @Override 161 protected OutputStream getStream() throws IOException 162 { 163 return currentOutputStream; 164 } 165 166 167 /** 168 * Switches the underlying output stream from a memory based stream to one 169 * that is backed by disk. This is the point at which we realise that too 170 * much data is being written to keep in memory, so we elect to switch to 171 * disk-based storage. 172 * 173 * @exception IOException if an error occurs. 174 */ 175 @Override 176 protected void thresholdReached() throws IOException 177 { 178 if (prefix != null) { 179 outputFile = File.createTempFile(prefix, suffix, directory); 180 } 181 FileOutputStream fos = new FileOutputStream(outputFile); 182 memoryOutputStream.writeTo(fos); 183 currentOutputStream = fos; 184 memoryOutputStream = null; 185 } 186 187 188 // --------------------------------------------------------- Public methods 189 190 191 /** 192 * Determines whether or not the data for this output stream has been 193 * retained in memory. 194 * 195 * @return <code>true</code> if the data is available in memory; 196 * <code>false</code> otherwise. 197 */ 198 public boolean isInMemory() 199 { 200 return (!isThresholdExceeded()); 201 } 202 203 204 /** 205 * Returns the data for this output stream as an array of bytes, assuming 206 * that the data has been retained in memory. If the data was written to 207 * disk, this method returns <code>null</code>. 208 * 209 * @return The data for this output stream, or <code>null</code> if no such 210 * data is available. 211 */ 212 public byte[] getData() 213 { 214 if (memoryOutputStream != null) 215 { 216 return memoryOutputStream.toByteArray(); 217 } 218 return null; 219 } 220 221 222 /** 223 * Returns either the output file specified in the constructor or 224 * the temporary file created or null. 225 * <p> 226 * If the constructor specifying the file is used then it returns that 227 * same output file, even when threashold has not been reached. 228 * <p> 229 * If constructor specifying a temporary file prefix/suffix is used 230 * then the temporary file created once the threashold is reached is returned 231 * If the threshold was not reached then <code>null</code> is returned. 232 * 233 * @return The file for this output stream, or <code>null</code> if no such 234 * file exists. 235 */ 236 public File getFile() 237 { 238 return outputFile; 239 } 240 241 242 /** 243 * Closes underlying output stream, and mark this as closed 244 * 245 * @exception IOException if an error occurs. 246 */ 247 @Override 248 public void close() throws IOException 249 { 250 super.close(); 251 closed = true; 252 } 253 254 255 /** 256 * Writes the data from this output stream to the specified output stream, 257 * after it has been closed. 258 * 259 * @param out output stream to write to. 260 * @exception IOException if this stream is not yet closed or an error occurs. 261 */ 262 public void writeTo(OutputStream out) throws IOException 263 { 264 // we may only need to check if this is closed if we are working with a file 265 // but we should force the habit of closing wether we are working with 266 // a file or memory. 267 if (!closed) 268 { 269 throw new IOException("Stream not closed"); 270 } 271 272 if(isInMemory()) 273 { 274 memoryOutputStream.writeTo(out); 275 } 276 else 277 { 278 FileInputStream fis = new FileInputStream(outputFile); 279 try { 280 IOUtils.copy(fis, out); 281 } finally { 282 IOUtils.closeQuietly(fis); 283 } 284 } 285 } 286 }