| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.axis2.Constants; |
| import org.apache.axis2.context.MessageContext; |
| import org.apache.axis2.description.Parameter; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| 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) { |
| writeToMemory(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);*/ |
| |
| |
| MessageContext messageContext = MessageContext.getCurrentMessageContext(); |
| String attachementDir = ""; |
| attachementDir = (String)messageContext.getProperty |
| (Constants.Configuration.ATTACHMENT_TEMP_DIR); |
| |
| if (attachementDir.equals("")) { |
| Parameter param = (Parameter)messageContext.getParameter |
| (Constants.Configuration.ATTACHMENT_TEMP_DIR); |
| if (param != null) { |
| attachementDir = (String)param.getValue(); |
| } |
| } |
| |
| diskCacheFile = java.io.File.createTempFile("Axis", ".att", |
| (attachementDir == null) |
| ? null |
| : new File( |
| attachementDir)); |
| 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; |
| } |
| |
| |
| /** |
| * This method is a low level write. Writes only to memory |
| * |
| * @param data |
| * @param length |
| * @throws java.io.IOException |
| */ |
| protected synchronized void writeToMemory(byte[] data, int length) throws java.io.IOException { |
| |
| if (closed) { |
| throw new java.io.IOException("streamClosed"); |
| } |
| |
| int byteswritten = 0; |
| 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); |
| } |
| 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(); |
| } |
| } |
| } |