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.wicket.util.io;
018
019import java.io.File;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023
024import org.apache.wicket.util.lang.Args;
025
026
027/**
028 * <p>
029 * An output stream which will retain data in memory until a specified threshold is reached, and
030 * only then commit it to disk. If the stream is closed before the threshold is reached, the data
031 * will not be written to disk at all.
032 * </p>
033 * <p>
034 * This class originated in FileUpload processing. In this use case, you do not know in advance the
035 * size of the file being uploaded. If the file is small you want to store it in memory (for speed),
036 * but if the file is large you want to store it to file (to avoid memory issues).
037 * </p>
038 * 
039 * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
040 */
041public class DeferredFileOutputStream extends ThresholdingOutputStream
042{
043        /**
044         * The output stream to which data will be written at any given time. This will always be one of
045         * <code>memoryOutputStream</code> or <code>diskOutputStream</code>.
046         */
047        private OutputStream currentOutputStream;
048
049        /**
050         * The output stream to which data will be written prior to the threshold being reached.
051         */
052        private ByteArrayOutputStream memoryOutputStream;
053
054        /**
055         * The file to which output will be directed if the threshold is exceeded.
056         */
057        private File outputFile;
058
059        private final FileFactory fileFactory;
060
061        /**
062         * Constructs an instance of this class which will trigger an event at the specified threshold,
063         * and save data to a file beyond that point.
064         * 
065         * @param threshold
066         *            The number of bytes at which to trigger an event.
067         * @param outputFile
068         *            The file to which data is saved beyond the threshold.
069         */
070        public DeferredFileOutputStream(final int threshold, final File outputFile)
071        {
072                super(threshold);
073
074                this.outputFile = Args.notNull(outputFile, "outputFile");
075                fileFactory = null;
076
077                memoryOutputStream = new ByteArrayOutputStream();
078                currentOutputStream = memoryOutputStream;
079        }
080
081        /**
082         * Constructs an instance of this class which will trigger an event at the specified threshold,
083         * and save data to a file beyond that point.
084         * 
085         * @param threshold
086         *            The number of bytes at which to trigger an event.
087         * @param fileFactory
088         *            The FileFactory to create the file.
089         */
090        public DeferredFileOutputStream(final int threshold, final FileFactory fileFactory)
091        {
092                super(threshold);
093                this.fileFactory = Args.notNull(fileFactory, "fileFactory");
094
095                memoryOutputStream = new ByteArrayOutputStream();
096                currentOutputStream = memoryOutputStream;
097        }
098
099        /**
100         * Returns the data for this output stream as an array of bytes, assuming that the data has been
101         * retained in memory. If the data was written to disk, this method returns <code>null</code>.
102         * 
103         * @return The data for this output stream, or <code>null</code> if no such data is available.
104         */
105        public byte[] getData()
106        {
107                if (memoryOutputStream != null)
108                {
109                        return memoryOutputStream.toByteArray();
110                }
111                return null;
112        }
113
114        /**
115         * Returns the data for this output stream as a <code>File</code>, assuming that the data was
116         * written to disk. If the data was retained in memory, this method returns <code>null</code>.
117         * 
118         * @return The file for this output stream, or <code>null</code> if no such file exists.
119         */
120        public File getFile()
121        {
122                return outputFile;
123        }
124
125        /**
126         * Determines whether or not the data for this output stream has been retained in memory.
127         * 
128         * @return <code>true</code> if the data is available in memory; <code>false</code> otherwise.
129         */
130        public boolean isInMemory()
131        {
132                return (!isThresholdExceeded());
133        }
134
135        /**
136         * Returns the current output stream. This may be memory based or disk based, depending on the
137         * current state with respect to the threshold.
138         * 
139         * @return The underlying output stream.
140         * @exception IOException
141         *                if an error occurs.
142         */
143        @Override
144        protected OutputStream getStream() throws IOException
145        {
146                return currentOutputStream;
147        }
148
149        /**
150         * Switches the underlying output stream from a memory based stream to one that is backed by
151         * disk. This is the point at which we realize that too much data is being written to keep in
152         * memory, so we elect to switch to disk-based storage.
153         * 
154         * @exception IOException
155         *                if an error occurs.
156         */
157        @Override
158        protected void thresholdReached() throws IOException
159        {
160                byte[] data = memoryOutputStream.toByteArray();
161                if (outputFile == null)
162                {
163                        outputFile = fileFactory.createFile();
164                }
165                FileOutputStream fos = new FileOutputStream(outputFile);
166                fos.write(data);
167                currentOutputStream = fos;
168                memoryOutputStream = null;
169        }
170
171        /**
172         * The file factory for this deferred file output stream.
173         * 
174         * @author jcompagner
175         */
176        public interface FileFactory
177        {
178                /**
179                 * @return the file to use for disk cache
180                 */
181                File createFile();
182        }
183}