DeferredFileOutputStream.java

  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. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.nio.file.Files;
  23. import java.nio.file.Path;
  24. import java.util.Objects;
  25. import java.util.function.Supplier;

  26. import org.apache.commons.io.build.AbstractStreamBuilder;
  27. import org.apache.commons.io.file.PathUtils;

  28. /**
  29.  * 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
  30.  * threshold is reached, the data will not be written to disk at all.
  31.  * <p>
  32.  * To build an instance, use {@link Builder}.
  33.  * </p>
  34.  * <p>
  35.  * The caller is responsible for deleting the output file ({@link #getFile()}, {@link #getPath()}) created by a DeferredFileOutputStream when the caller only
  36.  * configured a prefix.
  37.  * </p>
  38.  * <p>
  39.  * The caller is responsible for deleting the output file passed to a constructor or builder through {@link Builder#setOutputFile(File)} or
  40.  * {@link Builder#setOutputFile(Path)}.
  41.  * </p>
  42.  * <p>
  43.  * 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
  44.  * 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).
  45.  * </p>
  46.  *
  47.  * @see Builder
  48.  */
  49. public class DeferredFileOutputStream extends ThresholdingOutputStream {

  50.     // @formatter:off
  51.     /**
  52.      * Builds a new {@link DeferredFileOutputStream}.
  53.      * <p>
  54.      * For example:
  55.      * </p>
  56.      * <pre>{@code
  57.      * DeferredFileOutputStream s = DeferredFileOutputStream.builder()
  58.      *   .setBufferSize(4096)
  59.      *   .setDirectory(dir)
  60.      *   .setOutputFile(outputFile)
  61.      *   .setPrefix(prefix)
  62.      *   .setSuffix(suffix)
  63.      *   .setThreshold(threshold)
  64.      *   .get();}
  65.      * </pre>
  66.      * <p>
  67.      * The only super's aspect used is buffer size.
  68.      * </p>
  69.      *
  70.      * @see #get()
  71.      * @since 2.12.0
  72.      */
  73.     // @formatter:on
  74.     public static class Builder extends AbstractStreamBuilder<DeferredFileOutputStream, Builder> {

  75.         private int threshold;
  76.         private Path outputFile;
  77.         private String prefix;
  78.         private String suffix;
  79.         private Path directory;

  80.         /**
  81.          * Constructs a new builder of {@link DeferredFileOutputStream}.
  82.          */
  83.         public Builder() {
  84.             setBufferSizeDefault(AbstractByteArrayOutputStream.DEFAULT_SIZE);
  85.             setBufferSize(AbstractByteArrayOutputStream.DEFAULT_SIZE);
  86.         }

  87.         /**
  88.          * Builds a new {@link DeferredFileOutputStream}.
  89.          * <p>
  90.          * This builder uses the following aspects:
  91.          * </p>
  92.          * <ul>
  93.          * <li>{@link #getBufferSize()}</li>
  94.          * <li>threshold</li>
  95.          * <li>outputFile</li>
  96.          * <li>prefix</li>
  97.          * <li>suffix</li>
  98.          * <li>directory</li>
  99.          * </ul>
  100.          *
  101.          * @return a new instance.
  102.          * @see #getUnchecked()
  103.          */
  104.         @Override
  105.         public DeferredFileOutputStream get() {
  106.             return new DeferredFileOutputStream(threshold, outputFile, prefix, suffix, directory, getBufferSize());
  107.         }

  108.         /**
  109.          * Sets the temporary file directory.
  110.          *
  111.          * @param directory Temporary file directory.
  112.          * @return {@code this} instance.
  113.          */
  114.         public Builder setDirectory(final File directory) {
  115.             this.directory = toPath(directory, null);
  116.             return this;
  117.         }

  118.         /**
  119.          * Sets the temporary file directory.
  120.          *
  121.          * @param directory Temporary file directory.
  122.          * @return {@code this} instance.
  123.          * @since 2.14.0
  124.          */
  125.         public Builder setDirectory(final Path directory) {
  126.             this.directory = toPath(directory, null);
  127.             return this;
  128.         }

  129.         /**
  130.          * Sets the file to which data is saved beyond the threshold.
  131.          *
  132.          * @param outputFile The file to which data is saved beyond the threshold.
  133.          * @return {@code this} instance.
  134.          */
  135.         public Builder setOutputFile(final File outputFile) {
  136.             this.outputFile = toPath(outputFile, null);
  137.             return this;
  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.          * @since 2.14.0
  145.          */
  146.         public Builder setOutputFile(final Path outputFile) {
  147.             this.outputFile = toPath(outputFile, null);
  148.             return this;
  149.         }

  150.         /**
  151.          * Sets the prefix to use for the temporary file.
  152.          *
  153.          * @param prefix Prefix to use for the temporary file.
  154.          * @return {@code this} instance.
  155.          */
  156.         public Builder setPrefix(final String prefix) {
  157.             this.prefix = prefix;
  158.             return this;
  159.         }

  160.         /**
  161.          * Sets the suffix to use for the temporary file.
  162.          *
  163.          * @param suffix Suffix to use for the temporary file.
  164.          * @return {@code this} instance.
  165.          */
  166.         public Builder setSuffix(final String suffix) {
  167.             this.suffix = suffix;
  168.             return this;
  169.         }

  170.         /**
  171.          * Sets the number of bytes at which to trigger an event.
  172.          *
  173.          * @param threshold The number of bytes at which to trigger an event.
  174.          * @return {@code this} instance.
  175.          */
  176.         public Builder setThreshold(final int threshold) {
  177.             this.threshold = threshold;
  178.             return this;
  179.         }

  180.     }

  181.     /**
  182.      * Constructs a new {@link Builder}.
  183.      *
  184.      * @return a new {@link Builder}.
  185.      * @since 2.12.0
  186.      */
  187.     public static Builder builder() {
  188.         return new Builder();
  189.     }

  190.     private static int checkBufferSize(final int initialBufferSize) {
  191.         if (initialBufferSize < 0) {
  192.             throw new IllegalArgumentException("Initial buffer size must be at least 0.");
  193.         }
  194.         return initialBufferSize;
  195.     }

  196.     private static Path toPath(final File file, final Supplier<Path> defaultPathSupplier) {
  197.         return file != null ? file.toPath() : defaultPathSupplier == null ? null : defaultPathSupplier.get();
  198.     }

  199.     private static Path toPath(final Path file, final Supplier<Path> defaultPathSupplier) {
  200.         return file != null ? file : defaultPathSupplier == null ? null : defaultPathSupplier.get();
  201.     }

  202.     /**
  203.      * The output stream to which data will be written prior to the threshold being reached.
  204.      */
  205.     private ByteArrayOutputStream memoryOutputStream;

  206.     /**
  207.      * The output stream to which data will be written at any given time. This will always be one of {@code memoryOutputStream} or {@code diskOutputStream}.
  208.      */
  209.     private OutputStream currentOutputStream;

  210.     /**
  211.      * The file to which output will be directed if the threshold is exceeded.
  212.      */
  213.     private Path outputPath;

  214.     /**
  215.      * The temporary file prefix.
  216.      */
  217.     private final String prefix;

  218.     /**
  219.      * The temporary file suffix.
  220.      */
  221.     private final String suffix;

  222.     /**
  223.      * The directory to use for temporary files.
  224.      */
  225.     private final Path directory;

  226.     /**
  227.      * True when close() has been called successfully.
  228.      */
  229.     private boolean closed;

  230.     /**
  231.      * 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
  232.      * buffer size will default to {@value AbstractByteArrayOutputStream#DEFAULT_SIZE} bytes which is ByteArrayOutputStream's default buffer size.
  233.      *
  234.      * @param threshold  The number of bytes at which to trigger an event.
  235.      * @param outputFile The file to which data is saved beyond the threshold.
  236.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  237.      */
  238.     @Deprecated
  239.     public DeferredFileOutputStream(final int threshold, final File outputFile) {
  240.         this(threshold, outputFile, null, null, null, AbstractByteArrayOutputStream.DEFAULT_SIZE);
  241.     }

  242.     /**
  243.      * 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.
  244.      *
  245.      * @param threshold         The number of bytes at which to trigger an event.
  246.      * @param outputFile        The file to which data is saved beyond the threshold.
  247.      * @param prefix            Prefix to use for the temporary file.
  248.      * @param suffix            Suffix to use for the temporary file.
  249.      * @param directory         Temporary file directory.
  250.      * @param initialBufferSize The initial size of the in memory buffer.
  251.      * @throws IllegalArgumentException if initialBufferSize &lt; 0.
  252.      */
  253.     private DeferredFileOutputStream(final int threshold, final File outputFile, final String prefix, final String suffix, final File directory,
  254.             final int initialBufferSize) {
  255.         super(threshold);
  256.         this.outputPath = toPath(outputFile, null);
  257.         this.prefix = prefix;
  258.         this.suffix = suffix;
  259.         this.directory = toPath(directory, PathUtils::getTempDirectory);
  260.         this.memoryOutputStream = new ByteArrayOutputStream(checkBufferSize(initialBufferSize));
  261.         this.currentOutputStream = memoryOutputStream;
  262.     }

  263.     /**
  264.      * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a file beyond that point.
  265.      *
  266.      * @param threshold         The number of bytes at which to trigger an event.
  267.      * @param initialBufferSize The initial size of the in memory buffer.
  268.      * @param outputFile        The file to which data is saved beyond the threshold.
  269.      * @since 2.5
  270.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  271.      */
  272.     @Deprecated
  273.     public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final File outputFile) {
  274.         this(threshold, outputFile, null, null, null, initialBufferSize);
  275.     }

  276.     /**
  277.      * 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.
  278.      *
  279.      * @param threshold         The number of bytes at which to trigger an event.
  280.      * @param initialBufferSize The initial size of the in memory buffer.
  281.      * @param prefix            Prefix to use for the temporary file.
  282.      * @param suffix            Suffix to use for the temporary file.
  283.      * @param directory         Temporary file directory.
  284.      * @since 2.5
  285.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  286.      */
  287.     @Deprecated
  288.     public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final String prefix, final String suffix, final File directory) {
  289.         this(threshold, null, Objects.requireNonNull(prefix, "prefix"), suffix, directory, initialBufferSize);
  290.     }

  291.     /**
  292.      * 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.
  293.      *
  294.      * @param threshold         The number of bytes at which to trigger an event.
  295.      * @param outputFile        The file to which data is saved beyond the threshold.
  296.      * @param prefix            Prefix to use for the temporary file.
  297.      * @param suffix            Suffix to use for the temporary file.
  298.      * @param directory         Temporary file directory.
  299.      * @param initialBufferSize The initial size of the in memory buffer.
  300.      * @throws IllegalArgumentException if initialBufferSize &lt; 0.
  301.      */
  302.     private DeferredFileOutputStream(final int threshold, final Path outputFile, final String prefix, final String suffix, final Path directory,
  303.             final int initialBufferSize) {
  304.         super(threshold);
  305.         this.outputPath = toPath(outputFile, null);
  306.         this.prefix = prefix;
  307.         this.suffix = suffix;
  308.         this.directory = toPath(directory, PathUtils::getTempDirectory);
  309.         this.memoryOutputStream = new ByteArrayOutputStream(checkBufferSize(initialBufferSize));
  310.         this.currentOutputStream = memoryOutputStream;
  311.     }

  312.     /**
  313.      * 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
  314.      * initial buffer size will default to 32 bytes which is ByteArrayOutputStream's default buffer size.
  315.      *
  316.      * @param threshold The number of bytes at which to trigger an event.
  317.      * @param prefix    Prefix to use for the temporary file.
  318.      * @param suffix    Suffix to use for the temporary file.
  319.      * @param directory Temporary file directory.
  320.      * @since 1.4
  321.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  322.      */
  323.     @Deprecated
  324.     public DeferredFileOutputStream(final int threshold, final String prefix, final String suffix, final File directory) {
  325.         this(threshold, null, Objects.requireNonNull(prefix, "prefix"), suffix, directory, AbstractByteArrayOutputStream.DEFAULT_SIZE);
  326.     }

  327.     /**
  328.      * Closes underlying output stream, and mark this as closed
  329.      *
  330.      * @throws IOException if an error occurs.
  331.      */
  332.     @Override
  333.     public void close() throws IOException {
  334.         super.close();
  335.         closed = true;
  336.     }

  337.     /**
  338.      * 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
  339.      * method returns {@code null}.
  340.      *
  341.      * @return The data for this output stream, or {@code null} if no such data is available.
  342.      */
  343.     public byte[] getData() {
  344.         return memoryOutputStream != null ? memoryOutputStream.toByteArray() : null;
  345.     }

  346.     /**
  347.      * Gets either the output File specified in the constructor or the temporary File created or null.
  348.      * <p>
  349.      * If the constructor specifying the File is used then it returns that same output File, even when threshold has not been reached.
  350.      * </p>
  351.      * <p>
  352.      * If constructor specifying a temporary File prefix/suffix is used then the temporary File created once the threshold is reached is returned if the
  353.      * threshold was not reached then {@code null} is returned.
  354.      * </p>
  355.      *
  356.      * @return The File for this output stream, or {@code null} if no such File exists.
  357.      */
  358.     public File getFile() {
  359.         return outputPath != null ? outputPath.toFile() : null;
  360.     }

  361.     /**
  362.      * Gets either the output Path specified in the constructor or the temporary Path created or null.
  363.      * <p>
  364.      * If the constructor specifying the file is used then it returns that same output Path, even when threshold has not been reached.
  365.      * </p>
  366.      * <p>
  367.      * If constructor specifying a temporary Path prefix/suffix is used then the temporary Path created once the threshold is reached is returned if the
  368.      * threshold was not reached then {@code null} is returned.
  369.      * </p>
  370.      *
  371.      * @return The Path for this output stream, or {@code null} if no such Path exists.
  372.      * @since 2.14.0
  373.      */
  374.     public Path getPath() {
  375.         return outputPath;
  376.     }

  377.     /**
  378.      * Gets the current output stream. This may be memory based or disk based, depending on the current state with respect to the threshold.
  379.      *
  380.      * @return The underlying output stream.
  381.      * @throws IOException if an error occurs.
  382.      * @deprecated Use {@link #getOutputStream()}.
  383.      */
  384.     @Deprecated
  385.     @Override
  386.     protected OutputStream getStream() throws IOException {
  387.         return currentOutputStream;
  388.     }

  389.     /**
  390.      * Tests whether or not the data for this output stream has been retained in memory.
  391.      *
  392.      * @return {@code true} if the data is available in memory; {@code false} otherwise.
  393.      */
  394.     public boolean isInMemory() {
  395.         return !isThresholdExceeded();
  396.     }

  397.     /**
  398.      * 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
  399.      * is being written to keep in memory, so we elect to switch to disk-based storage.
  400.      *
  401.      * @throws IOException if an error occurs.
  402.      */
  403.     @Override
  404.     protected void thresholdReached() throws IOException {
  405.         if (prefix != null) {
  406.             outputPath = Files.createTempFile(directory, prefix, suffix);
  407.         }
  408.         PathUtils.createParentDirectories(outputPath, null, PathUtils.EMPTY_FILE_ATTRIBUTE_ARRAY);
  409.         final OutputStream fos = Files.newOutputStream(outputPath);
  410.         try {
  411.             memoryOutputStream.writeTo(fos);
  412.         } catch (final IOException e) {
  413.             fos.close();
  414.             throw e;
  415.         }
  416.         currentOutputStream = fos;
  417.         memoryOutputStream = null;
  418.     }

  419.     /**
  420.      * 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
  421.      * stream is backed by buffers of {@code this} stream, avoiding memory allocation and copy, thus saving space and time.<br>
  422.      * Otherwise, the returned stream will be one that is created from the data that has been committed to disk.
  423.      *
  424.      * @return the current contents of this output stream.
  425.      * @throws IOException if this stream is not yet closed or an error occurs.
  426.      * @see org.apache.commons.io.output.ByteArrayOutputStream#toInputStream()
  427.      * @since 2.9.0
  428.      */
  429.     public InputStream toInputStream() throws IOException {
  430.         // we may only need to check if this is closed if we are working with a file
  431.         // but we should force the habit of closing whether we are working with
  432.         // a file or memory.
  433.         if (!closed) {
  434.             throw new IOException("Stream not closed");
  435.         }
  436.         if (isInMemory()) {
  437.             return memoryOutputStream.toInputStream();
  438.         }
  439.         return Files.newInputStream(outputPath);
  440.     }

  441.     /**
  442.      * Writes the data from this output stream to the specified output stream, after it has been closed.
  443.      *
  444.      * @param outputStream output stream to write to.
  445.      * @throws NullPointerException if the OutputStream is {@code null}.
  446.      * @throws IOException          if this stream is not yet closed or an error occurs.
  447.      */
  448.     public void writeTo(final OutputStream outputStream) throws IOException {
  449.         // we may only need to check if this is closed if we are working with a file
  450.         // but we should force the habit of closing whether we are working with
  451.         // a file or memory.
  452.         if (!closed) {
  453.             throw new IOException("Stream not closed");
  454.         }
  455.         if (isInMemory()) {
  456.             memoryOutputStream.writeTo(outputStream);
  457.         } else {
  458.             Files.copy(outputPath, outputStream);
  459.         }
  460.     }
  461. }