Save This Page
Home » synapse-1.2-src » org.apache.synapse.util » [javadoc | source]
    1   package org.apache.synapse.util;
    2   
    3   import org.apache.commons.io.IOUtils;
    4   import org.apache.commons.logging.Log;
    5   import org.apache.commons.logging.LogFactory;
    6   
    7   import java.io;
    8   
    9   /**
   10    * Class representing some temporary data in the form of a byte stream.
   11    * <p>
   12    * Data is stored by writing to the output stream obtained using
   13    * {@link #getOutputStream()}. It can then be read back using
   14    * the input stream obtained from {@link #getInputStream()}.
   15    * The data is first stored into a fixed size buffer. Once this
   16    * buffer overflows, it is transferred to a temporary file. The buffer
   17    * is divided into a given number of fixed size chunks that are allocated
   18    * on demand. Since a temporary file may be created it is mandatory to
   19    * call {@link #release()} to discard the temporary data.
   20    */
   21   public class TemporaryData {
   22       
   23       private static final Log log = LogFactory.getLog(TemporaryData.class);
   24       
   25       class OutputStreamImpl extends OutputStream {
   26           
   27           private FileOutputStream fileOutputStream;
   28           
   29           public void write(byte[] b, int off, int len) throws IOException {
   30   
   31               if (fileOutputStream != null) {
   32                   fileOutputStream.write(b, off, len);
   33               } else if (len > (chunks.length-chunkIndex)*chunkSize - chunkOffset) {
   34   
   35                   // The buffer will overflow. Switch to a temporary file.
   36                   fileOutputStream = switchToTempFile();
   37                   
   38                   // Write the new data to the temporary file.
   39                   fileOutputStream.write(b, off, len);
   40   
   41               } else {
   42   
   43                   // The data will fit into the buffer.
   44                   while (len > 0) {
   45   
   46                       byte[] chunk = getCurrentChunk();
   47   
   48                       // Determine number of bytes that can be copied to the current chunk.
   49                       int c = Math.min(len, chunkSize-chunkOffset);
   50                       // Copy data to the chunk.
   51                       System.arraycopy(b, off, chunk, chunkOffset, c);
   52   
   53                       // Update variables.
   54                       len -= c;
   55                       off += c;
   56                       chunkOffset += c;
   57                       if (chunkOffset == chunkSize) {
   58                           chunkIndex++;
   59                           chunkOffset = 0;
   60                       }
   61                   }
   62               }
   63           }
   64   
   65           public void write(byte[] b) throws IOException {
   66               write(b, 0, b.length);
   67           }
   68   
   69           public void write(int b) throws IOException {
   70               write(new byte[] { (byte)b }, 0, 1);
   71           }
   72   
   73           public void flush() throws IOException {
   74               if (fileOutputStream != null) {
   75                   fileOutputStream.flush();
   76               }
   77           }
   78   
   79           public void close() throws IOException {
   80               if (fileOutputStream != null) {
   81                   fileOutputStream.close();
   82               }
   83           }
   84       }
   85       
   86       class InputStreamImpl extends InputStream {
   87   
   88           private int currentChunkIndex;
   89           private int currentChunkOffset;
   90           private int markChunkIndex;
   91           private int markChunkOffset;
   92           
   93           public int available() throws IOException {
   94               return (chunkIndex-currentChunkIndex)*chunkSize + chunkOffset - currentChunkOffset;
   95           }
   96   
   97           public int read(byte[] b, int off, int len) throws IOException {
   98   
   99               if (len == 0) {
  100                   return 0;
  101               }
  102   
  103               int read = 0;
  104               while (len > 0 && !(currentChunkIndex == chunkIndex
  105                       && currentChunkOffset == chunkOffset)) {
  106   
  107                   int c;
  108                   if (currentChunkIndex == chunkIndex) {
  109                       // The current chunk is the last one => take into account the offset
  110                       c = Math.min(len, chunkOffset-currentChunkOffset);
  111                   } else {
  112                       c = Math.min(len, chunkSize-currentChunkOffset);
  113                   }
  114   
  115                   // Copy the data.
  116                   System.arraycopy(chunks[currentChunkIndex], currentChunkOffset, b, off, c);
  117   
  118                   // Update variables
  119                   len -= c;
  120                   off += c;
  121                   currentChunkOffset += c;
  122                   read += c;
  123                   if (currentChunkOffset == chunkSize) {
  124                       currentChunkIndex++;
  125                       currentChunkOffset = 0;
  126                   }
  127               }
  128   
  129               if (read == 0) {
  130                   // We didn't read anything (and the len argument was not 0) => we reached the end of the buffer.
  131                   return -1;
  132               } else {
  133                   return read;
  134               }
  135           }
  136   
  137           public int read(byte[] b) throws IOException {
  138               return read(b, 0, b.length);
  139           }
  140   
  141           public int read() throws IOException {
  142               byte[] b = new byte[1];
  143               return read(b) == -1 ? -1 : (int)b[0] & 0xFF;
  144           }
  145   
  146           public boolean markSupported() {
  147               return true;
  148           }
  149   
  150           public void mark(int readlimit) {
  151               markChunkIndex = currentChunkIndex;
  152               markChunkOffset = currentChunkOffset;
  153           }
  154   
  155           public void reset() throws IOException {
  156               currentChunkIndex = markChunkIndex;
  157               currentChunkOffset = markChunkOffset;
  158           }
  159   
  160           public long skip(long n) throws IOException {
  161   
  162               int available = available();
  163               int c = n < available ? (int)n : available;
  164               int newOffset = currentChunkOffset + c;
  165               int chunkDelta = newOffset/chunkSize;
  166               currentChunkIndex += chunkDelta;
  167               currentChunkOffset = newOffset - (chunkDelta*chunkSize);
  168               return c;
  169           }
  170           
  171           public void close() throws IOException {
  172           }
  173       }
  174       
  175       /**
  176        * Size of the chunks that will be allocated in the buffer.
  177        */
  178       final int chunkSize;
  179       
  180       /**
  181        * The prefix to be used in generating the name of the temporary file.
  182        */
  183       final String tempPrefix;
  184       
  185       /**
  186        * The suffix to be used in generating the name of the temporary file.
  187        */
  188       final String tempSuffix;
  189       
  190       /**
  191        * Array of <code>byte[]</code> representing the chunks of the buffer.
  192        * A chunk is only allocated when the first byte is written to it.
  193        * This attribute is set to <code>null</code> when the buffer overflows and
  194        * is written out to a temporary file.
  195        */
  196       byte[][] chunks;
  197       
  198       /**
  199        * Index of the chunk the next byte will be written to.
  200        */
  201       int chunkIndex;
  202       
  203       /**
  204        * Offset into the chunk where the next byte will be written.
  205        */
  206       int chunkOffset;
  207       
  208       /**
  209        * The handle of the temporary file. This is only set when the memory buffer
  210        * overflows and is written out to a temporary file.
  211        */
  212       File temporaryFile;
  213       
  214       public TemporaryData(int numberOfChunks, int chunkSize, String tempPrefix, String tempSuffix) {
  215           this.chunkSize = chunkSize;
  216           this.tempPrefix = tempPrefix;
  217           this.tempSuffix = tempSuffix;
  218           chunks = new byte[numberOfChunks][];
  219       }
  220       
  221       /**
  222        * Get the current chunk to write to, allocating it if necessary.
  223        * 
  224        * @return the current chunk to write to (never null)
  225        */
  226       byte[] getCurrentChunk() {
  227           if (chunkOffset == 0) {
  228               // We will write the first byte to the current chunk. Allocate it.
  229               byte[] chunk = new byte[chunkSize];
  230               chunks[chunkIndex] = chunk;
  231               return chunk;
  232           } else {
  233               // The chunk has already been allocated.
  234               return chunks[chunkIndex];
  235           }
  236       }
  237       
  238       /**
  239        * Create a temporary file and write the existing in memory data to it.
  240        * 
  241        * @return an open FileOutputStream to the temporary file
  242        * @throws IOException
  243        */
  244       FileOutputStream switchToTempFile() throws IOException {
  245           temporaryFile = File.createTempFile(tempPrefix, tempSuffix);
  246           if (log.isDebugEnabled()) {
  247               log.debug("Using temporary file " + temporaryFile);
  248           }
  249           temporaryFile.deleteOnExit();
  250   
  251           FileOutputStream fileOutputStream = new FileOutputStream(temporaryFile);
  252           // Write the buffer to the temporary file.
  253           for (int i=0; i<chunkIndex; i++) {
  254               fileOutputStream.write(chunks[i]);
  255           }
  256   
  257           if (chunkOffset > 0) {
  258               fileOutputStream.write(chunks[chunkIndex], 0, chunkOffset);
  259           }
  260   
  261           // Release references to the buffer so that it can be garbage collected.
  262           chunks = null;
  263           
  264           return fileOutputStream;
  265       }
  266       
  267       public OutputStream getOutputStream() {
  268           return new OutputStreamImpl();
  269       }
  270       
  271       /**
  272        * Fill this object with data read from a given InputStream.
  273        * <p>
  274        * A call <code>tmp.readFrom(in)</code> has the same effect as the
  275        * following code:
  276        * <pre>
  277        * OutputStream out = tmp.getOutputStream();
  278        * IOUtils.copy(in, out);
  279        * out.close();
  280        * </pre>
  281        * However it does so in a more efficient way.
  282        * 
  283        * @param in An InputStream to read data from. This method will not
  284        *           close the stream.
  285        * @throws IOException
  286        */
  287       public void readFrom(InputStream in) throws IOException {
  288           while (true) {
  289               int c = in.read(getCurrentChunk(), chunkOffset, chunkSize-chunkOffset);
  290               if (c == -1) {
  291                   break;
  292               }
  293               chunkOffset += c;
  294               if (chunkOffset == chunkSize) {
  295                   chunkIndex++;
  296                   chunkOffset = 0;
  297                   if (chunkIndex == chunks.length) {
  298                       FileOutputStream fileOutputStream = switchToTempFile();
  299                       IOUtils.copy(in, fileOutputStream);
  300                       fileOutputStream.close();
  301                       break;
  302                   }
  303               }
  304           }
  305       }
  306       
  307       public InputStream getInputStream() throws IOException {
  308           if (temporaryFile != null) {
  309               return new FileInputStream(temporaryFile);
  310           } else {
  311               return new InputStreamImpl();
  312           }
  313       }
  314       
  315       public void release() {
  316           if (temporaryFile != null) {
  317               if (log.isDebugEnabled()) {
  318                   log.debug("Deleting temporary file " + temporaryFile);
  319               }
  320               temporaryFile.delete();
  321           }
  322       }
  323   
  324       protected void finalize() throws Throwable {
  325           if (temporaryFile != null) {
  326               log.warn("Cleaning up unreleased temporary file " + temporaryFile);
  327               temporaryFile.delete();
  328           }
  329       }
  330   }

Save This Page
Home » synapse-1.2-src » org.apache.synapse.util » [javadoc | source]