/* 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; } }