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