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 */
017 package org.apache.camel.converter.stream;
018
019 import java.io.ByteArrayInputStream;
020 import java.io.ByteArrayOutputStream;
021 import java.io.File;
022 import java.io.FileNotFoundException;
023 import java.io.FileOutputStream;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.OutputStream;
027
028 import org.apache.camel.Exchange;
029 import org.apache.camel.StreamCache;
030 import org.apache.camel.support.SynchronizationAdapter;
031 import org.apache.camel.util.FileUtil;
032 import org.apache.camel.util.IOHelper;
033 import org.slf4j.Logger;
034 import org.slf4j.LoggerFactory;
035
036 /**
037 * This output stream will store the content into a File if the stream context size is exceed the
038 * THRESHOLD which's default value is 64K. The temp file will store in the temp directory, you
039 * can configure it by setting the TEMP_DIR property. If you don't set the TEMP_DIR property,
040 * it will choose the directory which is set by the system property of "java.io.tmpdir".
041 * You can get a cached input stream of this stream. The temp file which is created with this
042 * output stream will be deleted when you close this output stream or the all cached
043 * fileInputStream is closed after the exchange is completed.
044 */
045 public class CachedOutputStream extends OutputStream {
046 public static final String THRESHOLD = "CamelCachedOutputStreamThreshold";
047 public static final String TEMP_DIR = "CamelCachedOutputStreamOutputDirectory";
048 private static final transient Logger LOG = LoggerFactory.getLogger(CachedOutputStream.class);
049
050 private OutputStream currentStream = new ByteArrayOutputStream(2048);
051 private boolean inMemory = true;
052 private int totalLength;
053 private File tempFile;
054 private FileInputStreamCache fileInputStreamCache;
055
056 private long threshold = 64 * 1024;
057 private File outputDir;
058
059 public CachedOutputStream(Exchange exchange) {
060 this(exchange, true);
061 }
062
063 public CachedOutputStream(Exchange exchange, boolean closedOnCompletion) {
064 String hold = exchange.getContext().getProperties().get(THRESHOLD);
065 String dir = exchange.getContext().getProperties().get(TEMP_DIR);
066 if (hold != null) {
067 this.threshold = exchange.getContext().getTypeConverter().convertTo(Long.class, hold);
068 }
069 if (dir != null) {
070 this.outputDir = exchange.getContext().getTypeConverter().convertTo(File.class, dir);
071 }
072
073 if (closedOnCompletion) {
074 // add on completion so we can cleanup after the exchange is done such as deleting temporary files
075 exchange.addOnCompletion(new SynchronizationAdapter() {
076 @Override
077 public void onDone(Exchange exchange) {
078 try {
079 if (fileInputStreamCache != null) {
080 fileInputStreamCache.close();
081 }
082 close();
083 } catch (Exception e) {
084 LOG.warn("Error deleting temporary cache file: " + tempFile, e);
085 }
086 }
087
088 @Override
089 public String toString() {
090 return "OnCompletion[CachedOutputStream]";
091 }
092 });
093 }
094 }
095
096 public void flush() throws IOException {
097 currentStream.flush();
098 }
099
100 public void close() throws IOException {
101 currentStream.close();
102 cleanUpTempFile();
103 }
104
105 public boolean equals(Object obj) {
106 return currentStream.equals(obj);
107 }
108
109 public int hashCode() {
110 return currentStream.hashCode();
111 }
112
113 public String toString() {
114 return "CachedOutputStream[size: " + totalLength + "]";
115 }
116
117 public void write(byte[] b, int off, int len) throws IOException {
118 this.totalLength += len;
119 if (threshold > 0 && inMemory && totalLength > threshold && currentStream instanceof ByteArrayOutputStream) {
120 pageToFileStream();
121 }
122 currentStream.write(b, off, len);
123 }
124
125 public void write(byte[] b) throws IOException {
126 this.totalLength += b.length;
127 if (threshold > 0 && inMemory && totalLength > threshold && currentStream instanceof ByteArrayOutputStream) {
128 pageToFileStream();
129 }
130 currentStream.write(b);
131 }
132
133 public void write(int b) throws IOException {
134 this.totalLength++;
135 if (threshold > 0 && inMemory && totalLength > threshold && currentStream instanceof ByteArrayOutputStream) {
136 pageToFileStream();
137 }
138 currentStream.write(b);
139 }
140
141 public InputStream getInputStream() throws IOException {
142 flush();
143
144 if (inMemory) {
145 if (currentStream instanceof ByteArrayOutputStream) {
146 return new ByteArrayInputStream(((ByteArrayOutputStream) currentStream).toByteArray());
147 } else {
148 throw new IllegalStateException("CurrentStream should be an instance of ByteArrayOutputStream but is: " + currentStream.getClass().getName());
149 }
150 } else {
151 try {
152 if (fileInputStreamCache == null) {
153 fileInputStreamCache = new FileInputStreamCache(tempFile);
154 }
155 return fileInputStreamCache;
156 } catch (FileNotFoundException e) {
157 throw new IOException("Cached file " + tempFile + " not found", e);
158 }
159 }
160 }
161
162 public InputStream getWrappedInputStream() throws IOException {
163 // The WrappedInputStream will close the CachedOuputStream when it is closed
164 return new WrappedInputStream(this, getInputStream());
165 }
166
167
168 public StreamCache getStreamCache() throws IOException {
169 flush();
170
171 if (inMemory) {
172 if (currentStream instanceof ByteArrayOutputStream) {
173 return new InputStreamCache(((ByteArrayOutputStream) currentStream).toByteArray());
174 } else {
175 throw new IllegalStateException("CurrentStream should be an instance of ByteArrayOutputStream but is: " + currentStream.getClass().getName());
176 }
177 } else {
178 try {
179 if (fileInputStreamCache == null) {
180 fileInputStreamCache = new FileInputStreamCache(tempFile);
181 }
182 return fileInputStreamCache;
183 } catch (FileNotFoundException e) {
184 throw new IOException("Cached file " + tempFile + " not found", e);
185 }
186 }
187 }
188
189 private void cleanUpTempFile() {
190 // cleanup temporary file
191 if (tempFile != null) {
192 FileUtil.deleteFile(tempFile);
193 tempFile = null;
194 }
195 }
196
197 private void pageToFileStream() throws IOException {
198 flush();
199
200 ByteArrayOutputStream bout = (ByteArrayOutputStream)currentStream;
201 if (outputDir == null) {
202 tempFile = FileUtil.createTempFile("cos", ".tmp");
203 } else {
204 tempFile = FileUtil.createTempFile("cos", ".tmp", outputDir);
205 }
206
207 LOG.trace("Creating temporary stream cache file: {}", tempFile);
208
209 try {
210 currentStream = IOHelper.buffered(new FileOutputStream(tempFile));
211 bout.writeTo(currentStream);
212 } finally {
213 // ensure flag is flipped to file based
214 inMemory = false;
215 }
216 }
217
218 // This class will close the CachedOutputStream when it is closed
219 private static class WrappedInputStream extends InputStream {
220 private CachedOutputStream cachedOutputStream;
221 private InputStream inputStream;
222
223 WrappedInputStream(CachedOutputStream cos, InputStream is) {
224 cachedOutputStream = cos;
225 inputStream = is;
226 }
227
228 @Override
229 public int read() throws IOException {
230 return inputStream.read();
231 }
232
233 @Override
234 public int available() throws IOException {
235 return inputStream.available();
236 }
237
238 @Override
239 public void reset() throws IOException {
240 inputStream.reset();
241 }
242
243 @Override
244 public void close() throws IOException {
245 inputStream.close();
246 cachedOutputStream.close();
247 }
248 }
249
250 }