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.camel.converter.stream; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023 024import org.apache.camel.Exchange; 025import org.apache.camel.StreamCache; 026import org.apache.camel.converter.stream.FileInputStreamCache.TempFileManager; 027import org.apache.camel.spi.StreamCachingStrategy; 028 029/** 030 * This output stream will store the content into a File if the stream context size is exceed the 031 * THRESHOLD value. The default THRESHOLD value is {@link StreamCache#DEFAULT_SPOOL_THRESHOLD} bytes . 032 * <p/> 033 * The temp file will store in the temp directory, you can configure it by setting the TEMP_DIR property. 034 * If you don't set the TEMP_DIR property, it will choose the directory which is set by the 035 * system property of "java.io.tmpdir". 036 * <p/> 037 * You can get a cached input stream of this stream. The temp file which is created with this 038 * output stream will be deleted when you close this output stream or the cached 039 * fileInputStream(s) is/are closed after all the exchanges using the temp file are completed. 040 */ 041public class CachedOutputStream extends OutputStream { 042 @Deprecated 043 public static final String THRESHOLD = "CamelCachedOutputStreamThreshold"; 044 @Deprecated 045 public static final String BUFFER_SIZE = "CamelCachedOutputStreamBufferSize"; 046 @Deprecated 047 public static final String TEMP_DIR = "CamelCachedOutputStreamOutputDirectory"; 048 @Deprecated 049 public static final String CIPHER_TRANSFORMATION = "CamelCachedOutputStreamCipherTransformation"; 050 051 private final StreamCachingStrategy strategy; 052 private OutputStream currentStream; 053 private boolean inMemory = true; 054 private int totalLength; 055 private final TempFileManager tempFileManager; 056 private final boolean closedOnCompletion; 057 058 public CachedOutputStream(Exchange exchange) { 059 this(exchange, true); 060 } 061 062 public CachedOutputStream(Exchange exchange, final boolean closedOnCompletion) { 063 this.closedOnCompletion = closedOnCompletion; 064 tempFileManager = new TempFileManager(closedOnCompletion); 065 tempFileManager.addExchange(exchange); 066 this.strategy = exchange.getContext().getStreamCachingStrategy(); 067 currentStream = new CachedByteArrayOutputStream(strategy.getBufferSize()); 068 } 069 070 public void flush() throws IOException { 071 currentStream.flush(); 072 } 073 074 public void close() throws IOException { 075 currentStream.close(); 076 // need to clean up the temp file this time 077 if (!closedOnCompletion) { 078 tempFileManager.closeFileInputStreams(); 079 tempFileManager.cleanUpTempFile(); 080 } 081 } 082 083 public boolean equals(Object obj) { 084 return currentStream.equals(obj); 085 } 086 087 public int hashCode() { 088 return currentStream.hashCode(); 089 } 090 091 public OutputStream getCurrentStream() { 092 return currentStream; 093 } 094 095 public String toString() { 096 return "CachedOutputStream[size: " + totalLength + "]"; 097 } 098 099 public void write(byte[] b, int off, int len) throws IOException { 100 this.totalLength += len; 101 if (inMemory && currentStream instanceof ByteArrayOutputStream && strategy.shouldSpoolCache(totalLength)) { 102 pageToFileStream(); 103 } 104 currentStream.write(b, off, len); 105 } 106 107 public void write(byte[] b) throws IOException { 108 this.totalLength += b.length; 109 if (inMemory && currentStream instanceof ByteArrayOutputStream && strategy.shouldSpoolCache(totalLength)) { 110 pageToFileStream(); 111 } 112 currentStream.write(b); 113 } 114 115 public void write(int b) throws IOException { 116 this.totalLength++; 117 if (inMemory && currentStream instanceof ByteArrayOutputStream && strategy.shouldSpoolCache(totalLength)) { 118 pageToFileStream(); 119 } 120 currentStream.write(b); 121 } 122 123 public InputStream getInputStream() throws IOException { 124 return (InputStream)newStreamCache(); 125 } 126 127 public InputStream getWrappedInputStream() throws IOException { 128 // The WrappedInputStream will close the CachedOutputStream when it is closed 129 return new WrappedInputStream(this, (InputStream)newStreamCache()); 130 } 131 132 /** 133 * @deprecated use {@link #newStreamCache()} 134 */ 135 @Deprecated 136 public StreamCache getStreamCache() throws IOException { 137 return newStreamCache(); 138 } 139 140 /** 141 * Creates a new {@link StreamCache} from the data cached in this {@link OutputStream}. 142 */ 143 public StreamCache newStreamCache() throws IOException { 144 flush(); 145 146 if (inMemory) { 147 if (currentStream instanceof CachedByteArrayOutputStream) { 148 return ((CachedByteArrayOutputStream) currentStream).newInputStreamCache(); 149 } else { 150 throw new IllegalStateException("CurrentStream should be an instance of CachedByteArrayOutputStream but is: " + currentStream.getClass().getName()); 151 } 152 } else { 153 return tempFileManager.newStreamCache(); 154 } 155 } 156 157 158 private void pageToFileStream() throws IOException { 159 flush(); 160 ByteArrayOutputStream bout = (ByteArrayOutputStream)currentStream; 161 try { 162 // creates an tmp file and a file output stream 163 currentStream = tempFileManager.createOutputStream(strategy); 164 bout.writeTo(currentStream); 165 } finally { 166 // ensure flag is flipped to file based 167 inMemory = false; 168 } 169 } 170 171 /** 172 * @deprecated use {@link #getStrategyBufferSize()} 173 */ 174 @Deprecated 175 public int getBufferSize() { 176 return getStrategyBufferSize(); 177 } 178 179 public int getStrategyBufferSize() { 180 return strategy.getBufferSize(); 181 } 182 183 // This class will close the CachedOutputStream when it is closed 184 private static class WrappedInputStream extends InputStream { 185 private CachedOutputStream cachedOutputStream; 186 private InputStream inputStream; 187 188 WrappedInputStream(CachedOutputStream cos, InputStream is) { 189 cachedOutputStream = cos; 190 inputStream = is; 191 } 192 193 @Override 194 public int read() throws IOException { 195 return inputStream.read(); 196 } 197 198 @Override 199 public int available() throws IOException { 200 return inputStream.available(); 201 } 202 203 @Override 204 public void reset() throws IOException { 205 inputStream.reset(); 206 } 207 208 @Override 209 public void close() throws IOException { 210 inputStream.close(); 211 cachedOutputStream.close(); 212 } 213 } 214 215}