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}