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 */ 017package org.apache.commons.io.output; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.util.Objects; 026import java.util.function.Supplier; 027 028import org.apache.commons.io.build.AbstractStreamBuilder; 029import org.apache.commons.io.file.PathUtils; 030 031/** 032 * An output stream which will retain data in memory until a specified threshold is reached, and only then commit it to disk. If the stream is closed before the 033 * threshold is reached, the data will not be written to disk at all. 034 * <p> 035 * To build an instance, use {@link Builder}. 036 * </p> 037 * <p> 038 * The caller is responsible for deleting the output file ({@link #getFile()}, {@link #getPath()}) created by a DeferredFileOutputStream when the caller only 039 * configured a prefix. 040 * </p> 041 * <p> 042 * The caller is responsible for deleting the output file passed to a constructor or builder through {@link Builder#setOutputFile(File)} or 043 * {@link Builder#setOutputFile(Path)}. 044 * </p> 045 * <p> 046 * This class originated in FileUpload processing. In this use case, you do not know in advance the size of the file being uploaded. If the file is small you 047 * want to store it in memory (for speed), but if the file is large you want to store it to file (to avoid memory issues). 048 * </p> 049 * 050 * @see Builder 051 */ 052public class DeferredFileOutputStream extends ThresholdingOutputStream { 053 054 // @formatter:off 055 /** 056 * Builds a new {@link DeferredFileOutputStream}. 057 * <p> 058 * For example: 059 * </p> 060 * <pre>{@code 061 * DeferredFileOutputStream s = DeferredFileOutputStream.builder() 062 * .setBufferSize(4096) 063 * .setDirectory(dir) 064 * .setOutputFile(outputFile) 065 * .setPrefix(prefix) 066 * .setSuffix(suffix) 067 * .setThreshold(threshold) 068 * .get();} 069 * </pre> 070 * <p> 071 * The only super's aspect used is buffer size. 072 * </p> 073 * 074 * @see #get() 075 * @since 2.12.0 076 */ 077 // @formatter:on 078 public static class Builder extends AbstractStreamBuilder<DeferredFileOutputStream, Builder> { 079 080 private int threshold; 081 private Path outputFile; 082 private String prefix; 083 private String suffix; 084 private Path directory; 085 086 /** 087 * Constructs a new builder of {@link DeferredFileOutputStream}. 088 */ 089 public Builder() { 090 setBufferSizeDefault(AbstractByteArrayOutputStream.DEFAULT_SIZE); 091 setBufferSize(AbstractByteArrayOutputStream.DEFAULT_SIZE); 092 } 093 094 /** 095 * Builds a new {@link DeferredFileOutputStream}. 096 * <p> 097 * This builder uses the following aspects: 098 * </p> 099 * <ul> 100 * <li>{@link #getBufferSize()}</li> 101 * <li>threshold</li> 102 * <li>outputFile</li> 103 * <li>prefix</li> 104 * <li>suffix</li> 105 * <li>directory</li> 106 * </ul> 107 * 108 * @return a new instance. 109 * @see #getUnchecked() 110 */ 111 @Override 112 public DeferredFileOutputStream get() { 113 return new DeferredFileOutputStream(threshold, outputFile, prefix, suffix, directory, getBufferSize()); 114 } 115 116 /** 117 * Sets the temporary file directory. 118 * 119 * @param directory Temporary file directory. 120 * @return {@code this} instance. 121 */ 122 public Builder setDirectory(final File directory) { 123 this.directory = toPath(directory, null); 124 return this; 125 } 126 127 /** 128 * Sets the temporary file directory. 129 * 130 * @param directory Temporary file directory. 131 * @return {@code this} instance. 132 * @since 2.14.0 133 */ 134 public Builder setDirectory(final Path directory) { 135 this.directory = toPath(directory, null); 136 return this; 137 } 138 139 /** 140 * Sets the file to which data is saved beyond the threshold. 141 * 142 * @param outputFile The file to which data is saved beyond the threshold. 143 * @return {@code this} instance. 144 */ 145 public Builder setOutputFile(final File outputFile) { 146 this.outputFile = toPath(outputFile, null); 147 return this; 148 } 149 150 /** 151 * Sets the file to which data is saved beyond the threshold. 152 * 153 * @param outputFile The file to which data is saved beyond the threshold. 154 * @return {@code this} instance. 155 * @since 2.14.0 156 */ 157 public Builder setOutputFile(final Path outputFile) { 158 this.outputFile = toPath(outputFile, null); 159 return this; 160 } 161 162 /** 163 * Sets the prefix to use for the temporary file. 164 * 165 * @param prefix Prefix to use for the temporary file. 166 * @return {@code this} instance. 167 */ 168 public Builder setPrefix(final String prefix) { 169 this.prefix = prefix; 170 return this; 171 } 172 173 /** 174 * Sets the suffix to use for the temporary file. 175 * 176 * @param suffix Suffix to use for the temporary file. 177 * @return {@code this} instance. 178 */ 179 public Builder setSuffix(final String suffix) { 180 this.suffix = suffix; 181 return this; 182 } 183 184 /** 185 * Sets the number of bytes at which to trigger an event. 186 * 187 * @param threshold The number of bytes at which to trigger an event. 188 * @return {@code this} instance. 189 */ 190 public Builder setThreshold(final int threshold) { 191 this.threshold = threshold; 192 return this; 193 } 194 195 } 196 197 /** 198 * Constructs a new {@link Builder}. 199 * 200 * @return a new {@link Builder}. 201 * @since 2.12.0 202 */ 203 public static Builder builder() { 204 return new Builder(); 205 } 206 207 private static int checkBufferSize(final int initialBufferSize) { 208 if (initialBufferSize < 0) { 209 throw new IllegalArgumentException("Initial buffer size must be at least 0."); 210 } 211 return initialBufferSize; 212 } 213 214 private static Path toPath(final File file, final Supplier<Path> defaultPathSupplier) { 215 return file != null ? file.toPath() : defaultPathSupplier == null ? null : defaultPathSupplier.get(); 216 } 217 218 private static Path toPath(final Path file, final Supplier<Path> defaultPathSupplier) { 219 return file != null ? file : defaultPathSupplier == null ? null : defaultPathSupplier.get(); 220 } 221 222 /** 223 * The output stream to which data will be written prior to the threshold being reached. 224 */ 225 private ByteArrayOutputStream memoryOutputStream; 226 227 /** 228 * The output stream to which data will be written at any given time. This will always be one of {@code memoryOutputStream} or {@code diskOutputStream}. 229 */ 230 private OutputStream currentOutputStream; 231 232 /** 233 * The file to which output will be directed if the threshold is exceeded. 234 */ 235 private Path outputPath; 236 237 /** 238 * The temporary file prefix. 239 */ 240 private final String prefix; 241 242 /** 243 * The temporary file suffix. 244 */ 245 private final String suffix; 246 247 /** 248 * The directory to use for temporary files. 249 */ 250 private final Path directory; 251 252 /** 253 * True when close() has been called successfully. 254 */ 255 private boolean closed; 256 257 /** 258 * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a file beyond that point. The initial 259 * buffer size will default to {@value AbstractByteArrayOutputStream#DEFAULT_SIZE} bytes which is ByteArrayOutputStream's default buffer size. 260 * 261 * @param threshold The number of bytes at which to trigger an event. 262 * @param outputFile The file to which data is saved beyond the threshold. 263 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 264 */ 265 @Deprecated 266 public DeferredFileOutputStream(final int threshold, final File outputFile) { 267 this(threshold, outputFile, null, null, null, AbstractByteArrayOutputStream.DEFAULT_SIZE); 268 } 269 270 /** 271 * Constructs an instance of this class which will trigger an event at the specified threshold, and save data either to a file beyond that point. 272 * 273 * @param threshold The number of bytes at which to trigger an event. 274 * @param outputFile The file to which data is saved beyond the threshold. 275 * @param prefix Prefix to use for the temporary file. 276 * @param suffix Suffix to use for the temporary file. 277 * @param directory Temporary file directory. 278 * @param initialBufferSize The initial size of the in memory buffer. 279 * @throws IllegalArgumentException if initialBufferSize < 0. 280 */ 281 private DeferredFileOutputStream(final int threshold, final File outputFile, final String prefix, final String suffix, final File directory, 282 final int initialBufferSize) { 283 super(threshold); 284 this.outputPath = toPath(outputFile, null); 285 this.prefix = prefix; 286 this.suffix = suffix; 287 this.directory = toPath(directory, PathUtils::getTempDirectory); 288 this.memoryOutputStream = new ByteArrayOutputStream(checkBufferSize(initialBufferSize)); 289 this.currentOutputStream = memoryOutputStream; 290 } 291 292 /** 293 * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a file beyond that point. 294 * 295 * @param threshold The number of bytes at which to trigger an event. 296 * @param initialBufferSize The initial size of the in memory buffer. 297 * @param outputFile The file to which data is saved beyond the threshold. 298 * @since 2.5 299 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 300 */ 301 @Deprecated 302 public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final File outputFile) { 303 this(threshold, outputFile, null, null, null, initialBufferSize); 304 } 305 306 /** 307 * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a temporary file beyond that point. 308 * 309 * @param threshold The number of bytes at which to trigger an event. 310 * @param initialBufferSize The initial size of the in memory buffer. 311 * @param prefix Prefix to use for the temporary file. 312 * @param suffix Suffix to use for the temporary file. 313 * @param directory Temporary file directory. 314 * @since 2.5 315 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 316 */ 317 @Deprecated 318 public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final String prefix, final String suffix, final File directory) { 319 this(threshold, null, Objects.requireNonNull(prefix, "prefix"), suffix, directory, initialBufferSize); 320 } 321 322 /** 323 * Constructs an instance of this class which will trigger an event at the specified threshold, and save data either to a file beyond that point. 324 * 325 * @param threshold The number of bytes at which to trigger an event. 326 * @param outputFile The file to which data is saved beyond the threshold. 327 * @param prefix Prefix to use for the temporary file. 328 * @param suffix Suffix to use for the temporary file. 329 * @param directory Temporary file directory. 330 * @param initialBufferSize The initial size of the in memory buffer. 331 * @throws IllegalArgumentException if initialBufferSize < 0. 332 */ 333 private DeferredFileOutputStream(final int threshold, final Path outputFile, final String prefix, final String suffix, final Path directory, 334 final int initialBufferSize) { 335 super(threshold); 336 this.outputPath = toPath(outputFile, null); 337 this.prefix = prefix; 338 this.suffix = suffix; 339 this.directory = toPath(directory, PathUtils::getTempDirectory); 340 this.memoryOutputStream = new ByteArrayOutputStream(checkBufferSize(initialBufferSize)); 341 this.currentOutputStream = memoryOutputStream; 342 } 343 344 /** 345 * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a temporary file beyond that point. The 346 * initial buffer size will default to 32 bytes which is ByteArrayOutputStream's default buffer size. 347 * 348 * @param threshold The number of bytes at which to trigger an event. 349 * @param prefix Prefix to use for the temporary file. 350 * @param suffix Suffix to use for the temporary file. 351 * @param directory Temporary file directory. 352 * @since 1.4 353 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 354 */ 355 @Deprecated 356 public DeferredFileOutputStream(final int threshold, final String prefix, final String suffix, final File directory) { 357 this(threshold, null, Objects.requireNonNull(prefix, "prefix"), suffix, directory, AbstractByteArrayOutputStream.DEFAULT_SIZE); 358 } 359 360 /** 361 * Closes underlying output stream, and mark this as closed 362 * 363 * @throws IOException if an error occurs. 364 */ 365 @Override 366 public void close() throws IOException { 367 super.close(); 368 closed = true; 369 } 370 371 /** 372 * Gets the data for this output stream as an array of bytes, assuming that the data has been retained in memory. If the data was written to disk, this 373 * method returns {@code null}. 374 * 375 * @return The data for this output stream, or {@code null} if no such data is available. 376 */ 377 public byte[] getData() { 378 return memoryOutputStream != null ? memoryOutputStream.toByteArray() : null; 379 } 380 381 /** 382 * Gets either the output File specified in the constructor or the temporary File created or null. 383 * <p> 384 * If the constructor specifying the File is used then it returns that same output File, even when threshold has not been reached. 385 * </p> 386 * <p> 387 * If constructor specifying a temporary File prefix/suffix is used then the temporary File created once the threshold is reached is returned if the 388 * threshold was not reached then {@code null} is returned. 389 * </p> 390 * 391 * @return The File for this output stream, or {@code null} if no such File exists. 392 */ 393 public File getFile() { 394 return outputPath != null ? outputPath.toFile() : null; 395 } 396 397 /** 398 * Gets either the output Path specified in the constructor or the temporary Path created or null. 399 * <p> 400 * If the constructor specifying the file is used then it returns that same output Path, even when threshold has not been reached. 401 * </p> 402 * <p> 403 * If constructor specifying a temporary Path prefix/suffix is used then the temporary Path created once the threshold is reached is returned if the 404 * threshold was not reached then {@code null} is returned. 405 * </p> 406 * 407 * @return The Path for this output stream, or {@code null} if no such Path exists. 408 * @since 2.14.0 409 */ 410 public Path getPath() { 411 return outputPath; 412 } 413 414 /** 415 * Gets the current output stream. This may be memory based or disk based, depending on the current state with respect to the threshold. 416 * 417 * @return The underlying output stream. 418 * @throws IOException if an error occurs. 419 * @deprecated Use {@link #getOutputStream()}. 420 */ 421 @Deprecated 422 @Override 423 protected OutputStream getStream() throws IOException { 424 return currentOutputStream; 425 } 426 427 /** 428 * Tests whether or not the data for this output stream has been retained in memory. 429 * 430 * @return {@code true} if the data is available in memory; {@code false} otherwise. 431 */ 432 public boolean isInMemory() { 433 return !isThresholdExceeded(); 434 } 435 436 /** 437 * Switches the underlying output stream from a memory based stream to one that is backed by disk. This is the point at which we realize that too much data 438 * is being written to keep in memory, so we elect to switch to disk-based storage. 439 * 440 * @throws IOException if an error occurs. 441 */ 442 @Override 443 protected void thresholdReached() throws IOException { 444 if (prefix != null) { 445 outputPath = Files.createTempFile(directory, prefix, suffix); 446 } 447 PathUtils.createParentDirectories(outputPath, null, PathUtils.EMPTY_FILE_ATTRIBUTE_ARRAY); 448 final OutputStream fos = Files.newOutputStream(outputPath); 449 try { 450 memoryOutputStream.writeTo(fos); 451 } catch (final IOException e) { 452 fos.close(); 453 throw e; 454 } 455 currentOutputStream = fos; 456 memoryOutputStream = null; 457 } 458 459 /** 460 * Converts the current contents of this byte stream to an {@link InputStream}. If the data for this output stream has been retained in memory, the returned 461 * stream is backed by buffers of {@code this} stream, avoiding memory allocation and copy, thus saving space and time.<br> 462 * Otherwise, the returned stream will be one that is created from the data that has been committed to disk. 463 * 464 * @return the current contents of this output stream. 465 * @throws IOException if this stream is not yet closed or an error occurs. 466 * @see org.apache.commons.io.output.ByteArrayOutputStream#toInputStream() 467 * @since 2.9.0 468 */ 469 public InputStream toInputStream() throws IOException { 470 // we may only need to check if this is closed if we are working with a file 471 // but we should force the habit of closing whether we are working with 472 // a file or memory. 473 if (!closed) { 474 throw new IOException("Stream not closed"); 475 } 476 if (isInMemory()) { 477 return memoryOutputStream.toInputStream(); 478 } 479 return Files.newInputStream(outputPath); 480 } 481 482 /** 483 * Writes the data from this output stream to the specified output stream, after it has been closed. 484 * 485 * @param outputStream output stream to write to. 486 * @throws NullPointerException if the OutputStream is {@code null}. 487 * @throws IOException if this stream is not yet closed or an error occurs. 488 */ 489 public void writeTo(final OutputStream outputStream) throws IOException { 490 // we may only need to check if this is closed if we are working with a file 491 // but we should force the habit of closing whether we are working with 492 // a file or memory. 493 if (!closed) { 494 throw new IOException("Stream not closed"); 495 } 496 if (isInMemory()) { 497 memoryOutputStream.writeTo(outputStream); 498 } else { 499 Files.copy(outputPath, outputStream); 500 } 501 } 502}