Java tutorial
/* File: $Id$ * Revision: $Revision$ * Author: $Author$ * Date: $Date$ * * The Netarchive Suite - Software to harvest and preserve websites * Copyright 2004-2012 The Royal Danish Library, the Danish State and * University Library, the National Library of France and the Austrian * National Library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package dk.netarkivet.common.distribute; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.Calendar; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import dk.netarkivet.common.exceptions.ArgumentNotValid; import dk.netarkivet.common.exceptions.IOFailure; import dk.netarkivet.common.utils.FileUtils; import dk.netarkivet.common.utils.StreamUtils; import dk.netarkivet.common.utils.TimeUtils; /** * Abstract superclass for easy implementation of remote file. * * Sub classes should override this class, and do the following: * - Implement getChecksum. * - Implement getInputStream. * - Implement cleanup. * - Add getInstance(File, Boolean, Boolean, Boolean)-method to make the file * work with the factory. */ public abstract class AbstractRemoteFile implements RemoteFile { /** The file this is remote file for. */ protected final File file; /** If true, communication is checksummed. */ protected final boolean useChecksums; /** If true, the file may be deleted after all transfers are done. */ protected final boolean fileDeletable; /** If true, the file may be downloaded multiple times. Otherwise, the * remote file is invalidated after first transfer. */ protected final boolean multipleDownloads; /** The size of the file. */ protected final long filesize; /** * A named logger for this class. */ private static final transient Log log = LogFactory.getLog(AbstractRemoteFile.class.getName()); /** * Initialise common fields in remote file. * Overriding classes should also initialise checksum field. * * @param file The file to make remote file for. * @param useChecksums If true, communications should be checksummed. * @param fileDeletable If true, the file may be downloaded multiple times. * Otherwise, the remote file is invalidated after first transfer. * @param multipleDownloads If useChecksums is true, contains the file * checksum. */ public AbstractRemoteFile(File file, boolean useChecksums, boolean fileDeletable, boolean multipleDownloads) { ArgumentNotValid.checkNotNull(file, "File file"); if (!file.isFile() || !file.canRead()) { throw new ArgumentNotValid("File '" + file.getAbsolutePath() + "' is not a readable file"); } this.file = file; this.fileDeletable = fileDeletable; this.multipleDownloads = multipleDownloads; this.useChecksums = useChecksums; this.filesize = file.length(); } /** Copy this remote file to the given file. * This method will make a fileoutputstream, and use appendTo to write * the remote file to this stream. * @param destFile The file to write the remote file to. * @throws ArgumentNotValid on null destFile, or parent to destfile is not * a writeable directory, or destfile exists and cannot be overwritten. * @throws IOFailure on I/O trouble writing remote file to destination. */ public void copyTo(File destFile) { ArgumentNotValid.checkNotNull(destFile, "File destFile"); destFile = destFile.getAbsoluteFile(); if ((!destFile.isFile() || !destFile.canWrite()) && (!destFile.getParentFile().isDirectory() || !destFile.getParentFile().canWrite())) { throw new ArgumentNotValid( "Destfile '" + destFile + "' does not point to a writable file for remote file '" + file + "'"); } try { FileOutputStream fos = null; int retry = 0; boolean success = false; // retry if it fails, but always make at least one attempt. do { try { try { fos = new FileOutputStream(destFile); appendTo(fos); success = true; } finally { if (fos != null) { fos.close(); } } } catch (IOFailure e) { if (retry == 0) { log.warn("Could not retrieve the file '" + getName() + "' on first attempt. Will retry up to '" + getNumberOfRetries() + "' times.", e); } else { log.warn("Could not retrieve the file '" + getName() + "' on retry number '" + retry + "' of '" + getNumberOfRetries() + "' retries.", e); } } retry++; if (!success && retry < getNumberOfRetries()) { log.debug("CopyTo attempt #" + retry + " of max " + getNumberOfRetries() + " failed. Will sleep a while before trying to " + "copyTo again."); TimeUtils.exponentialBackoffSleep(retry, Calendar.MINUTE); } } while (!success && retry < getNumberOfRetries()); // handle case when the retrieval is unsuccessful. if (!success) { throw new IOFailure("Unable to retrieve the file '" + getName() + "' in '" + getNumberOfRetries() + "' attempts."); } } catch (Exception e) { FileUtils.remove(destFile); throw new IOFailure("IO trouble transferring file", e); } } /** Append this remote file to the given output stream. * This method will use getInputStream to get the remote stream, and then * copy that stream to the given output stream. * @param out The stream to write the remote file to. * @throws ArgumentNotValid if outputstream is null. * @throws IOFailure on I/O trouble writing remote file to stream. */ public void appendTo(OutputStream out) { ArgumentNotValid.checkNotNull(out, "OutputStream out"); StreamUtils.copyInputStreamToOutputStream(getInputStream(), out); } /** Get an input stream representing the remote file. * The returned input stream should throw IOFailure on close, if * checksums are requested, but do not match. The returned inputstream * should call cleanup on close, if multipleDownloads is not true. * @return An input stream for the remote file. * @throws IOFailure on I/O trouble generating inputstream for remote file. */ public abstract InputStream getInputStream(); /** * Get the name of the remote file. * @return The name of the remote file. */ public String getName() { return file.getName(); } /** Get checksum for file, or null if checksums were not requested. * @return checksum for file, or null if checksums were not requested. */ public abstract String getChecksum(); /** * Invalidate all file handles. If file is deletable, it should be deleted * after this method is called. This method should never throw exceptions, * but only log a warning on trouble. It should be idempotent, meaning it * should be safe to call this method twice. */ public abstract void cleanup(); /** * Method for retrieving the number of retries for retrieving a file. * @return The number of retries for retrieving a file. */ public abstract int getNumberOfRetries(); /** Get the size of this remote file. * @return The size of this remote file. */ public long getSize() { return filesize; } }