View Javadoc
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   */
18  package org.codehaus.plexus.archiver.zip;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.io.SequenceInputStream;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  
28  import org.apache.commons.io.output.ThresholdingOutputStream;
29  import org.codehaus.plexus.archiver.util.Streams;
30  
31  /**
32   * Offloads to disk when a given memory consumption has been reached
33   */
34  class OffloadingOutputStream extends ThresholdingOutputStream {
35  
36      // ----------------------------------------------------------- Data members
37  
38      /**
39       * The output stream to which data will be written prior to the threshold
40       * being reached.
41       */
42      private ByteArrayOutputStream memoryOutputStream;
43  
44      /**
45       * The output stream to which data will be written at any given time. This
46       * will always be one of <code>memoryOutputStream</code> or
47       * <code>diskOutputStream</code>.
48       */
49      private OutputStream currentOutputStream;
50  
51      /**
52       * The path to which output will be directed if the threshold is exceeded.
53       */
54      private Path outputPath = null;
55  
56      /**
57       * The temporary file prefix.
58       */
59      private final String prefix;
60  
61      /**
62       * The temporary file suffix.
63       */
64      private final String suffix;
65  
66      // ----------------------------------------------------------- Constructors
67  
68      /**
69       * Constructs an instance of this class which will trigger an event at the
70       * specified threshold, and save data to a temporary file beyond that point.
71       *
72       * @param threshold The number of bytes at which to trigger an event.
73       * @param prefix    Prefix to use for the temporary file.
74       * @param suffix    Suffix to use for the temporary file.
75       * @since 1.4
76       */
77      public OffloadingOutputStream(int threshold, String prefix, String suffix) {
78          super(threshold);
79  
80          if (prefix == null) {
81              throw new IllegalArgumentException("Temporary file prefix is missing");
82          }
83  
84          memoryOutputStream = new ByteArrayOutputStream(threshold / 10);
85          currentOutputStream = memoryOutputStream;
86          this.prefix = prefix;
87          this.suffix = suffix;
88      }
89  
90      // --------------------------------------- ThresholdingOutputStream methods
91  
92      /**
93       * Returns the current output stream. This may be memory based or disk
94       * based, depending on the current state with respect to the threshold.
95       *
96       * @return The underlying output stream.
97       * @throws java.io.IOException if an error occurs.
98       */
99      @Override
100     @Deprecated
101     protected OutputStream getStream() throws IOException {
102         return currentOutputStream;
103     }
104 
105     /**
106      * Switches the underlying output stream from a memory based stream to one
107      * that is backed by disk. This is the point at which we realise that too
108      * much data is being written to keep in memory, so we elect to switch to
109      * disk-based storage.
110      *
111      * @throws java.io.IOException if an error occurs.
112      */
113     @Override
114     protected void thresholdReached() throws IOException {
115         outputPath = Files.createTempFile(prefix, suffix);
116         currentOutputStream = Streams.fileOutputStream(outputPath);
117     }
118 
119     public InputStream getInputStream() throws IOException {
120 
121         InputStream memoryAsInput = memoryOutputStream.toInputStream();
122         if (outputPath == null) {
123             return memoryAsInput;
124         }
125         return new SequenceInputStream(memoryAsInput, Files.newInputStream(outputPath));
126     }
127 
128     // --------------------------------------------------------- Public methods
129 
130     /**
131      * Returns the data for this output stream as an array of bytes, assuming
132      * that the data has been retained in memory. If the data was written to
133      * disk, this method returns <code>null</code>.
134      *
135      * @return The data for this output stream, or <code>null</code> if no such
136      * data is available.
137      */
138     public byte[] getData() {
139         if (memoryOutputStream != null) {
140             return memoryOutputStream.toByteArray();
141         }
142         return null;
143     }
144 
145     /**
146      * Returns either the output file specified in the constructor or
147      * the temporary file created or null.
148      * <p>
149      * If the constructor specifying the file is used then it returns that
150      * same output file, even when threshold has not been reached.
151      * <p>
152      * If constructor specifying a temporary file prefix/suffix is used
153      * then the temporary file created once the threshold is reached is returned
154      * If the threshold was not reached then <code>null</code> is returned.
155      *
156      * @return The file for this output stream, or <code>null</code> if no such
157      * file exists.
158      */
159     public File getFile() {
160         return outputPath != null ? outputPath.toFile() : null;
161     }
162 
163     /**
164      * Closes underlying output stream.
165      *
166      * @throws java.io.IOException if an error occurs.
167      */
168     @Override
169     public void close() throws IOException {
170         super.close();
171         currentOutputStream.close();
172     }
173 }