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