/* * ItemExport.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 18:02:24 +0100 (Sat, 11 Apr 2009) $ * * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts * Institute of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the Hewlett-Packard Company nor the name of the * Massachusetts Institute of Technology nor the names of their * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.app.itemexport; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.mail.MessagingException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.PosixParser; import org.apache.commons.codec.binary.Hex; import org.apache.log4j.Logger; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DCValue; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.ItemIterator; import org.dspace.content.MetadataSchema; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.I18nUtil; import org.dspace.core.LogManager; import org.dspace.core.Utils; import org.dspace.core.Email; import org.dspace.eperson.EPerson; import org.dspace.handle.HandleManager; /** * Item exporter to create simple AIPs for DSpace content. Currently exports * individual items, or entire collections. For instructions on use, see * printUsage() method. * <P> * ItemExport creates the simple AIP package that the importer also uses. It * consists of: * <P> * /exportdir/42/ (one directory per item) / dublin_core.xml - qualified dublin * core in RDF schema / contents - text file, listing one file per line / file1 * - files contained in the item / file2 / ... * <P> * issues -doesn't handle special characters in metadata (needs to turn &'s into * &, etc.) * <P> * Modified by David Little, UCSD Libraries 12/21/04 to allow the registration * of files (bitstreams) into DSpace. * * @author David Little * @author Jay Paz */ public class ItemExport { private static final int SUBDIR_LIMIT = 0; static ExecutorService es = Executors.newFixedThreadPool(5); /** * used for export download */ public static final String COMPRESSED_EXPORT_MIME_TYPE = "application/zip"; /** log4j logger */ private static Logger log = Logger.getLogger(ItemExport.class); /* * */ public static void main(String[] argv) throws Exception { // create an options object and populate it CommandLineParser parser = new PosixParser(); Options options = new Options(); options.addOption("t", "type", true, "type: COLLECTION or ITEM"); options.addOption("i", "id", true, "ID or handle of thing to export"); options.addOption("d", "dest", true, "destination where you want items to go"); options.addOption("m", "migrate", false, "export for migration (remove handle and metadata that will be re-created in new system)"); options.addOption("n", "number", true, "sequence number to begin exporting items with"); options.addOption("h", "help", false, "help"); CommandLine line = parser.parse(options, argv); String typeString = null; String destDirName = null; String myIDString = null; int seqStart = -1; int myType = -1; Item myItem = null; Collection mycollection = null; if (line.hasOption('h')) { HelpFormatter myhelp = new HelpFormatter(); myhelp.printHelp("ItemExport\n", options); System.out .println("\nfull collection: ItemExport -t COLLECTION -i ID -d dest -n number"); System.out .println("singleitem: ItemExport -t ITEM -i ID -d dest -n number"); System.exit(0); } if (line.hasOption('t')) // type { typeString = line.getOptionValue('t'); if (typeString.equals("ITEM")) { myType = Constants.ITEM; } else if (typeString.equals("COLLECTION")) { myType = Constants.COLLECTION; } } if (line.hasOption('i')) // id { myIDString = line.getOptionValue('i'); } if (line.hasOption('d')) // dest { destDirName = line.getOptionValue('d'); } if (line.hasOption('n')) // number { seqStart = Integer.parseInt(line.getOptionValue('n')); } boolean migrate = false; if (line.hasOption('m')) // number { migrate = true; } // now validate the args if (myType == -1) { System.out .println("type must be either COLLECTION or ITEM (-h for help)"); System.exit(1); } if (destDirName == null) { System.out .println("destination directory must be set (-h for help)"); System.exit(1); } if (seqStart == -1) { System.out .println("sequence start number must be set (-h for help)"); System.exit(1); } if (myIDString == null) { System.out .println("ID must be set to either a database ID or a handle (-h for help)"); System.exit(1); } Context c = new Context(); c.setIgnoreAuthorization(true); if (myType == Constants.ITEM) { // first, is myIDString a handle? if (myIDString.indexOf('/') != -1) { myItem = (Item) HandleManager.resolveToObject(c, myIDString); if ((myItem == null) || (myItem.getType() != Constants.ITEM)) { myItem = null; } } else { myItem = Item.find(c, Integer.parseInt(myIDString)); } if (myItem == null) { System.out .println("Error, item cannot be found: " + myIDString); } } else { if (myIDString.indexOf('/') != -1) { // has a / must be a handle mycollection = (Collection) HandleManager.resolveToObject(c, myIDString); // ensure it's a collection if ((mycollection == null) || (mycollection.getType() != Constants.COLLECTION)) { mycollection = null; } } else if (myIDString != null) { mycollection = Collection.find(c, Integer.parseInt(myIDString)); } if (mycollection == null) { System.out.println("Error, collection cannot be found: " + myIDString); System.exit(1); } } if (myItem != null) { // it's only a single item exportItem(c, myItem, destDirName, seqStart, migrate); } else { System.out.println("Exporting from collection: " + myIDString); // it's a collection, so do a bunch of items ItemIterator i = mycollection.getItems(); try { exportItem(c, i, destDirName, seqStart, migrate); } finally { if (i != null) i.close(); } } c.complete(); } static void exportItem(Context c, ItemIterator i, String destDirName, int seqStart, boolean migrate) throws Exception { int mySequenceNumber = seqStart; int counter = SUBDIR_LIMIT - 1; int subDirSuffix = 0; String fullPath = destDirName; String subdir = ""; File dir; if (SUBDIR_LIMIT > 0) { dir = new File(destDirName); if (!dir.isDirectory()) { throw new IOException(destDirName + " is not a directory."); } } System.out.println("Beginning export"); while (i.hasNext()) { if (SUBDIR_LIMIT > 0 && ++counter == SUBDIR_LIMIT) { subdir = new Integer(subDirSuffix++).toString(); fullPath = destDirName + File.separatorChar + subdir; counter = 0; if (!new File(fullPath).mkdirs()) { throw new IOException("Error, can't make dir " + fullPath); } } System.out.println("Exporting item to " + mySequenceNumber); exportItem(c, i.next(), fullPath, mySequenceNumber, migrate); mySequenceNumber++; } } private static void exportItem(Context c, Item myItem, String destDirName, int seqStart, boolean migrate) throws Exception { File destDir = new File(destDirName); if (destDir.exists()) { // now create a subdirectory File itemDir = new File(destDir + "/" + seqStart); System.out.println("Exporting Item " + myItem.getID() + " to " + itemDir); if (itemDir.exists()) { throw new Exception("Directory " + destDir + "/" + seqStart + " already exists!"); } if (itemDir.mkdir()) { // make it this far, now start exporting writeMetadata(c, myItem, itemDir, migrate); writeBitstreams(c, myItem, itemDir); if (!migrate) { writeHandle(c, myItem, itemDir); } } else { throw new Exception("Error, can't make dir " + itemDir); } } else { throw new Exception("Error, directory " + destDirName + " doesn't exist!"); } } /** * Discover the different schemas in use and output a seperate metadata XML * file for each schema. * * @param c * @param i * @param destDir * @throws Exception */ private static void writeMetadata(Context c, Item i, File destDir, boolean migrate) throws Exception { // Build a list of schemas for the item HashMap map = new HashMap(); DCValue[] dcorevalues = i.getMetadata(Item.ANY, Item.ANY, Item.ANY, Item.ANY); for (int ii = 0; ii < dcorevalues.length; ii++) { map.put(dcorevalues[ii].schema, null); } // Save each of the schemas into it's own metadata file Iterator iterator = map.keySet().iterator(); while (iterator.hasNext()) { String schema = (String) iterator.next(); writeMetadata(c, schema, i, destDir, migrate); } } // output the item's dublin core into the item directory private static void writeMetadata(Context c, String schema, Item i, File destDir, boolean migrate) throws Exception { String filename; if (schema.equals(MetadataSchema.DC_SCHEMA)) { filename = "dublin_core.xml"; } else { filename = "metadata_" + schema + ".xml"; } File outFile = new File(destDir, filename); System.out.println("Attempting to create file " + outFile); if (outFile.createNewFile()) { BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(outFile)); DCValue[] dcorevalues = i.getMetadata(schema, Item.ANY, Item.ANY, Item.ANY); // XML preamble byte[] utf8 = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n" .getBytes("UTF-8"); out.write(utf8, 0, utf8.length); String dcTag = "<dublin_core schema=\"" + schema + "\">\n"; utf8 = dcTag.getBytes("UTF-8"); out.write(utf8, 0, utf8.length); String dateIssued = null; String dateAccessioned = null; for (int j = 0; j < dcorevalues.length; j++) { DCValue dcv = dcorevalues[j]; String qualifier = dcv.qualifier; if (qualifier == null) { qualifier = "none"; } utf8 = (" <dcvalue element=\"" + dcv.element + "\" " + "qualifier=\"" + qualifier + "\">" + Utils.addEntities(dcv.value) + "</dcvalue>\n") .getBytes("UTF-8"); if ((!migrate) || (migrate && !( (dcv.element.equals("date") && qualifier.equals("issued")) || (dcv.element.equals("date") && qualifier.equals("accessioned")) || (dcv.element.equals("date") && qualifier.equals("available")) || (dcv.element.equals("identifier") && qualifier.equals("uri")) || (dcv.element.equals("description") && qualifier.equals("provenance")) || (dcv.element.equals("format") && qualifier.equals("extent")) || (dcv.element.equals("format") && qualifier.equals("mimetype"))))) { out.write(utf8, 0, utf8.length); } // Store the date issued and assection to see if they are different // because we need to keep date.issued if they are, when migrating if ((dcv.element.equals("date") && qualifier.equals("issued"))) { dateIssued = dcv.value; } if ((dcv.element.equals("date") && qualifier.equals("accessioned"))) { dateAccessioned = dcv.value; } } // When migrating, only keep date.issued if it is different to date.accessioned if ((migrate) && (!dateIssued.equals(dateAccessioned))) { utf8 = (" <dcvalue element=\"date\" " + "qualifier=\"issued\">" + Utils.addEntities(dateIssued) + "</dcvalue>\n") .getBytes("UTF-8"); out.write(utf8, 0, utf8.length); } utf8 = "</dublin_core>\n".getBytes("UTF-8"); out.write(utf8, 0, utf8.length); out.close(); } else { throw new Exception("Cannot create dublin_core.xml in " + destDir); } } // create the file 'handle' which contains the handle assigned to the item private static void writeHandle(Context c, Item i, File destDir) throws Exception { if (i.getHandle() == null) { return; } String filename = "handle"; File outFile = new File(destDir, filename); if (outFile.createNewFile()) { PrintWriter out = new PrintWriter(new FileWriter(outFile)); out.println(i.getHandle()); // close the contents file out.close(); } else { throw new Exception("Cannot create file " + filename + " in " + destDir); } } /** * Create both the bitstreams and the contents file. Any bitstreams that * were originally registered will be marked in the contents file as such. * However, the export directory will contain actual copies of the content * files being exported. * * @param c * the DSpace context * @param i * the item being exported * @param destDir * the item's export directory * @throws Exception * if there is any problem writing to the export directory */ private static void writeBitstreams(Context c, Item i, File destDir) throws Exception { File outFile = new File(destDir, "contents"); if (outFile.createNewFile()) { PrintWriter out = new PrintWriter(new FileWriter(outFile)); Bundle[] bundles = i.getBundles(); for (int j = 0; j < bundles.length; j++) { // bundles can have multiple bitstreams now... Bitstream[] bitstreams = bundles[j].getBitstreams(); String bundleName = bundles[j].getName(); // GWaller 11/02/09 IssueID #192 Ignore bundles storing archived packages if (bundleName.startsWith(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE) || bundleName.startsWith(Constants.BACKUP_CONTENT_PACKAGE_BUNDLE)){ // simply go to the next bundle continue; } int url_count = 0; for (int k = 0; k < bitstreams.length; k++) { Bitstream b = bitstreams[k]; String myName = b.getName(); String oldName = myName; String description = b.getDescription(); if ((description != null) && (!description.equals(""))) { description = "\tdescription:" + description; } else { description = ""; } int myPrefix = 1; // only used with name conflict InputStream is = b.retrieve(); boolean isDone = false; // done when bitstream is finally // written while (!isDone) { if (myName.contains(File.separator)) { String dirs; if (!bundleName.equals("URL_BUNDLE")) { dirs = myName.substring(0, myName.lastIndexOf(File.separator)); File fdirs = new File(destDir + File.separator + dirs); fdirs.mkdirs(); } } File fout; if (bundleName.equals("URL_BUNDLE")) { url_count++; fout = new File(destDir, "url" + Integer.toString(url_count)); } else fout = new File(destDir, myName); if (fout.createNewFile()) { FileOutputStream fos = new FileOutputStream(fout); Utils.bufferedCopy(is, fos); // close streams is.close(); fos.close(); // write the manifest file entry if (b.isRegisteredBitstream()) { out.println("-r -s " + b.getStoreNumber() + " -f " + myName + "\tbundle:" + bundleName + description); } else { out.println(myName + "\tbundle:" + bundleName + description); } isDone = true; } else { myName = myPrefix + "_" + oldName; // keep // appending // numbers to the // filename until // unique myPrefix++; } } } } // close the contents file out.close(); } else { throw new Exception("Cannot create contents in " + destDir); } } /** * Convenience methot to create export a single Community, Collection, or * Item * * @param dso * - the dspace object to export * @param context * - the dspace context * @throws Exception */ public static void createDownloadableExport(DSpaceObject dso, Context context, boolean migrate) throws Exception { EPerson eperson = context.getCurrentUser(); ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1); list.add(dso); processDownloadableExport(list, context, eperson == null ? null : eperson.getEmail(), migrate); } /** * Convenience method to export a List of dspace objects (Community, * Collection or Item) * * @param dsObjects * - List containing dspace objects * @param context * - the dspace context * @throws Exception */ public static void createDownloadableExport(List<DSpaceObject> dsObjects, Context context, boolean migrate) throws Exception { EPerson eperson = context.getCurrentUser(); processDownloadableExport(dsObjects, context, eperson == null ? null : eperson.getEmail(), migrate); } /** * Convenience methot to create export a single Community, Collection, or * Item * * @param dso * - the dspace object to export * @param context * - the dspace context * @param additionalEmail * - cc email to use * @throws Exception */ public static void createDownloadableExport(DSpaceObject dso, Context context, String additionalEmail, boolean migrate) throws Exception { ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1); list.add(dso); processDownloadableExport(list, context, additionalEmail, migrate); } /** * Convenience method to export a List of dspace objects (Community, * Collection or Item) * * @param dsObjects * - List containing dspace objects * @param context * - the dspace context * @param additionalEmail * - cc email to use * @throws Exception */ public static void createDownloadableExport(List<DSpaceObject> dsObjects, Context context, String additionalEmail, boolean migrate) throws Exception { processDownloadableExport(dsObjects, context, additionalEmail, migrate); } /** * Does the work creating a List with all the Items in the Community or * Collection It then kicks off a new Thread to export the items, zip the * export directory and send confirmation email * * @param dsObjects * - List of dspace objects to process * @param context * - the dspace context * @param additionalEmail * - email address to cc in addition the the current user email * @throws Exception */ private static void processDownloadableExport(List<DSpaceObject> dsObjects, Context context, final String additionalEmail, boolean toMigrate) throws Exception { final EPerson eperson = context.getCurrentUser(); final boolean migrate = toMigrate; // before we create a new export archive lets delete the 'expired' // archives deleteOldExportArchives(eperson.getID()); // keep track of the commulative size of all bitstreams in each of the // items // it will be checked against the config file entry float size = 0; final ArrayList<Integer> items = new ArrayList<Integer>(); for (DSpaceObject dso : dsObjects) { if (dso.getType() == Constants.COMMUNITY) { Community community = (Community) dso; // get all the collections in the community Collection[] collections = community.getCollections(); for (Collection collection : collections) { // get all the items in each collection ItemIterator iitems = collection.getItems(); try { while (iitems.hasNext()) { Item item = iitems.next(); // get all the bundles in the item Bundle[] bundles = item.getBundles(); for (Bundle bundle : bundles) { // GWaller 11/02/09 IssueID #192 Ignore bundles storing archived packages String bundleName = bundle.getName(); if (bundleName.startsWith(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE) || bundleName.startsWith(Constants.BACKUP_CONTENT_PACKAGE_BUNDLE)){ // simply go to the next bundle continue; } // get all the bitstreams in each bundle Bitstream[] bitstreams = bundle.getBitstreams(); for (Bitstream bit : bitstreams) { // add up the size size += bit.getSize(); } } items.add(item.getID()); } } finally { if (iitems != null) iitems.close(); } } } else if (dso.getType() == Constants.COLLECTION) { Collection collection = (Collection) dso; // get all the items in the collection ItemIterator iitems = collection.getItems(); try { while (iitems.hasNext()) { Item item = iitems.next(); // get all thebundles in the item Bundle[] bundles = item.getBundles(); for (Bundle bundle : bundles) { // GWaller 11/02/09 IssueID #192 Ignore bundles storing archived packages String bundleName = bundle.getName(); if (bundleName.startsWith(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE) || bundleName.startsWith(Constants.BACKUP_CONTENT_PACKAGE_BUNDLE)){ // simply go to the next bundle continue; } // get all the bitstreams in the bundle Bitstream[] bitstreams = bundle.getBitstreams(); for (Bitstream bit : bitstreams) { // add up the size size += bit.getSize(); } } items.add(item.getID()); } } finally { if (iitems != null) iitems.close(); } } else if (dso.getType() == Constants.ITEM) { Item item = (Item) dso; // get all the bundles in the item Bundle[] bundles = item.getBundles(); for (Bundle bundle : bundles) { // GWaller 11/02/09 IssueID #192 Ignore bundles storing archived packages String bundleName = bundle.getName(); if (bundleName.startsWith(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE) || bundleName.startsWith(Constants.BACKUP_CONTENT_PACKAGE_BUNDLE)){ // simply go to the next bundle continue; } // get all the bitstreams in the bundle Bitstream[] bitstreams = bundle.getBitstreams(); for (Bitstream bit : bitstreams) { // add up the size size += bit.getSize(); } } items.add(item.getID()); } else { // nothing to do just ignore this type of DSPaceObject } } // check the size of all the bitstreams against the configuration file // entry if it exists String megaBytes = ConfigurationManager .getProperty("org.dspace.app.itemexport.max.size"); if (megaBytes != null) { float maxSize = 0; try { maxSize = Float.parseFloat(megaBytes); } catch (Exception e) { // ignore...configuration entry may not be present } if (maxSize > 0) { if (maxSize < (size / 1048576.00)) { // a megabyte throw new ItemExportException(ItemExportException.EXPORT_TOO_LARGE, "The overall size of this export is too large. Please contact your administrator for more information."); } } } /* * if we have any items to process then create a task and use an * executor service to submit it to the thread pool */ if (items.size() > 0) { FutureTask<Integer> task = new FutureTask<Integer>(new CallableExport(eperson, items, migrate)); es.submit(task); } } /** * Create a file name based on the date and eperson * * @param eperson * - eperson who requested export and will be able to download it * @param date * - the date the export process was created * @return String representing the file name in the form of * 'export_yyy_MMM_dd_count_epersonID' * @throws Exception */ public static String assembleFileName(String type, EPerson eperson, Date date) throws Exception { // to format the date SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MMM_dd"); String downloadDir = getExportDownloadDirectory(eperson.getID()); // used to avoid name collision int count = 1; boolean exists = true; String fileName = null; while (exists) { String myString = Thread.currentThread().toString(); String md5String = null; try { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(myString.getBytes()); md5String = new String(Hex.encodeHex(digest.digest())); } catch (Exception e) { log.warn("Cannot create MD5 hash of thread info", e); } fileName = type + "_export_" + sdf.format(date) + "_" + count + "_" + md5String + "_" + eperson.getID(); exists = new File(downloadDir + System.getProperty("file.separator") + fileName + ".zip").exists(); count++; } return fileName; } /** * Use config file entry for org.dspace.app.itemexport.download.dir and id * of the eperson to create a download directory name * * @param ePersonID * - id of the eperson who requested export archive * @return String representing a directory in the form of * org.dspace.app.itemexport.download.dir/epersonID * @throws Exception */ public static String getExportDownloadDirectory(int ePersonID) throws Exception { String downloadDir = ConfigurationManager.getProperty("org.dspace.app.itemexport.download.dir"); if (downloadDir == null) { throw new Exception( "A dspace.cfg entry for 'org.dspace.app.itemexport.download.dir' does not exist."); } return downloadDir + System.getProperty("file.separator") + ePersonID; } /** * Returns config file entry for org.dspace.app.itemexport.work.dir * * @return String representing config file entry for * org.dspace.app.itemexport.work.dir * @throws Exception */ public static String getExportWorkDirectory() throws Exception { String exportDir = ConfigurationManager .getProperty("org.dspace.app.itemexport.work.dir"); if (exportDir == null) { throw new Exception( "A dspace.cfg entry for 'org.dspace.app.itemexport.work.dir' does not exist."); } return exportDir; } /** * Used to read the export archived. Inteded for download. * * @param fileName * the name of the file to download * @param eperson * the eperson requesting the download * @return an input stream of the file to be downloaded * @throws Exception */ public static InputStream getExportDownloadInputStream(String fileName, EPerson eperson) throws Exception { File file = new File(getExportDownloadDirectory(eperson.getID()) + System.getProperty("file.separator") + fileName); if (file.exists()) { return new FileInputStream(file); } else return null; } /** * Get the file size of the export archive represented by the file name * * @param fileName * name of the file to get the size * @return * @throws Exception */ public static long getExportFileSize(String fileName) throws Exception { String strID = fileName.substring(fileName.lastIndexOf('_') + 1, fileName.lastIndexOf('.')); File file = new File( getExportDownloadDirectory(Integer.parseInt(strID)) + System.getProperty("file.separator") + fileName); if (!file.exists() || !file.isFile()) { throw new FileNotFoundException("The file " + getExportDownloadDirectory(Integer.parseInt(strID)) + System.getProperty("file.separator") + fileName + " does not exist."); } return file.length(); } public static long getExportFileLastModified(String fileName) throws Exception { String strID = fileName.substring(fileName.lastIndexOf('_') + 1, fileName.lastIndexOf('.')); File file = new File( getExportDownloadDirectory(Integer.parseInt(strID)) + System.getProperty("file.separator") + fileName); if (!file.exists() || !file.isFile()) { throw new FileNotFoundException("The file " + getExportDownloadDirectory(Integer.parseInt(strID)) + System.getProperty("file.separator") + fileName + " does not exist."); } return file.lastModified(); } /** * The file name of the export archive contains the eperson id of the person * who created it When requested for download this method can check if the * person requesting it is the same one that created it * * @param context * dspace context * @param fileName * the file name to check auths for * @return true if it is the same person false otherwise */ public static boolean canDownload(Context context, String fileName) { EPerson eperson = context.getCurrentUser(); if (eperson == null) { return false; } String strID = fileName.substring(fileName.lastIndexOf('_') + 1, fileName.lastIndexOf('.')); try { if (Integer.parseInt(strID) == eperson.getID()) { return true; } } catch (Exception e) { return false; } return false; } /** * Reads the download directory for the eperson to see if any export * archives are available * * @param eperson * @return a list of file names representing export archives that have been * processed * @throws Exception */ public static List<String> getExportsAvailable(EPerson eperson) throws Exception { File downloadDir = new File(getExportDownloadDirectory(eperson.getID())); if (!downloadDir.exists() || !downloadDir.isDirectory()) { return null; } List<String> fileNames = new ArrayList<String>(); for (String fileName : downloadDir.list()) { if (fileName.contains("export") && fileName.endsWith(".zip")) { fileNames.add(fileName); } } if (fileNames.size() > 0) { return fileNames; } return null; } /** * A clean up method that is ran before a new export archive is created. It * uses the config file entry 'org.dspace.app.itemexport.life.span.hours' to * determine if the current exports are too old and need pruging * * @param epersonID * - the id of the eperson to clean up * @throws Exception */ public static void deleteOldExportArchives(int epersonID) throws Exception { int hours = ConfigurationManager .getIntProperty("org.dspace.app.itemexport.life.span.hours"); Calendar now = Calendar.getInstance(); now.setTime(new Date()); now.add(Calendar.HOUR, (-hours)); File downloadDir = new File(getExportDownloadDirectory(epersonID)); if (downloadDir.exists()) { File[] files = downloadDir.listFiles(); for (File file : files) { if (file.lastModified() < now.getTimeInMillis()) { file.delete(); } } } } /** * Since the archive is created in a new thread we are unable to communicate * with calling method about success or failure. We accomplis this * communication with email instead. Send a success email once the export * archive is complete and ready for download * * @param context * - the current Context * @param eperson * - eperson to send the email to * @param fileName * - the file name to be downloaded. It is added to the url in * the email * @throws MessagingException */ public static void emailSuccessMessage(Context context, EPerson eperson, String fileName) throws MessagingException { try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); Email email = ConfigurationManager.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_success")); email.addRecipient(eperson.getEmail()); email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/exportdownload/" + fileName); email.addArgument(ConfigurationManager.getProperty("org.dspace.app.itemexport.life.span.hours")); email.send(); } catch (Exception e) { log.warn(LogManager.getHeader(context, "emailSuccessMessage", "cannot notify user of export"), e); } } /** * Since the archive is created in a new thread we are unable to communicate * with calling method about success or failure. We accomplis this * communication with email instead. Send an error email if the export * archive fails * * @param eperson * - EPerson to send the error message to * @param error * - the error message * @throws MessagingException */ public static void emailErrorMessage(EPerson eperson, String error) throws MessagingException { log.warn("An error occured during item export, the user will be notified. " + error); try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); // GWaller IssueID #611 26/1/11 Use the export_error template on error not export_success Email email = ConfigurationManager.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_error")); email.addRecipient(eperson.getEmail()); email.addArgument(error); email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/feedback"); email.send(); } catch (Exception e) { log.warn("error during item export error notification", e); } } public static void zip(String strSource, String target) throws Exception { ZipOutputStream cpZipOutputStream = null; String tempFileName = target + "_tmp"; try { File cpFile = new File(strSource); if (!cpFile.isFile() && !cpFile.isDirectory()) { return; } File targetFile = new File(tempFileName); if (!targetFile.exists()) { targetFile.createNewFile(); } FileOutputStream fos = new FileOutputStream(tempFileName); cpZipOutputStream = new ZipOutputStream(fos); cpZipOutputStream.setLevel(9); zipFiles(cpFile, strSource, tempFileName, cpZipOutputStream); cpZipOutputStream.finish(); cpZipOutputStream.close(); deleteDirectory(cpFile); targetFile.renameTo(new File(target)); } catch (Exception e) { throw e; } } private static void zipFiles(File cpFile, String strSource, String strTarget, ZipOutputStream cpZipOutputStream) throws Exception { int byteCount; final int DATA_BLOCK_SIZE = 2048; FileInputStream cpFileInputStream; if (cpFile.isDirectory()) { File[] fList = cpFile.listFiles(); for (int i = 0; i < fList.length; i++) { zipFiles(fList[i], strSource, strTarget, cpZipOutputStream); } } else { try { if (cpFile.getAbsolutePath().equalsIgnoreCase(strTarget)) { return; } String strAbsPath = cpFile.getPath(); String strZipEntryName = strAbsPath.substring(strSource .length() + 1, strAbsPath.length()); // byte[] b = new byte[ (int)(cpFile.length()) ]; cpFileInputStream = new FileInputStream(cpFile); ZipEntry cpZipEntry = new ZipEntry(strZipEntryName); cpZipOutputStream.putNextEntry(cpZipEntry); byte[] b = new byte[DATA_BLOCK_SIZE]; while ((byteCount = cpFileInputStream.read(b, 0, DATA_BLOCK_SIZE)) != -1) { cpZipOutputStream.write(b, 0, byteCount); } // cpZipOutputStream.write(b, 0, (int)cpFile.length()); cpZipOutputStream.closeEntry(); } catch (Exception e) { throw e; } } } private static boolean deleteDirectory(File path) { if (path.exists()) { File[] files = path.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { deleteDirectory(files[i]); } else { files[i].delete(); } } } boolean pathDeleted = path.delete(); return (pathDeleted); } }