Package org.dspace.app.itemexport

Source Code of org.dspace.app.itemexport.ItemExport

/*
* ItemExport.java
*
* Version: $Revision: 4505 $
*
* Date: $Date: 2009-11-05 13:26:45 +0000 (Thu, 05 Nov 2009) $
*
* Copyright (c) 2002-2009, The DSpace Foundation.  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 DSpace Foundation nor the names of its
* 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.sql.SQLException;
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.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.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
* &amp;, 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;

    /**
     * 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("z", "zip", true, "export as zip file (specify filename e.g. export.zip)");
        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;
        }

        boolean zip = false;
        String zipFileName = "";
        if (line.hasOption('z'))
        {
            zip = true;
            zipFileName = line.getOptionValue('z');
        }

        // 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 (zip)
        {
            ItemIterator items;
            if (myItem != null)
            {
                items = new ItemIterator(c, new ArrayList(myItem.getID()));
            }
            else
            {
                System.out.println("Exporting from collection: " + myIDString);
                items = mycollection.getItems();
            }
            exportAsZip(c, items, destDirName, zipFileName, seqStart, migrate);
        }
        else
        {
            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();
    }

    private 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);
                }*/
                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 (DCValue dcv : dcorevalues)
            {
                String qualifier = dcv.qualifier;

                if (qualifier == null)
                {
                    qualifier = "none";
                }

                String language = dcv.language;

                if (language != null)
                {
                    language = " language=\"" + language + "\"";
                }
                else
                {
                    language = "";
                }

                utf8 = ("  <dcvalue element=\"" + dcv.element + "\" "
                        + "qualifier=\"" + qualifier + "\""
                        + language + ">"
                        + 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.value.startsWith("http://hdl.handle.net/" +
                       ConfigurationManager.getProperty("handle.prefix") + "/"))) ||
                     (dcv.element.equals("description") && qualifier.equals("provenance") &&
                       dcv.value.startsWith("Made available in DSpace")) ||
                     (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 != null) && ((dateAccessioned != null && !dateIssued.equals(dateAccessioned)) || dateAccessioned == null))
            {
                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();

                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 = "";
                    }

                    String primary = "";
                    if (bundles[j].getPrimaryBitstreamID() == b.getID()) {
                        primary = "\tprimary:true ";
                    }

                    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 = myName.substring(0, myName
                                    .lastIndexOf(File.separator));
                            File fdirs = new File(destDir + File.separator
                                    + dirs);
                            fdirs.mkdirs();
                        }

                        File 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 +
                                        primary + description);
                            }
                            else
                            {
                                out.println(myName + "\tbundle:" + bundleName +
                                            primary + 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);
        }
    }

    /**
     * Method to perform an export and save it as a zip file.
     *
     * @param context The DSpace Context
     * @param items The items to export
     * @param destDirName The directory to save the export in
     * @param zipFileName The name to save the zip file as
     * @param seqStart The first number in the sequence
     * @param migrate Whether to use the migrate option or not
     * @throws Exception
     */
    public static void exportAsZip(Context context, ItemIterator items,
                                   String destDirName, String zipFileName,
                                   int seqStart, boolean migrate) throws Exception
    {
        String workDir = getExportWorkDirectory() +
                         System.getProperty("file.separator") +
                         zipFileName;

        File wkDir = new File(workDir);
        if (!wkDir.exists())
        {
            wkDir.mkdirs();
        }

        File dnDir = new File(destDirName);
        if (!dnDir.exists())
        {
            dnDir.mkdirs();
        }

        // export the items using normal export method
        exportItem(context, items, workDir, seqStart, migrate);

        // now zip up the export directory created above
        zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName);
    }

    /**
     * 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)
                            {
                                // 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)
                        {
                            // 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)
                {
                    // 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 kick off annonymous thread
        if (items.size() > 0)
        {
            Thread go = new Thread()
            {
                public void run()
                {
                    Context context = null;
                    ItemIterator iitems = null;
                    try
                    {
                        // create a new dspace context
                        context = new Context();
                        // ignore auths
                        context.setIgnoreAuthorization(true);
                        iitems = new ItemIterator(context, items);

                        String fileName = assembleFileName("item", eperson,
                                new Date());
                        String workDir = getExportWorkDirectory()
                                + System.getProperty("file.separator")
                                + fileName;
                        String downloadDir = getExportDownloadDirectory(eperson
                                .getID());

                        File wkDir = new File(workDir);
                        if (!wkDir.exists())
                        {
                            wkDir.mkdirs();
                        }

                        File dnDir = new File(downloadDir);
                        if (!dnDir.exists())
                        {
                            dnDir.mkdirs();
                        }

                        // export the items using normal export method
                        exportItem(context, iitems, workDir, 1, migrate);
                        // now zip up the export directory created above
                        zip(workDir, downloadDir
                                + System.getProperty("file.separator")
                                + fileName + ".zip");
                        // email message letting user know the file is ready for
                        // download
                        emailSuccessMessage(context, eperson, fileName + ".zip");
                        // return to enforcing auths
                        context.setIgnoreAuthorization(false);
                    }
                    catch (Exception e1)
                    {
                        try
                        {
                            emailErrorMessage(eperson, e1.getMessage());
                        }
                        catch (Exception e)
                        {
                            // wont throw here
                        }
                        throw new RuntimeException(e1);
                    }
                    finally
                    {
                        if (iitems != null)
                            iitems.close();
                       
                        // Make sure the database connection gets closed in all conditions.
                      try {
              context.complete();
            } catch (SQLException sqle) {
              context.abort();
            }
                    }
                }

            };

            go.isDaemon();
            go.start();
        }
    }

    /**
     * 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)
        {
            fileName = type + "_export_" + sdf.format(date) + "_" + count + "_"
                    + 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);
            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();

            // Fix issue on Windows with stale file handles open before trying to delete them
            System.gc();

            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);
    }

}
TOP

Related Classes of org.dspace.app.itemexport.ItemExport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.