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}