content: /* The contents of this file are subject to the terms
   * of the Common Development and Distribution License
   * (the License). You may not use this file except in
   * compliance with the License.
   *
   * You can obtain a copy of the License at
   * http://www.sun.com/cddl/cddl.html or
   * install_dir/legal/LICENSE
   * See the License for the specific language governing
   * permission and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL
   * Header Notice in each file and include the License file
   * at install_dir/legal/LICENSE.
   * If applicable, add the following below the CDDL Header,
   * with the fields enclosed by brackets [] replaced by
   * your own identifying information:
   * "Portions Copyrighted [year] [name of copyright owner]"
   *
   * $Id$
   *
   * Copyright 2005-2009 Sun Microsystems Inc. All Rights Reserved
   */
  package com.sun.faban.common;
  
  import java.io.*;
  import java.util.logging.Logger;
  import java.util.logging.Level;
  
  /**
   * The FileTransfer class represents a file to be transferred via RMI from
   * one to the other system. We override the serialization mechanism
   * to ensure we don't need to suck the file's content into memory causing
   * memory bloat. This is useful for transferring large files over RMI.
   * As this object gets serialized, deserialized, the file transfer happens
   * internally and the destination file gets created as a result of the
   * deserialization. The file size limit is Long.MAX_VALUE (64bit).
   *
   * @author Akara Sucharitakul
   */
  public class FileTransfer implements Externalizable {
  
      private static final long serialVersionUID = 20090812L;    
  
      private static final int MAX_BUFFER_SIZE = 8192;
      private static final Logger logger =
                              Logger.getLogger(FileTransfer.class.getName());
  
      private String src;
      private String dest;
      private long size; // Size only gets populated once file transfer happens.
  
      private transient long transferSize;
      private transient byte[] buffer;
      private transient FileInputStream dataIn;
  
      /**
       * Creates a file transfer object.
       * @param src The source file name
       * @param dest The destination file name
       * @exception IOException Error reading the file to be transferred
       */
      public FileTransfer(String src, String dest) throws IOException {
          this.src = src;
          this.dest = dest;
  
          // Ensure the file is really there and readable.
          File srcFile = new File(src);
          if (!srcFile.exists())
              throw new FileNotFoundException("File " +
                      srcFile.getAbsolutePath() + " does not exist.");
          transferSize = srcFile.length();
          if (transferSize < 0)
              throw new IOException(srcFile.getAbsolutePath() +
                      ": Invalid file size of " + transferSize);
  
          dataIn = new FileInputStream(srcFile);
          buffer = new byte[transferSize < MAX_BUFFER_SIZE ?
                                  (int) transferSize : MAX_BUFFER_SIZE];
  
          // Fill the first full buffer now, in order to detect I/O issues
          // now and not during serialization.
          int idx = 0;
          while (idx < buffer.length) {
              int readCount = dataIn.read(buffer, idx, buffer.length - idx);
              if (readCount < 0)
                  throw new IOException("Error reading file " +
                          srcFile.getAbsolutePath() + ". Size: " + transferSize +
                          ", Read: " + idx);
              idx += readCount;
          }
  
          if (buffer.length == transferSize) { // We have read everything now.
              dataIn.close();
          }
      }
  
      /**
       * Creates a file transfer object from a byte buffer.
       * @param buffer The buffer
       * @param offset The starting offset to use
       * @param length The length of data to use, in bytes
       * @param dest The destination file
       */
      public FileTransfer(byte[] buffer, int offset, int length, String dest) {
          this.src = "";
          this.dest = dest;
          transferSize = length;
          this.buffer = new byte[length];
          System.arraycopy(buffer, offset, this.buffer, 0, length);
      }
  
      /**
       * The noarg constructore is used for deserializing.
       */
      public FileTransfer() {
  
      }
  
      /**
       * Obtains the sources file name.
       * @return The source file name, or an empty string if the transfer happens
       *         from a buffer.
       */
      public String getSource() {
          return src;
      }
  
      /**
       * Obtains the destination file name.     *
       * @return The destination file name
       */
      public String getDest() {
          return dest;
      }
  
      /**
       * Obtains the size of the file transferred, or 0 if the file transfer
       * has not yet happen.
       * @return The size of the file transferred, or 0
       */
      public long getSize() {
          return size;
      }
  
      /**
       * Obtains the size to be transferred on the sending side, or the size
       * really transferred on the receiving side.
       * @return The transfer size
       */
      public long getTransferSize() {
          return transferSize;
      }
  
      public void writeExternal(ObjectOutput out) throws IOException {
  
          // Flush headers and the first chunk.
          size = transferSize;
          out.writeObject(src);
          out.writeObject(dest);
          out.writeLong(size);
          out.write(buffer);
  
          // Then stream the rest, if any
          if (size > buffer.length) {
              // Write the rest of the data stream, one buffer at a time.
              long remainder = size - buffer.length;
              while (remainder > 0) {
                  int chunkSize = remainder < buffer.length ?
                          (int) remainder : buffer.length;
                  if (dataIn != null) {
                      // Even if we have or had errors, we need to transfer
                      // the whole agreed upon bytes, valid or not.
                      // This is not to corrupt the stream.
                      try {
                          chunkSize = dataIn.read(buffer, 0, chunkSize);
                      } catch (IOException e) {
                          logger.log(Level.WARNING,
                                      "Error reading from file " + src, e);
                          dataIn = null;
                      }
                  }
                  if (chunkSize < 0) {
                      break;
                  } else if (chunkSize == 0) {
                      continue;
                  }
                  out.write(buffer, 0, chunkSize);
                  remainder -= chunkSize;
              }
              if (dataIn != null)
                  try {
                      dataIn.close();
                  } catch (IOException e) {
                      logger.log(Level.WARNING, "Error closing file " +
                                                  src, e);
                  }
              buffer = null;
          }
      }
  
      public void readExternal(ObjectInput in)
              throws IOException, ClassNotFoundException {
  
          // Read the headers
          src = (String) in.readObject();
          dest = (String) in.readObject();
          size = in.readLong();
  
          // Convert destination file name to OS path name
          dest = Utilities.convertPath(dest);
  
          // We need to ensure we read everything out in order not to
          // cause an rmi stream corruption, even if our file write bails.
          // Create the file
          FileOutputStream dataOut = null;
          try {
              dataOut = new FileOutputStream(dest);
          } catch (IOException e) {
              logger.log(Level.WARNING, "Error opening file " + dest, e);
          }
  
          // Then, read the data stream and save to file, one buffer at a time.
          if (buffer == null)
              buffer = new byte[size < MAX_BUFFER_SIZE ? (int) size :
                                                          MAX_BUFFER_SIZE];
          long remainder = size;
          while (remainder > 0) {
              int chunkSize =
                      remainder < buffer.length ? (int) remainder : buffer.length;
              chunkSize = in.read(buffer, 0, chunkSize);
              if (chunkSize < 0) {
                  break;
              } else if (chunkSize == 0) {
                  continue;
              }
              if (dataOut != null)
                  // We still have to clear the stream,
                  // even if we cannot write it to file.
                  try {
                      dataOut.write(buffer, 0, chunkSize);
                  } catch (IOException e) {
                      logger.log(Level.WARNING, "Error writing to file " +
                                              dest, e);
                      dataOut = null;
                  }
              remainder -= chunkSize;
          }
          transferSize = size - remainder;
          if (dataOut != null) {
              try {
                  dataOut.flush();
              } catch (IOException e) {
                  logger.log(Level.WARNING, "Error flushing data to file " +
                                              dest, e);
              }
              try {
                  dataOut.close();
              } catch (IOException e) {
                  logger.log(Level.WARNING, "Error closing file " +
                                              dest, e);
              }
          }
          buffer = null;
      }
  }
  