All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.emc.esu.api.rest.DownloadHelper Maven / Gradle / Ivy

There is a newer version: 2.2.2
Show newest version
/*
 * Copyright 2013 EMC Corporation. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * or in the "license" file accompanying this file. This file 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 com.emc.esu.api.rest;

import com.emc.esu.api.BufferSegment;
import com.emc.esu.api.Checksum;
import com.emc.esu.api.Checksum.Algorithm;
import com.emc.esu.api.EsuApi;
import com.emc.esu.api.EsuException;
import com.emc.esu.api.Extent;
import com.emc.esu.api.Identifier;
import com.emc.esu.api.MetadataList;
import com.emc.esu.api.ProgressListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

/**
 * Helper class to download objects.  For large transfers, the content
 * generally needs to be transferred from the server in smaller chunks.  This
 * helper class reads an object's contents incrementally from the server and
 * writes it to a file or stream.
 */
public class DownloadHelper {

    private static final Logger l4j = Logger.getLogger(DownloadHelper.class);
    private static final int DEFAULT_BUFFSIZE = 4096*1024; // 4MB

    private EsuApi esu;
    private BufferSegment buffer;

    private long currentBytes;
    private long totalBytes;
    private boolean complete;
    private boolean failed;
    private Exception error;
    private boolean closeStream;
    private OutputStream stream;
    private List listeners;
    private boolean checksumming;
    private Checksum checksum;

    /**
     * Creates a new download helper.
     * @param esuApi the API connection object to use to communicate
     * with the server.
     * @param buffer the buffer to use for the transfers from the server.  If
     * null, a default 4MB buffer will be used.
     */
    public DownloadHelper(EsuApi esuApi, byte[] buffer) {
        this.esu = esuApi;
        this.buffer = new BufferSegment(
                buffer == null ? new byte[DEFAULT_BUFFSIZE] : buffer);
        listeners = new ArrayList();
    }

    /**
     * Downloads the given object's contents to a file.
     * @param id the identifier of the object to download
     * @param f the file to write the object's contents to.
     */
    public void readObject( Identifier id, File f) {
        OutputStream out;
        try {
            out = new FileOutputStream(f);
        } catch (FileNotFoundException e) {
            throw new EsuException("Error opening output file", e);
        }
        readObject(id, out, true);
    }

    /**
     * Downloads the given object's contents to a stream.
     * @param id the identifier of the object to download.
     * @param stream the stream to write the object's contents to.
     * @param closeStream specifies whether to close the stream after
     * the transfer is complete.  Defaults to true.
     */
    public void readObject( Identifier id, OutputStream stream, boolean closeStream) {

        this.currentBytes = 0;
        this.complete = false;
        this.failed = false;
        this.error = null;
        this.closeStream = closeStream;
        this.stream = stream;

        // Get the file size.  Set to -1 if unknown.
        MetadataList sMeta = this.esu.getAllMetadata(id).getMetadata();
        if (sMeta.getMetadata("size") != null) {
            String size = sMeta.getMetadata("size").getValue();
            if (size != null && size.length() > 0) {
                this.totalBytes = Long.parseLong(size);
            } else {
                this.totalBytes = -1;
            }
        } else {
            this.totalBytes = -1;
        }

        // We need to know how big the object is to download it.  Fail the
        // transfer if we can't determine the object size.
        if (this.totalBytes == -1) {
            throw new EsuException("Failed to get object size");
        }
        
        if( checksumming ) {
        	try {
				checksum = new Checksum( Algorithm.SHA0 );
			} catch (NoSuchAlgorithmException e) {
				throw new RuntimeException( "Could not initialize SHA0 hash algorithm" );
			}
        }

        // Loop, downloading chunks until the transfer is complete.
        while (true) {
            try {
                Extent extent = null;

                // Determine how much data to download.  If we're at the last
                // request in the transfer, only request as many bytes as needed
                // to get to the end of the file.
                if (currentBytes + buffer.getBuffer().length > totalBytes) {
                    // Would go past end of file.  Request less bytes.                                      
                    extent = new Extent(this.currentBytes, totalBytes
                            - currentBytes);
                } else {
                    extent = new Extent(this.currentBytes,
                            buffer.getBuffer().length);
                }
                buffer.setSize((int) extent.getSize());

                // Read data from the server.
                byte[] obuffer = this.esu.readObject(id, extent, buffer.getBuffer(), checksum);

                // See if they returned the buffer we're using.  In some
                // cases, this doesn't happen (when content length is not
                // defined in the response).
                if( obuffer != buffer.getBuffer() ) {
                    if( obuffer.length != extent.getSize() ) {
                        throw new EsuException( "Read size mismatch.  " +
                        		"Requested " + extent.getSize() + 
                        		" bytes but received " + 
                        		obuffer.length + " bytes" );
                    }
                    stream.write( obuffer, 0, obuffer.length );
                } else {
                    // Write to the stream
                    stream.write(buffer.getBuffer(), buffer.getOffset(), buffer
                            .getSize());
                }

                // Update progress
                this.progress(buffer.getSize());

                // See if we're done.
                if (this.currentBytes == this.totalBytes) {
                	if( checksumming && checksum.getExpectedValue() != null ) {
                		// Validate
                    	
                    	if( !checksum.getExpectedValue().equals( checksum.toString() ) ) {
                    		throw new EsuException("Checksum validation error.  Expected " + checksum.getExpectedValue() + " but computed " + checksum.toString() );
                    	} else {
                    		l4j.info( "Checksum OK: " + checksum.getExpectedValue() );
                    	}

                	}
                    this.complete();
                    return;
                }
            } catch (EsuException e) {
                this.fail(e);
                throw e;
            } catch (IOException e) {
                fail(e);
                throw new EsuException("Error downloading file", e);
            }
        }
    }

    /**
     * Gets the current number of bytes that have been downloaded.
     * @return the current number of bytes downloaded.
     */
    public long getCurrentBytes() {
        return currentBytes;
    }

    /**
     * Gets the total number of bytes to download.
     * @return the total number of bytes to download.
     */
    public long getTotalBytes() {
        return totalBytes;
    }

    /**
     * Returns true if the transfer has completed.
     * @return true if the transfer has completed, false otherwise.
     */
    public boolean isComplete() {
        return this.complete;
    }

    /**
     * Returns true if the transfer has failed.
     * @return true if the transfer has failed, false otherwise.
     */
    public boolean isFailed() {
        return this.failed;
    }

    /**
     * If the transfer has failed, return the error that caused the failure.
     * @return the error that caused the transfer to fail.
     */
    public Exception getError() {
        return this.error;
    }

    /**
     * Sets a listener to provide feedback on the transfer's progress.
     * @param listener the listener to use for feedback.  Set
     * to null to disable progress notifications.
     */
    public void addListener(ProgressListener listener) {
        this.listeners.add(listener);
    }

    /////////////////////
    // Private methods //
    /////////////////////

    /**
     * Updates progress on the current transfer and notifies the listener if
     * required.
     * @param bytes the number of bytes transferred
     */
    private void progress(long bytes) {
        this.currentBytes += bytes;
        for (Iterator i = listeners.iterator(); i.hasNext();) {
            ProgressListener pl = i.next();
            pl.onProgress(currentBytes, totalBytes);
        }
    }

    /**
     * Marks the current transfer as complete, closes the stream if required,
     * and notifies the listener.
     */
    private void complete() {
        complete = true;

        if (closeStream) {
            try {
                stream.close();
            } catch (IOException e) {
                // ignore
                l4j.warn("Error closing output stream", e);
            }
        }

        for (Iterator i = listeners.iterator(); i.hasNext();) {
            ProgressListener pl = i.next();
            pl.onComplete();
        }
    }

    /**
     * Fails the current transfer.  Sets the failed flag and notifies the 
     * listener if required.
     * @param e the error that caused the transfer to fail.
     */
    private void fail(Exception e) {
        this.failed = true;
        this.error = e;
        for (Iterator i = listeners.iterator(); i.hasNext();) {
            ProgressListener pl = i.next();
            pl.onError(e);
        }

    }

	/**
	 * @param checksumming the checksumming to set
	 */
	public void setChecksumming(boolean checksumming) {
		this.checksumming = checksumming;
	}

	/**
	 * @return the checksumming
	 */
	public boolean isChecksumming() {
		return checksumming;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy