blob: 3ebdd5ba80adf28fc57ed70bc096b57654b883f1 [file] [log] [blame]
/*
* Copyright 2004,2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.axis2.saaj.util;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.util.LinkedList;
import java.util.List;
/**
*
*/
public class SAAJDataSource implements javax.activation.DataSource {
/**
* The content type. This defaults to
* <code>application/octet-stream</code>.
*/
protected String contentType = "application/octet-stream";
/**
* The incoming source stream.
*/
private InputStream ss;
/**
* Field MIN_MEMORY_DISK_CACHED
*/
public static final int MIN_MEMORY_DISK_CACHED = -1;
/**
* Field MAX_MEMORY_DISK_CACHED
*/
public static final int MAX_MEMORY_DISK_CACHED = 16 * 1024;
/**
* Field maxCached
*/
protected int maxCached = MAX_MEMORY_DISK_CACHED; // max in memory cached. Default.
/**
* Field diskCacheFile
*/
protected java.io.File diskCacheFile = null;
// A list of open input Streams.
/**
* Field readers
*/
protected java.util.WeakHashMap readers = new java.util.WeakHashMap();
/**
* Flag to show if the resources behind this have been deleted.
*/
protected boolean deleted;
/**
* Field READ_CHUNK_SZ
*/
public static final int READ_CHUNK_SZ = 32 * 1024;
/**
* The linked list to hold the in memory buffers.
*/
protected java.util.LinkedList memorybuflist = new java.util.LinkedList();
/**
* Hold the last memory buffer.
*/
protected byte[] currentMemoryBuf = null;
/**
* The number of bytes written to the above buffer.
*/
protected int currentMemoryBufSz;
/**
* The total size in bytes in this data source.
*/
protected long totalsz;
/**
* This is the cached disk stream.
*/
protected java.io.BufferedOutputStream cachediskstream;
/**
* If true the source input stream is now closed.
*/
protected boolean closed = false;
/**
* Constructor SAAJDataSource.
*/
protected SAAJDataSource() {
}
/**
* Create a new boundary stream.
*
* @param ss is the source input stream that is used to create this data source.
* @param maxCached This is the max memory that is to be used to cache the data.
* @param contentType the mime type for this data stream.
* by buffering you can some effiency in searching.
* @throws java.io.IOException
*/
public SAAJDataSource(InputStream ss, int maxCached, String contentType)
throws java.io.IOException {
this(ss, maxCached, contentType, false);
}
/**
* Create a new boundary stream.
*
* @param ss is the source input stream that is used to create this data source.
* @param maxCached This is the max memory that is to be used to cache the data.
* @param contentType the mime type for this data stream.
* by buffering you can some effiency in searching.
* @param readall if true will read in the whole source.
* @throws java.io.IOException
*/
public SAAJDataSource(InputStream ss,
int maxCached,
String contentType, boolean readall) throws java.io.IOException {
if (ss instanceof BufferedInputStream) {
this.ss = ss;
} else {
this.ss = new BufferedInputStream(ss);
}
this.maxCached = maxCached;
if ((null != contentType) && (contentType.length() != 0)) {
this.contentType = contentType;
}
if (maxCached < MIN_MEMORY_DISK_CACHED) {
throw new IllegalArgumentException("badMaxCached " + maxCached);
}
// for now read all in to disk.
if (readall) {
byte[] readbuffer = new byte[READ_CHUNK_SZ];
int read = 0;
do {
read = ss.read(readbuffer);
if (read > 0) {
write(readbuffer, read);
}
} while (read > -1);
close();
}
}
/**
* This method is a low level write.
* Close the stream.
*
* @throws java.io.IOException
*/
protected synchronized void close() throws java.io.IOException {
if (!closed) {
closed = true; // Markit as closed.
if (null != cachediskstream) { // close the disk cache.
cachediskstream.close();
cachediskstream = null;
}
if (null != memorybuflist) { // There is a memory buffer.
if (currentMemoryBufSz > 0) {
byte[] tmp =
new byte[currentMemoryBufSz]; // Get the last buffer and make it the sizeof the actual data.
System.arraycopy(currentMemoryBuf, 0, tmp, 0,
currentMemoryBufSz);
memorybuflist.set(memorybuflist.size() - 1,
tmp); // Now replace the last buffer with this size.
}
currentMemoryBuf = null; // No need for this anymore.
}
}
}
/**
* Routine to flush data to disk if is in memory.
*
* @throws java.io.IOException
* @throws java.io.FileNotFoundException
*/
protected void flushToDisk() throws IOException, FileNotFoundException {
LinkedList ml = memorybuflist;
if (ml != null) {
if (null == cachediskstream) { // Need to create a disk cache
try {
/* MessageContext mc = MessageContext.getCurrentContext();
String attdir = (mc == null)
? null
: mc.getStrProp(
MessageContext.ATTACHMENTS_DIR);*/
String attdir = "temp";
diskCacheFile = java.io.File.createTempFile("Axis", ".att",
(attdir == null)
? null
: new File(
attdir));
cachediskstream = new BufferedOutputStream(new FileOutputStream(diskCacheFile));
int listsz = ml.size();
// Write out the entire memory held store to disk.
for (java.util.Iterator it = ml.iterator();
it.hasNext();) {
byte[] rbuf = (byte[]) it.next();
int bwrite = (listsz-- == 0)
? currentMemoryBufSz
: rbuf.length;
cachediskstream.write(rbuf, 0, bwrite);
if (closed) {
cachediskstream.close();
cachediskstream = null;
}
}
memorybuflist = null;
} catch (java.lang.SecurityException se) {
diskCacheFile = null;
cachediskstream = null;
maxCached = java.lang.Integer.MAX_VALUE;
}
}
}
}
/**
* Write bytes to the stream.
*
* @param data all bytes of this array are written to the stream
* @throws java.io.IOException if there was a problem writing the data
*/
protected void write(byte[] data) throws java.io.IOException {
write(data, data.length);
}
/**
* This method is a low level write.
* Note it is designed to in the future to allow streaming to both memory
* AND to disk simultaneously.
*
* @param data
* @param length
* @throws java.io.IOException
*/
protected synchronized void write(byte[] data, int length) throws java.io.IOException {
if (closed) {
throw new java.io.IOException("streamClosed");
}
int byteswritten = 0;
if ((null != memorybuflist)
&& (totalsz + length > maxCached)) { // Cache to disk.
if (null == cachediskstream) { // Need to create a disk cache
flushToDisk();
}
}
if (memorybuflist != null) { // Can write to memory.
do {
if (null == currentMemoryBuf) {
currentMemoryBuf = new byte[READ_CHUNK_SZ];
currentMemoryBufSz = 0;
memorybuflist.add(currentMemoryBuf);
}
// bytes to write is the min. between the remaining bytes and what is left in this buffer.
int bytes2write = Math.min((length - byteswritten),
(currentMemoryBuf.length
- currentMemoryBufSz));
// copy the data.
System.arraycopy(data, byteswritten, currentMemoryBuf,
currentMemoryBufSz, bytes2write);
byteswritten += bytes2write;
currentMemoryBufSz += bytes2write;
if (byteswritten
< length) { // only get more if we really need it.
currentMemoryBuf = new byte[READ_CHUNK_SZ];
currentMemoryBufSz = 0;
memorybuflist.add(currentMemoryBuf); // add it to the chain.
}
} while (byteswritten < length);
}
if (null != cachediskstream) { // Write to the out going stream.
cachediskstream.write(data, 0, length);
}
totalsz += length;
}
/**
* get the filename of the content if it is cached to disk.
*
* @return file object pointing to file, or null for memory-stored content
*/
public File getDiskCacheFile() {
return diskCacheFile;
}
public InputStream getInputStream() throws IOException {
return new SAAJInputStream(); // Return the memory held stream.
}
public OutputStream getOutputStream() throws IOException {
//TODO: Method implementation
return null;
}
public String getContentType() {
return contentType;
}
public String getName() {
String ret = null;
try {
flushToDisk();
if (diskCacheFile != null) {
ret = diskCacheFile.getAbsolutePath();
}
} catch (Exception e) {
diskCacheFile = null;
}
return ret;
}
/**
* Inner class to handle getting an input stream to this data source
* Handles creating an input stream to the source.
*/
private class SAAJInputStream extends java.io.InputStream {
/**
* bytes read.
*/
protected long bread = 0;
/**
* The real stream.
*/
private FileInputStream fileIS;
/**
* The position in the list were we are reading from.
*/
int currentIndex;
/**
* the buffer we are currently reading from.
*/
byte[] currentBuf;
/**
* The current position in there.
*/
int currentBufPos;
/**
* The read stream has been closed.
*/
boolean readClosed;
/**
* Constructor Instream.
*
* @throws java.io.IOException if the Instream could not be created or
* if the data source has been deleted
*/
protected SAAJInputStream() throws java.io.IOException {
if (deleted) {
throw new java.io.IOException("resourceDeleted");
}
readers.put(this, null);
}
/**
* Query for the number of bytes available for reading.
*
* @return the number of bytes left
* @throws java.io.IOException if this stream is not in a state that
* supports reading
*/
public int available() throws java.io.IOException {
if (deleted) {
throw new java.io.IOException("resourceDeleted");
}
if (readClosed) {
throw new java.io.IOException("streamClosed");
}
return new Long(Math.min(Integer.MAX_VALUE, totalsz - bread)).intValue();
}
/**
* Read a byte from the stream.
*
* @return byte read or -1 if no more data.
* @throws java.io.IOException
*/
public int read() throws java.io.IOException {
synchronized (SAAJDataSource.this) {
byte[] retb = new byte[1];
int br = read(retb, 0, 1);
if (br == -1) {
return -1;
}
return 0xFF & retb[0];
}
}
/**
* Not supported.
*/
public boolean markSupported() {
return false;
}
/**
* Not supported.
*
* @param readlimit
*/
public void mark(int readlimit) {
}
/**
* Not supported.
*
* @throws java.io.IOException
*/
public void reset() throws IOException {
throw new IOException("noResetMark");
}
public long skip(long skipped) throws IOException {
if (deleted) {
throw new IOException("resourceDeleted");
}
if (readClosed) {
throw new IOException("streamClosed");
}
if (skipped < 1) {
return 0; // nothing to skip.
}
synchronized (SAAJDataSource.this) {
skipped = Math.min(skipped, totalsz - bread); // only skip what we've read.
if (skipped == 0) {
return 0;
}
List ml = memorybuflist; // hold the memory list.
int bwritten = 0;
if (ml != null) {
if (null == currentBuf) { // get the buffer we need to read from.
currentBuf = (byte[]) ml.get(currentIndex);
currentBufPos = 0; // start reading from the begining.
}
do {
long bcopy = Math.min(currentBuf.length - currentBufPos,
skipped - bwritten);
bwritten += bcopy;
currentBufPos += bcopy;
if (bwritten < skipped) {
currentBuf = (byte[]) ml.get(++currentIndex);
currentBufPos = 0;
}
} while (bwritten < skipped);
}
if (null != fileIS) {
fileIS.skip(skipped);
}
bread += skipped;
}
return skipped;
}
public int read(byte[] b, int off, int len) throws IOException {
if (deleted) {
throw new IOException("resourceDeleted");
}
if (readClosed) {
throw new IOException("streamClosed");
}
if (b == null) {
throw new RuntimeException("nullInput");
}
if (off < 0) {
throw new IndexOutOfBoundsException("negOffset " + off);
}
if (len < 0) {
throw new IndexOutOfBoundsException("length " + len);
}
if (len + off > b.length) {
throw new IndexOutOfBoundsException("writeBeyond");
}
if (len == 0) {
return 0;
}
int bwritten = 0;
synchronized (SAAJDataSource.this) {
if (bread == totalsz) {
return -1;
}
List ml = memorybuflist;
long longlen = len;
longlen = Math.min(longlen, totalsz - bread); // Only return the number of bytes in the data store that is left.
len = new Long(longlen).intValue();
if (ml != null) {
if (null == currentBuf) { // Get the buffer we need to read from.
currentBuf = (byte[]) ml.get(currentIndex);
currentBufPos = 0; // New buffer start from the begining.
}
do {
// The bytes to copy, the minimum of the bytes left in this buffer or bytes remaining.
int bcopy = Math.min(currentBuf.length - currentBufPos, len - bwritten);
// Copy the data.
System.arraycopy(currentBuf, currentBufPos, b, off + bwritten, bcopy);
bwritten += bcopy;
currentBufPos += bcopy;
if (bwritten < len) { // Get the next buffer.
currentBuf = (byte[]) ml.get(++currentIndex);
currentBufPos = 0;
}
} while (bwritten < len);
}
if ((bwritten == 0) && (null != diskCacheFile)) {
if (null == fileIS) { // we are now reading from disk.
fileIS = new java.io.FileInputStream(diskCacheFile);
if (bread > 0) {
fileIS.skip(bread); // Skip what we've read so far.
}
}
if (cachediskstream != null) {
cachediskstream.flush();
}
bwritten = fileIS.read(b, off, len);
}
if (bwritten > 0) {
bread += bwritten;
}
}
return bwritten;
}
/**
* close the stream.
*
* @throws IOException
*/
public synchronized void close() throws IOException {
if (!readClosed) {
readers.remove(this);
readClosed = true;
if (fileIS != null) {
fileIS.close();
}
fileIS = null;
}
}
protected void finalize() throws Throwable {
super.finalize();
close();
}
}
}