001: package org.apache.synapse.util;
002:
003: import java.io.File;
004: import java.io.FileInputStream;
005: import java.io.FileOutputStream;
006: import java.io.IOException;
007: import java.io.InputStream;
008: import java.io.OutputStream;
009:
010: import org.apache.commons.logging.Log;
011: import org.apache.commons.logging.LogFactory;
012:
013: /**
014: * Class representing some temporary data in the form of a byte stream.
015: * <p>
016: * Data is stored by writing to the output stream obtained using
017: * {@link #getOutputStream()}. It can then be read back using
018: * the input stream obtained from {@link #getInputStream()}.
019: * The data is first stored into a fixed size buffer. Once this
020: * buffer overflows, it is transferred to a temporary file. The buffer
021: * is divided into a given number of fixed size chunks that are allocated
022: * on demand. Since a temporary file may be created it is mandatory to
023: * call {@link #release()} to discard the temporary data.
024: */
025: public class TemporaryData {
026: private static final Log log = LogFactory
027: .getLog(TemporaryData.class);
028:
029: class OutputStreamImpl extends OutputStream {
030: private FileOutputStream fileOutputStream;
031:
032: public void write(byte[] b, int off, int len)
033: throws IOException {
034: if (fileOutputStream != null) {
035: fileOutputStream.write(b, off, len);
036: } else if (len > (chunks.length - chunkIndex) * chunkSize
037: - chunkOffset) {
038: // The buffer will overflow. Switch to a temporary file.
039: temporaryFile = File.createTempFile(tempPrefix,
040: tempSuffix);
041: if (log.isDebugEnabled()) {
042: log.debug("Using temporary file " + temporaryFile);
043: }
044: temporaryFile.deleteOnExit();
045: fileOutputStream = new FileOutputStream(temporaryFile);
046: // Write the buffer to the temporary file.
047: for (int i = 0; i < chunkIndex; i++) {
048: fileOutputStream.write(chunks[i]);
049: }
050: if (chunkOffset > 0) {
051: fileOutputStream.write(chunks[chunkIndex], 0,
052: chunkOffset);
053: }
054: // Release references to the buffer so that it can be garbage collected.
055: chunks = null;
056: // Finally, write the new data to the temporary file.
057: fileOutputStream.write(b, off, len);
058: } else {
059: // The data will fit into the buffer.
060: while (len > 0) {
061: byte[] chunk;
062: if (chunkOffset == 0) {
063: // We will write the first byte to the current chunk. Allocate it.
064: chunk = new byte[chunkSize];
065: chunks[chunkIndex] = chunk;
066: } else {
067: // The chunk has already been allocated.
068: chunk = chunks[chunkIndex];
069: }
070: // Determine number of bytes that can be copied to the current chunk.
071: int c = Math.min(len, chunkSize - chunkOffset);
072: // Copy data to the chunk.
073: System.arraycopy(b, off, chunk, chunkOffset, c);
074: // Update variables.
075: len -= c;
076: off += c;
077: chunkOffset += c;
078: if (chunkOffset == chunkSize) {
079: chunkIndex++;
080: chunkOffset = 0;
081: }
082: }
083: }
084: }
085:
086: public void write(byte[] b) throws IOException {
087: write(b, 0, b.length);
088: }
089:
090: public void write(int b) throws IOException {
091: write(new byte[] { (byte) b }, 0, 1);
092: }
093:
094: public void flush() throws IOException {
095: if (fileOutputStream != null) {
096: fileOutputStream.flush();
097: }
098: }
099:
100: public void close() throws IOException {
101: if (fileOutputStream != null) {
102: fileOutputStream.close();
103: }
104: }
105: }
106:
107: class InputStreamImpl extends InputStream {
108: private int currentChunkIndex;
109: private int currentChunkOffset;
110: private int markChunkIndex;
111: private int markChunkOffset;
112:
113: public int available() throws IOException {
114: return (chunkIndex - currentChunkIndex) * chunkSize
115: + chunkOffset - currentChunkOffset;
116: }
117:
118: public int read(byte[] b, int off, int len) throws IOException {
119: if (len == 0) {
120: return 0;
121: }
122: int read = 0;
123: while (len > 0
124: && !(currentChunkIndex == chunkIndex && currentChunkOffset == chunkOffset)) {
125: int c;
126: if (currentChunkIndex == chunkIndex) {
127: // The current chunk is the last one => take into account the offset
128: c = Math.min(len, chunkOffset - currentChunkOffset);
129: } else {
130: c = Math.min(len, chunkSize - currentChunkOffset);
131: }
132: // Copy the data.
133: System.arraycopy(chunks[currentChunkIndex],
134: currentChunkOffset, b, off, c);
135: // Update variables
136: len -= c;
137: off += c;
138: currentChunkOffset += c;
139: read += c;
140: if (currentChunkOffset == chunkSize) {
141: currentChunkIndex++;
142: currentChunkOffset = 0;
143: }
144: }
145: if (read == 0) {
146: // We didn't read anything (and the len argument was not 0) => we reached the end of the buffer.
147: return -1;
148: } else {
149: return read;
150: }
151: }
152:
153: public int read(byte[] b) throws IOException {
154: return read(b, 0, b.length);
155: }
156:
157: public int read() throws IOException {
158: byte[] b = new byte[1];
159: return read(b) == -1 ? -1 : (int) b[0] & 0xFF;
160: }
161:
162: public boolean markSupported() {
163: return true;
164: }
165:
166: public void mark(int readlimit) {
167: markChunkIndex = currentChunkIndex;
168: markChunkOffset = currentChunkOffset;
169: }
170:
171: public void reset() throws IOException {
172: currentChunkIndex = markChunkIndex;
173: currentChunkOffset = markChunkOffset;
174: }
175:
176: public long skip(long n) throws IOException {
177: int available = available();
178: int c = n < available ? (int) n : available;
179: int newOffset = currentChunkOffset + c;
180: int chunkDelta = newOffset / chunkSize;
181: currentChunkIndex += chunkDelta;
182: currentChunkOffset = newOffset - (chunkDelta * chunkSize);
183: return c;
184: }
185:
186: public void close() throws IOException {
187: }
188: }
189:
190: /**
191: * Size of the chunks that will be allocated in the buffer.
192: */
193: final int chunkSize;
194:
195: /**
196: * The prefix to be used in generating the name of the temporary file.
197: */
198: final String tempPrefix;
199:
200: /**
201: * The suffix to be used in generating the name of the temporary file.
202: */
203: final String tempSuffix;
204:
205: /**
206: * Array of <code>byte[]</code> representing the chunks of the buffer.
207: * A chunk is only allocated when the first byte is written to it.
208: * This attribute is set to <code>null</code> when the buffer overflows and
209: * is written out to a temporary file.
210: */
211: byte[][] chunks;
212:
213: /**
214: * Index of the chunk the next byte will be written to.
215: */
216: int chunkIndex;
217:
218: /**
219: * Offset into the chunk where the next byte will be written.
220: */
221: int chunkOffset;
222:
223: /**
224: * The handle of the temporary file. This is only set when the memory buffer
225: * overflows and is written out to a temporary file.
226: */
227: File temporaryFile;
228:
229: public TemporaryData(int numberOfChunks, int chunkSize,
230: String tempPrefix, String tempSuffix) {
231: this .chunkSize = chunkSize;
232: this .tempPrefix = tempPrefix;
233: this .tempSuffix = tempSuffix;
234: chunks = new byte[numberOfChunks][];
235: }
236:
237: public OutputStream getOutputStream() {
238: return new OutputStreamImpl();
239: }
240:
241: public InputStream getInputStream() throws IOException {
242: if (temporaryFile != null) {
243: return new FileInputStream(temporaryFile);
244: } else {
245: return new InputStreamImpl();
246: }
247: }
248:
249: public void release() {
250: if (temporaryFile != null) {
251: temporaryFile.delete();
252: }
253: }
254:
255: protected void finalize() throws Throwable {
256: if (temporaryFile != null) {
257: log.warn("Cleaning up unreleased temporary file "
258: + temporaryFile);
259: temporaryFile.delete();
260: }
261: }
262: }
|