diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExport.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExport.java
index c9e0a8b42a..ad2b2154a7 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExport.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExport.java
@@ -41,13 +41,34 @@ 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.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
+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.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.mail.BodyPart;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
@@ -57,13 +78,17 @@ import org.apache.commons.cli.PosixParser;
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.Utils;
+import org.dspace.eperson.EPerson;
import org.dspace.handle.HandleManager;
/**
@@ -81,459 +106,1077 @@ import org.dspace.handle.HandleManager;
* issues -doesn't handle special characters in metadata (needs to turn &'s into
* &, etc.)
*
- * Modified by David Little, UCSD Libraries 12/21/04 to
- * allow the registration of files (bitstreams) into DSpace.
+ * 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;
-
- /*
- *
- */
- 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("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'));
- }
-
- // 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);
- }
- else
- {
- System.out.println("Exporting from collection: " + myIDString);
-
- // it's a collection, so do a bunch of items
- ItemIterator i = mycollection.getItems();
-
- exportItem(c, i, destDirName, seqStart);
- }
-
- c.complete();
- }
-
- private static void exportItem(Context c, ItemIterator i,
- String destDirName, int seqStart) 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 + dir.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);
- mySequenceNumber++;
- }
- }
-
- private static void exportItem(Context c, Item myItem, String destDirName,
- int seqStart) 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);
- writeBitstreams(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)
- 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);
- }
- }
-
- // output the item's dublin core into the item directory
- private static void writeMetadata(Context c, String schema, Item i, File destDir)
- 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 = "\n"
- .getBytes("UTF-8");
- out.write(utf8, 0, utf8.length);
-
- String dcTag = "\n";
- utf8 = dcTag.getBytes("UTF-8");
- out.write(utf8, 0, utf8.length);
-
- for (int j = 0; j < dcorevalues.length; j++)
- {
- DCValue dcv = dcorevalues[j];
- String qualifier = dcv.qualifier;
-
- if (qualifier == null)
- {
- qualifier = "none";
- }
-
- utf8 = (" "
- + Utils.addEntities(dcv.value) + "\n").getBytes("UTF-8");
-
- out.write(utf8, 0, utf8.length);
- }
-
- utf8 = "\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
- {
- 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;
- int myPrefix = 1; // only used with name conflict
-
- InputStream is = b.retrieve();
-
- boolean isDone = false; // done when bitstream is finally
- // written
-
- while (!isDone)
- {
- 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);
- } else {
- out.println(myName + "\tbundle:" + bundleName);
- }
-
- 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);
- }
- }
+public class ItemExport {
+ private static final int SUBDIR_LIMIT = 0;
+
+ /**
+ * used for export download
+ */
+ public static final String COMPRESSED_EXPORT_MIME_TYPE = "application/zip";
+
+ /*
+ *
+ */
+ 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("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'));
+ }
+
+ // 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);
+ } else {
+ System.out.println("Exporting from collection: " + myIDString);
+
+ // it's a collection, so do a bunch of items
+ ItemIterator i = mycollection.getItems();
+
+ exportItem(c, i, destDirName, seqStart);
+ }
+
+ c.complete();
+ }
+
+ private static void exportItem(Context c, ItemIterator i,
+ String destDirName, int seqStart) 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);
+ mySequenceNumber++;
+ }
+ }
+
+ private static void exportItem(Context c, Item myItem, String destDirName,
+ int seqStart) 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);
+ writeBitstreams(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)
+ 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);
+ }
+ }
+
+ // output the item's dublin core into the item directory
+ private static void writeMetadata(Context c, String schema, Item i,
+ File destDir) 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 = "\n"
+ .getBytes("UTF-8");
+ out.write(utf8, 0, utf8.length);
+
+ String dcTag = "\n";
+ utf8 = dcTag.getBytes("UTF-8");
+ out.write(utf8, 0, utf8.length);
+
+ for (int j = 0; j < dcorevalues.length; j++) {
+ DCValue dcv = dcorevalues[j];
+ String qualifier = dcv.qualifier;
+
+ if (qualifier == null) {
+ qualifier = "none";
+ }
+
+ utf8 = (" "
+ + Utils.addEntities(dcv.value) + "\n")
+ .getBytes("UTF-8");
+
+ out.write(utf8, 0, utf8.length);
+ }
+
+ utf8 = "\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;
+ int myPrefix = 1; // only used with name conflict
+
+ InputStream is = b.retrieve();
+
+ boolean isDone = false; // done when bitstream is finally
+ // written
+
+ while (!isDone) {
+ 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);
+ } else {
+ out.println(myName + "\tbundle:" + bundleName);
+ }
+
+ 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) throws Exception {
+ EPerson eperson = context.getCurrentUser();
+ ArrayList list = new ArrayList(1);
+ list.add(dso);
+ processDownloadableExport(list, context, eperson == null ? null
+ : eperson.getEmail());
+ }
+
+ /**
+ * 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 dsObjects,
+ Context context) throws Exception {
+ EPerson eperson = context.getCurrentUser();
+ processDownloadableExport(dsObjects, context, eperson == null ? null
+ : eperson.getEmail());
+ }
+
+ /**
+ * 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) throws Exception {
+ ArrayList list = new ArrayList(1);
+ list.add(dso);
+ processDownloadableExport(list, context, additionalEmail);
+ }
+
+ /**
+ * 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 dsObjects,
+ Context context, String additionalEmail) throws Exception {
+ processDownloadableExport(dsObjects, context, additionalEmail);
+ }
+
+ /**
+ * 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 dsObjects,
+ Context context, final String additionalEmail) throws Exception {
+ final EPerson eperson = context.getCurrentUser();
+
+ // 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 items = new ArrayList();
+ 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();
+ 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());
+ }
+ }
+ } else if (dso.getType() == Constants.COLLECTION) {
+ Collection collection = (Collection) dso;
+ // get all the items in the collection
+ ItemIterator iitems = collection.getItems();
+ 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());
+ }
+ } 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 Exception(
+ "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;
+ try {
+ // create a new dspace context
+ context = new Context();
+ // ignore auths
+ context.setIgnoreAuthorization(true);
+ ItemIterator iitems = new ItemIterator(context, items);
+
+ String fileName = assembleFileName(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);
+ // 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(eperson.getEmail(),
+ ConfigurationManager
+ .getProperty("mail.from.address"),
+ additionalEmail, fileName + ".zip");
+ // return to enforcing auths
+ context.setIgnoreAuthorization(false);
+ } catch (Exception e1) {
+ try {
+ emailErrorMessage(eperson.getEmail(),
+ ConfigurationManager
+ .getProperty("mail.from.address"),
+ additionalEmail, e1.getMessage());
+ } catch (Exception e) {
+ // wont throw here
+ }
+ throw new RuntimeException(e1);
+ }
+ }
+
+ };
+
+ 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
+ */
+ private static String assembleFileName(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 = "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
+ */
+ private 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
+ */
+ private 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
+ * @param eperson
+ * the eperson requesting file
+ * @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 getExportsAvailable(EPerson eperson)
+ throws Exception {
+ File downloadDir = new File(getExportDownloadDirectory(eperson.getID()));
+ if (!downloadDir.exists() || !downloadDir.isDirectory()) {
+ return null;
+ }
+
+ List fileNames = new ArrayList();
+
+ 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
+ */
+ private 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 toMail -
+ * email to send message to
+ * @param fromMail -
+ * email for the from field
+ * @param ccMail -
+ * carbon copy email
+ * @param fileName -
+ * the file name to be downloaded. It is added to the url in the
+ * email
+ * @throws MessagingException
+ */
+ private static void emailSuccessMessage(String toMail, String fromMail,
+ String ccMail, String fileName) throws MessagingException {
+ StringBuffer content = new StringBuffer();
+ content
+ .append("The item export you requested from the repositry is now ready for download.");
+ content.append(System.getProperty("line.separator"));
+ content.append(System.getProperty("line.separator"));
+ content
+ .append("You may download the compressed file using the following web address:");
+ content.append(System.getProperty("line.separator"));
+ content.append(ConfigurationManager.getProperty("dspace.url"));
+ content.append("/exportdownload/");
+ content.append(fileName);
+ content.append(System.getProperty("line.separator"));
+ content.append(System.getProperty("line.separator"));
+ content.append("Tis file will remain available for at least ");
+ content.append(ConfigurationManager
+ .getProperty("org.dspace.app.itemexport.life.span.hours"));
+ content.append(" hours.");
+ content.append(System.getProperty("line.separator"));
+ content.append(System.getProperty("line.separator"));
+ content.append("Thank you");
+
+ sendMessage(toMail, fromMail, ccMail,
+ "Item export requested is ready for download", content);
+ }
+
+ /**
+ * 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 toMail -
+ * email to send message to
+ * @param fromMail -
+ * email for the from field
+ * @param ccMail -
+ * carbon copy email
+ * @param fileName -
+ * the file name to be downloaded. It is added to the url in the
+ * email
+ * @throws MessagingException
+ */
+ private static void emailErrorMessage(String toMail, String fromMail,
+ String ccMail, String error) throws MessagingException {
+ StringBuffer content = new StringBuffer();
+ content.append("The item export you requested was not completed.");
+ content.append(System.getProperty("line.separator"));
+ content.append(System.getProperty("line.separator"));
+ content
+ .append("For more infrmation you may contact your system administrator.");
+ content.append(System.getProperty("line.separator"));
+ content.append(System.getProperty("line.separator"));
+ content.append("Error message received: ");
+ content.append(error);
+ content.append(System.getProperty("line.separator"));
+ content.append(System.getProperty("line.separator"));
+ content.append("Thank you");
+
+ sendMessage(toMail, fromMail, ccMail,
+ "Item export requested was not completed", content);
+ }
+
+ private static void sendMessage(String toMail, String fromMail,
+ String ccMail, String subject, StringBuffer content)
+ throws MessagingException {
+ try {
+ if (toMail == null || !toMail.contains("@")) {
+ return;
+ }
+
+ // Get the mail configuration properties
+ String server = ConfigurationManager.getProperty("mail.server");
+
+ // Set up properties for mail session
+ Properties props = System.getProperties();
+ props.put("mail.smtp.host", server);
+
+ // Get session
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage msg = new MimeMessage(session);
+ Multipart multipart = new MimeMultipart();
+
+ // create the first part of the email
+ BodyPart messageBodyPart = new MimeBodyPart();
+ messageBodyPart.setText(content.toString());
+
+ multipart.addBodyPart(messageBodyPart);
+ msg.setContent(multipart);
+ msg.setFrom(new InternetAddress(fromMail));
+ msg.addRecipient(Message.RecipientType.TO, new InternetAddress(
+ toMail));
+ if (ccMail != null && ccMail.contains("@")) {
+ msg.addRecipient(Message.RecipientType.CC, new InternetAddress(
+ ccMail));
+ }
+ msg.setSentDate(new Date());
+ msg.setSubject(subject);
+ Transport.send(msg);
+ } catch (MessagingException e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ private 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);
+ }
+
}
diff --git a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/ItemExportArchiveServlet.java b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/ItemExportArchiveServlet.java
new file mode 100644
index 0000000000..15ff3d9de2
--- /dev/null
+++ b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/ItemExportArchiveServlet.java
@@ -0,0 +1,158 @@
+/*
+ * ItemExportArchiveServlet.java
+ *
+ * Version: $Revision: 2073 $
+ *
+ * Date: $Date: 2007-07-19 11:45:10 -0500 (Thu, 19 Jul 2007) $
+ *
+ * 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.webui.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.SQLException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+import org.dspace.app.itemexport.ItemExport;
+import org.dspace.app.webui.util.JSPManager;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.core.ConfigurationManager;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.core.LogManager;
+import org.dspace.core.Utils;
+
+/**
+ * Servlet for retrieving item export archives. The bits are simply piped to the
+ * user. If there is an If-Modified-Since
header, only a 304
+ * status code is returned if the containing item has not been modified since
+ * that date.
+ *
+ * /exportdownload/filename
+ *
+ * @author Jay Paz
+ */
+public class ItemExportArchiveServlet extends DSpaceServlet {
+ /** log4j category */
+ private static Logger log = Logger
+ .getLogger(ItemExportArchiveServlet.class);
+
+ /**
+ * Threshold on export size size before content-disposition will be set.
+ */
+ private int threshold;
+
+ @Override
+ public void init(ServletConfig arg0) throws ServletException {
+
+ super.init(arg0);
+ threshold = ConfigurationManager
+ .getIntProperty("webui.content_disposition_threshold");
+ }
+
+ @Override
+ protected void doDSGet(Context context, HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException,
+ SQLException, AuthorizeException {
+ String filename = null;
+
+ filename = request.getPathInfo().substring(
+ request.getPathInfo().lastIndexOf('/')+1);
+ System.out.println(filename);
+
+ if (ItemExport.canDownload(context, filename)) {
+ try {
+ InputStream exportStream = ItemExport
+ .getExportDownloadInputStream(filename, context
+ .getCurrentUser());
+
+ if (exportStream == null || filename == null) {
+ // No bitstream found or filename was wrong -- ID invalid
+ log.info(LogManager.getHeader(context, "invalid_id",
+ "path=" + filename));
+ JSPManager.showInvalidIDError(request, response, filename,
+ Constants.BITSTREAM);
+
+ return;
+ }
+
+ log.info(LogManager.getHeader(context,
+ "download_export_archive", "filename=" + filename));
+
+ // Modification date
+ // TODO: Currently the date of the item, since we don't have
+ // dates
+ // for files
+ long lastModified = ItemExport
+ .getExportFileLastModified(filename);
+ response.setDateHeader("Last-Modified", lastModified);
+
+ // Check for if-modified-since header
+ long modSince = request.getDateHeader("If-Modified-Since");
+
+ if (modSince != -1 && lastModified < modSince) {
+ // Item has not been modified since requested date,
+ // hence bitstream has not; return 304
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ return;
+ }
+
+ // Set the response MIME type
+ response.setContentType(ItemExport.COMPRESSED_EXPORT_MIME_TYPE);
+
+ // Response length
+ long size = ItemExport.getExportFileSize(filename);
+ response.setHeader("Content-Length", String.valueOf(size));
+
+ response.setHeader("Content-Disposition",
+ "attachment;filename=" + filename);
+
+ Utils.bufferedCopy(exportStream, response.getOutputStream());
+ exportStream.close();
+ response.getOutputStream().flush();
+ } catch (Exception e) {
+ throw new ServletException(e);
+ }
+ } else {
+ throw new AuthorizeException(
+ "You are not authorized to download this Export Archive.");
+ }
+ }
+
+}
diff --git a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MyDSpaceServlet.java b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MyDSpaceServlet.java
index 2fae1e4146..2f6e1e026d 100644
--- a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MyDSpaceServlet.java
+++ b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MyDSpaceServlet.java
@@ -50,11 +50,13 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.dspace.app.webui.util.JSPManager;
+import org.dspace.app.itemexport.ItemExport;
import org.dspace.app.util.SubmissionConfigReader;
import org.dspace.app.util.SubmissionConfig;
import org.dspace.app.webui.util.UIUtil;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
+import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.ItemIterator;
import org.dspace.content.SupervisedItem;
@@ -73,6 +75,7 @@ import org.dspace.workflow.WorkflowManager;
* Servlet for constructing the components of the "My DSpace" page
*
* @author Robert Tansley
+ * @author Jay Paz
* @version $Id$
*/
public class MyDSpaceServlet extends DSpaceServlet
@@ -94,6 +97,9 @@ public class MyDSpaceServlet extends DSpaceServlet
/** The "reason for rejection" page */
public static final int REJECT_REASON_PAGE = 4;
+
+ /** The "request export archive for download" page */
+ public static final int REQUEST_EXPORT_ARCHIVE = 5;
protected void doDSGet(Context context, HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException,
@@ -135,6 +141,10 @@ public class MyDSpaceServlet extends DSpaceServlet
case REJECT_REASON_PAGE:
processRejectReason(context, request, response);
+ break;
+ case REQUEST_EXPORT_ARCHIVE:
+ processExportArchive(context, request, response);
+
break;
default:
@@ -576,6 +586,99 @@ public class MyDSpaceServlet extends DSpaceServlet
}
}
+ private void processExportArchive(Context context,
+ HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
+
+ if (request.getParameter("item_id") != null) {
+ Item item = null;
+ try {
+ item = Item.find(context, Integer.parseInt(request
+ .getParameter("item_id")));
+ } catch (Exception e) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ }
+
+ if (item == null) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ } else {
+ try {
+ ItemExport.createDownloadableExport(item, context);
+ } catch (Exception e) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ }
+ }
+
+ // success
+ JSPManager.showJSP(request, response, "/mydspace/task-complete.jsp");
+ } else if (request.getParameter("collection_id") != null) {
+ Collection col = null;
+ try {
+ col = Collection.find(context, Integer.parseInt(request
+ .getParameter("collection_id")));
+ } catch (Exception e) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ }
+
+ if (col == null) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ } else {
+ try {
+ ItemExport.createDownloadableExport(col, context);
+ } catch (Exception e) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ }
+ }
+ JSPManager.showJSP(request, response, "/mydspace/task-complete.jsp");
+ } else if (request.getParameter("community_id") != null) {
+ Community com = null;
+ try {
+ com = Community.find(context, Integer.parseInt(request
+ .getParameter("community_id")));
+ } catch (Exception e) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ }
+
+ if (com == null) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ } else {
+ try {
+ org.dspace.app.itemexport.ItemExport.createDownloadableExport(com, context);
+ } catch (Exception e) {
+ log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
+ .getRequestLogInfo(request)));
+ JSPManager.showIntegrityError(request, response);
+ return;
+ }
+ }
+ JSPManager.showJSP(request, response, "/mydspace/task-complete.jsp");
+ }
+
+
+ }
// ****************************************************************
// ****************************************************************
// METHODS FOR SHOWING FORMS
@@ -623,7 +726,16 @@ public class MyDSpaceServlet extends DSpaceServlet
SupervisedItem[] supervisedItems = SupervisedItem.findbyEPerson(
context, currentUser);
-
+ // export archives available for download
+ List exportArchives = null;
+ try{
+ exportArchives = ItemExport.getExportsAvailable(currentUser);
+ }
+ catch (Exception e) {
+ // nothing to do they just have no export archives available for download
+ }
+
+
// Set attributes
request.setAttribute("mydspace.user", currentUser);
request.setAttribute("workspace.items", workspaceItems);
@@ -633,6 +745,7 @@ public class MyDSpaceServlet extends DSpaceServlet
request.setAttribute("group.memberships", memberships);
request.setAttribute("display.groupmemberships", new Boolean(displayMemberships));
request.setAttribute("supervised.items", supervisedItems);
+ request.setAttribute("export.archives", exportArchives);
// Forward to main mydspace page
JSPManager.showJSP(request, response, "/mydspace/main.jsp");
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml
index a1dff39e94..f6f9e9c08b 100644
--- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml
@@ -164,6 +164,11 @@
org.dspace.app.webui.servlet.BitstreamServlet
+
+ exportdownload
+ org.dspace.app.webui.servlet.ItemExportArchiveServlet
+
+
browse
org.dspace.app.webui.servlet.BrowserServlet
@@ -413,6 +418,11 @@
/bitstream/*
+
+ exportdownload
+ /exportdownload/*
+
+
browse
/browse
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp
index 4e71344cf1..8d03a08572 100644
--- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp
+++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp
@@ -126,6 +126,7 @@
ItemCounter ic = new ItemCounter(UIUtil.obtainContext(request));
%>
+<%@page import="org.dspace.app.webui.servlet.MyDSpaceServlet"%>
@@ -288,6 +289,17 @@
+<% } %>
+<% if( editor_button || admin_button) { %>
+
+
+
+ |
+
<% } %>
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp
index 9f9c1b0a9b..87ef7a580f 100644
--- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp
+++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp
@@ -103,6 +103,7 @@
ItemCounter ic = new ItemCounter(UIUtil.obtainContext(request));
%>
+<%@page import="org.dspace.app.webui.servlet.MyDSpaceServlet"%>
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/mydspace/main.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/mydspace/main.jsp
index be3795ba58..2b0418cb08 100644
--- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/mydspace/main.jsp
+++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/mydspace/main.jsp
@@ -73,6 +73,7 @@
<%@ page import="org.dspace.eperson.Group" %>
<%@ page import="org.dspace.workflow.WorkflowItem" %>
<%@ page import="org.dspace.workflow.WorkflowManager" %>
+<%@ page import="java.util.List" %>
<%
EPerson user = (EPerson) request.getAttribute("mydspace.user");
@@ -95,6 +96,8 @@
SupervisedItem[] supervisedItems =
(SupervisedItem[]) request.getAttribute("supervised.items");
+ List exportsAvailable = (List)request.getAttribute("export.archives");
+
// Is the logged in user an admin
Boolean displayMembership = (Boolean)request.getAttribute("display.groupmemberships");
boolean displayGroupMembership = (displayMembership == null ? false : displayMembership.booleanValue());
@@ -438,4 +441,13 @@
<%
}
%>
+
+ <%if(exportsAvailable!=null && exportsAvailable.size()>0){ %>
+
+
+ <%for(String fileName:exportsAvailable){%>
+ - " title="<%= fileName %>"><%=fileName%>
+ <% } %>
+
+ <%} %>
diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/administrative/ItemExport.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/administrative/ItemExport.java
new file mode 100644
index 0000000000..a517617d74
--- /dev/null
+++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/administrative/ItemExport.java
@@ -0,0 +1,323 @@
+/*
+ * ItemExport.java
+ *
+ * Version: $Revision: 1.3 $
+ *
+ * Date: $Date: 2006/07/13 23:20:54 $
+ *
+ * Copyright (c) 2002, 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.xmlui.aspect.administrative;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.ProcessingException;
+import org.apache.cocoon.caching.CacheableProcessingComponent;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.Response;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.util.HashUtil;
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.source.impl.validity.NOPValidity;
+import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer;
+import org.dspace.app.xmlui.utils.DSpaceValidity;
+import org.dspace.app.xmlui.utils.UIException;
+import org.dspace.app.xmlui.wing.Message;
+import org.dspace.app.xmlui.wing.WingException;
+import org.dspace.app.xmlui.wing.element.Body;
+import org.dspace.app.xmlui.wing.element.Division;
+import org.dspace.app.xmlui.wing.element.List;
+import org.dspace.app.xmlui.wing.element.PageMeta;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.Collection;
+import org.dspace.content.Community;
+import org.dspace.content.Item;
+import org.dspace.eperson.Group;
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * Create the ability to view currently available export archives for download.
+ *
+ * @author Jay Paz
+ */
+public class ItemExport extends AbstractDSpaceTransformer implements
+ CacheableProcessingComponent {
+ private final static Message T_dspace_home = message("xmlui.general.dspace_home");
+
+ private static final Message T_main_head = message("xmlui.administrative.ItemExport.head");
+
+ private static final Message T_export_bad_item_id = message("xmlui.administrative.ItemExport.item.id.error");
+
+ private static final Message T_export_bad_col_id = message("xmlui.administrative.ItemExport.collection.id.error");
+
+ private static final Message T_export_bad_community_id = message("xmlui.administrative.ItemExport.community.id.error");
+
+ private static final Message T_export_item_not_found = message("xmlui.administrative.ItemExport.item.not.found");
+
+ private static final Message T_export_col_not_found = message("xmlui.administrative.ItemExport.collection.not.found");
+
+ private static final Message T_export_community_not_found = message("xmlui.administrative.ItemExport.community.not.found");
+
+ private static final Message T_item_export_success = message("xmlui.administrative.ItemExport.item.success");
+
+ private static final Message T_col_export_success = message("xmlui.administrative.ItemExport.collection.success");
+
+ private static final Message T_community_export_success = message("xmlui.administrative.ItemExport.community.success");
+
+ private static final Message T_avail_head = message("xmlui.administrative.ItemExport.available.head");
+
+ /** The Cocoon request */
+ Request request;
+
+ /** The Cocoon response */
+ Response response;
+
+ java.util.List errors;
+
+ java.util.List availableExports;
+
+ Message message;
+
+ /** Cached validity object */
+ private SourceValidity validity;
+
+ @Override
+ public void setup(SourceResolver resolver, Map objectModel, String src,
+ Parameters parameters) throws ProcessingException, SAXException,
+ IOException {
+ super.setup(resolver, objectModel, src, parameters);
+ this.objectModel = objectModel;
+ this.request = ObjectModelHelper.getRequest(objectModel);
+ this.response = ObjectModelHelper.getResponse(objectModel);
+ try {
+ availableExports = org.dspace.app.itemexport.ItemExport
+ .getExportsAvailable(context.getCurrentUser());
+ } catch (Exception e) {
+ // nothing to do
+ }
+ errors = new ArrayList();
+ if (request.getParameter("itemID") != null) {
+ Item item = null;
+ try {
+ item = Item.find(context, Integer.parseInt(request
+ .getParameter("itemID")));
+ } catch (Exception e) {
+ errors.add(T_export_bad_item_id);
+ }
+
+ if (item == null) {
+ errors.add(T_export_item_not_found);
+ } else {
+ try {
+ org.dspace.app.itemexport.ItemExport
+ .createDownloadableExport(item, context);
+ } catch (Exception e) {
+ errors.add(message(e.getMessage()));
+ }
+ }
+ if (errors.size() <= 0)
+ message = T_item_export_success;
+ } else if (request.getParameter("collectionID") != null) {
+ Collection col = null;
+ try {
+ col = Collection.find(context, Integer.parseInt(request
+ .getParameter("collectionID")));
+ } catch (Exception e) {
+ errors.add(T_export_bad_col_id);
+ }
+
+ if (col == null) {
+ errors.add(T_export_col_not_found);
+ } else {
+ try {
+ org.dspace.app.itemexport.ItemExport
+ .createDownloadableExport(col, context);
+ } catch (Exception e) {
+ errors.add(message(e.getMessage()));
+ }
+ }
+ if (errors.size() <= 0)
+ message = T_col_export_success;
+ } else if (request.getParameter("communityID") != null) {
+ Community com = null;
+ try {
+ com = Community.find(context, Integer.parseInt(request
+ .getParameter("communityID")));
+ } catch (Exception e) {
+ errors.add(T_export_bad_community_id);
+ }
+
+ if (com == null) {
+ errors.add(T_export_community_not_found);
+ } else {
+ try {
+ org.dspace.app.itemexport.ItemExport
+ .createDownloadableExport(com, context);
+ } catch (Exception e) {
+ errors.add(message(e.getMessage()));
+ }
+ }
+ if (errors.size() <= 0)
+ message = T_community_export_success;
+ }
+ }
+
+ /**
+ * Generate the unique cache key.
+ *
+ * @return The generated key hashes the src
+ */
+ public Serializable getKey() {
+ Request request = ObjectModelHelper.getRequest(objectModel);
+
+ // Special case, don't cache anything if the user is logging
+ // in. The problem occures because of timming, this cache key
+ // is generated before we know whether the operation has
+ // succeded or failed. So we don't know whether to cache this
+ // under the user's specific cache or under the anonymous user.
+ if (request.getParameter("login_email") != null
+ || request.getParameter("login_password") != null
+ || request.getParameter("login_realm") != null) {
+ return "0";
+ }
+
+ String key;
+ if (context.getCurrentUser() != null) {
+ key = context.getCurrentUser().getEmail();
+ if (availableExports != null && availableExports.size() > 0) {
+ for (String fileName : availableExports) {
+ key += ":" + fileName;
+ }
+ }
+
+ if (request.getQueryString() != null) {
+ key += request.getQueryString();
+ }
+ } else
+ key = "anonymous";
+
+ return HashUtil.hash(key);
+ }
+
+ /**
+ * Generate the validity object.
+ *
+ * @return The generated validity object or null if the
+ * component is currently not cacheable.
+ */
+ public SourceValidity getValidity() {
+ if (this.validity == null) {
+ // Only use the DSpaceValidity object is someone is logged in.
+ if (context.getCurrentUser() != null) {
+ try {
+ DSpaceValidity validity = new DSpaceValidity();
+
+ validity.add(eperson);
+
+ Group[] groups = Group.allMemberGroups(context, eperson);
+ for (Group group : groups) {
+ validity.add(group);
+ }
+
+ this.validity = validity.complete();
+ } catch (SQLException sqle) {
+ // Just ignore it and return invalid.
+ }
+ } else {
+ this.validity = NOPValidity.SHARED_INSTANCE;
+ }
+ }
+ return this.validity;
+ }
+
+ /**
+ * Add Page metadata.
+ */
+ public void addPageMeta(PageMeta pageMeta) throws SAXException,
+ WingException, UIException, SQLException, IOException,
+ AuthorizeException {
+ pageMeta.addMetadata("title").addContent(T_main_head);
+
+ pageMeta.addTrailLink(contextPath + "/", T_dspace_home);
+ pageMeta.addTrail().addContent(T_main_head);
+ }
+
+ public void addBody(Body body) throws SAXException, WingException,
+ UIException, SQLException, IOException, AuthorizeException {
+ Division main = body.addDivision("export_main");
+ main.setHead(T_main_head);
+
+ if (message != null) {
+ main.addDivision("success", "success").addPara(message);
+ }
+
+ if (errors.size() > 0) {
+ Division errors = main.addDivision("export-errors", "error");
+ for (Message error : this.errors) {
+ errors.addPara(error);
+ }
+ }
+
+ if (availableExports != null && availableExports.size() > 0) {
+ Division avail = main.addDivision("available-exports",
+ "available-exports");
+ avail.setHead(T_avail_head);
+
+ List fileList = avail.addList("available-files", List.TYPE_ORDERED);
+ for (String fileName : availableExports) {
+ fileList.addItem().addXref(
+ this.contextPath + "/exportdownload/" + fileName,
+ fileName);
+ }
+ }
+ }
+
+ /**
+ * recycle
+ */
+ public void recycle() {
+ this.validity = null;
+ this.errors = null;
+ this.message = null;
+ this.availableExports = null;
+ super.recycle();
+ }
+
+}
diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/administrative/Navigation.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/administrative/Navigation.java
index c3d1471e1d..a77b4a106b 100644
--- a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/administrative/Navigation.java
+++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/administrative/Navigation.java
@@ -42,13 +42,18 @@ package org.dspace.app.xmlui.aspect.administrative;
import java.io.IOException;
import java.io.Serializable;
import java.sql.SQLException;
+import java.util.Map;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.util.HashUtil;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.NOPValidity;
+import org.dspace.app.itemexport.ItemExport;
import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer;
import org.dspace.app.xmlui.utils.DSpaceValidity;
import org.dspace.app.xmlui.utils.HandleUtil;
@@ -75,6 +80,7 @@ import org.xml.sax.SAXException;
* @author Scott Phillips
* @author Afonso Araujo Neto (internationalization)
* @author Alexey Maslov
+ * @author Jay Paz
*/
public class Navigation extends AbstractDSpaceTransformer implements CacheableProcessingComponent
{
@@ -101,12 +107,18 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
private static final Message T_statistics = message("xmlui.administrative.Navigation.statistics");
-
+ private static final Message T_context_export_item = message("xmlui.administrative.Navigation.context_export_item");
+ private static final Message T_context_export_collection = message("xmlui.administrative.Navigation.context_export_collection");
+ private static final Message T_context_export_community = message("xmlui.administrative.Navigation.context_export_community");
+ private static final Message T_account_export = message("xmlui.administrative.Navigation.account_export");
/** Cached validity object */
private SourceValidity validity;
+ /** exports available for download */
+ java.util.List availableExports = null;
+
/**
* Generate the unique cache key.
*
@@ -128,9 +140,16 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
return "0";
}
- String key;
+ String key;
if (context.getCurrentUser() != null)
- key = context.getCurrentUser().getEmail();
+ {
+ key = context.getCurrentUser().getEmail();
+ if(availableExports!=null && availableExports.size()>0){
+ for(String fileName:availableExports){
+ key+= ":"+fileName;
+ }
+ }
+ }
else
key = "anonymous";
@@ -176,6 +195,20 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
return this.validity;
}
+
+ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters) throws ProcessingException, SAXException, IOException {
+ super.setup(resolver, objectModel, src, parameters);
+ try{
+ availableExports = ItemExport.getExportsAvailable(context.getCurrentUser());
+ }
+ catch (Exception e) {
+ // nothing to do
+ }
+ }
+
+
+
+
public void addOptions(Options options) throws SAXException, WingException,
UIException, SQLException, IOException, AuthorizeException
{
@@ -183,10 +216,16 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
* even if they are never used
*/
options.addList("browse");
- options.addList("account");
+ List account = options.addList("account");
List context = options.addList("context");
List admin = options.addList("administrative");
+ // My Account options
+ if(availableExports!=null && availableExports.size()>0){
+ account.addItem().addXref(contextPath+"/admin/export", T_account_export);
+ }
+
+
// Context Administrative options
DSpaceObject dso = HandleUtil.obtainHandle(objectModel);
if (dso instanceof Item)
@@ -197,6 +236,7 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
{
context.setHead(T_context_head);
context.addItem().addXref(contextPath+"/admin/item?itemID="+item.getID(), T_context_edit_item);
+ context.addItem().addXref(contextPath+"/admin/export?itemID="+item.getID(), T_context_export_item );
}
}
else if (dso instanceof Collection)
@@ -209,7 +249,8 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
{
context.setHead(T_context_head);
context.addItemXref(contextPath+"/admin/collection?collectionID=" + collection.getID(), T_context_edit_collection);
- context.addItemXref(contextPath+"/admin/mapper?collectionID="+collection.getID(), T_context_item_mapper);
+ context.addItemXref(contextPath+"/admin/mapper?collectionID="+collection.getID(), T_context_item_mapper);
+ context.addItem().addXref(contextPath+"/admin/export?collectionID="+collection.getID(), T_context_export_collection );
}
}
else if (dso instanceof Community)
@@ -220,7 +261,8 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
if (community.canEditBoolean())
{
context.setHead(T_context_head);
- context.addItemXref(contextPath+"/admin/community?communityID=" + community.getID(), T_context_edit_community);
+ context.addItemXref(contextPath+"/admin/community?communityID=" + community.getID(), T_context_edit_community);
+ context.addItem().addXref(contextPath+"/admin/export?communityID="+community.getID(), T_context_export_community );
}
// can they add to this community?
diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/DSpaceCocoonServlet.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/DSpaceCocoonServlet.java
index 0016fda01c..b9d117a1d9 100644
--- a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/DSpaceCocoonServlet.java
+++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/DSpaceCocoonServlet.java
@@ -223,7 +223,13 @@ public class DSpaceCocoonServlet extends CocoonServlet
finally
{
// Ensure that the current context is removed from ThreadLocal
- ContextMap.removeCurrentContext();
+ try {
+ ContextMap.removeCurrentContext();
+ }
+ catch (NoSuchMethodError nsme)
+ {
+ // just ignore this, it means we're using an out of date logging library.
+ }
}
}
diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/ItemExportDownloadReader.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/ItemExportDownloadReader.java
new file mode 100644
index 0000000000..7e94be81ff
--- /dev/null
+++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/ItemExportDownloadReader.java
@@ -0,0 +1,299 @@
+/*
+ * ItemExportDownlaodReader.java
+ *
+ * Version: $Revision: 1.5 $
+ *
+ * Date: $Date: 2006/08/08 20:59:54 $
+ *
+ * Copyright (c) 2002, 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.xmlui.cocoon;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.avalon.excalibur.pool.Recyclable;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.ProcessingException;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.Response;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.environment.http.HttpEnvironment;
+import org.apache.cocoon.environment.http.HttpResponse;
+import org.apache.cocoon.reading.AbstractReader;
+import org.apache.cocoon.util.ByteRange;
+import org.dspace.app.itemexport.ItemExport;
+import org.dspace.app.xmlui.utils.AuthenticationUtil;
+import org.dspace.app.xmlui.utils.ContextUtil;
+import org.dspace.core.Context;
+import org.xml.sax.SAXException;
+
+/**
+ * @author Jay Paz
+ */
+
+public class ItemExportDownloadReader extends AbstractReader implements Recyclable
+{
+
+ /**
+ * Messages to be sent when the user is not authorized to view
+ * a particular bitstream. They will be redirected to the login
+ * where this message will be displayed.
+ */
+ private final static String AUTH_REQUIRED_HEADER = "xmlui.ItemExportDownloadReader.auth_header";
+ private final static String AUTH_REQUIRED_MESSAGE = "xmlui.ItemExportDownloadReader.auth_message";
+
+ /**
+ * How big of a buffer should we use when reading from the bitstream before
+ * writting to the HTTP response?
+ */
+ protected static final int BUFFER_SIZE = 8192;
+
+ /**
+ * When should a download expire in milliseconds. This should be set to
+ * some low value just to prevent someone hiting DSpace repeatily from
+ * killing the server. Note: 60000 milliseconds are in a second.
+ *
+ * Format: minutes * seconds * milliseconds
+ */
+ protected static final int expires = 60 * 60 * 60000;
+
+ /** The Cocoon response */
+ protected Response response;
+
+ /** The Cocoon request */
+ protected Request request;
+
+ /** The bitstream file */
+ protected InputStream compressedExportInputStream;
+
+ /** The compressed export's reported size */
+ protected long compressedExportSize;
+
+ protected String compressedExportName;
+ /**
+ * Set up the export reader.
+ *
+ * See the class description for information on configuration options.
+ */
+ public void setup(SourceResolver resolver, Map objectModel, String src,
+ Parameters par) throws ProcessingException, SAXException,
+ IOException
+ {
+ super.setup(resolver, objectModel, src, par);
+
+ try
+ {
+ this.request = ObjectModelHelper.getRequest(objectModel);
+ this.response = ObjectModelHelper.getResponse(objectModel);
+ Context context = ContextUtil.obtainContext(objectModel);
+
+ // Get our parameters that identify the bitstream
+ String fileName = par.getParameter("fileName", null);
+
+
+ // Is there a User logged in and does the user have access to read it?
+ if (!ItemExport.canDownload(context, fileName))
+ {
+ if(this.request.getSession().getAttribute("dspace.current.user.id")!=null){
+ // A user is logged in, but they are not authorized to read this bitstream,
+ // instead of asking them to login again we'll point them to a friendly error
+ // message that tells them the bitstream is restricted.
+ String redictURL = request.getContextPath() + "/restricted-resource?name=" + fileName;
+
+ HttpServletResponse httpResponse = (HttpServletResponse)
+ objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT);
+ httpResponse.sendRedirect(redictURL);
+ return;
+ }
+ else{
+
+ // The user does not have read access to this bitstream. Inturrupt this current request
+ // and then forward them to the login page so that they can be authenticated. Once that is
+ // successfull they will request will be resumed.
+ AuthenticationUtil.interruptRequest(objectModel, AUTH_REQUIRED_HEADER, AUTH_REQUIRED_MESSAGE, null);
+
+ // Redirect
+ String redictURL = request.getContextPath() + "/login";
+
+ HttpServletResponse httpResponse = (HttpServletResponse)
+ objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT);
+ httpResponse.sendRedirect(redictURL);
+ return;
+ }
+ }
+
+
+ // Success, bitstream found and the user has access to read it.
+ // Store these for later retreval:
+ this.compressedExportInputStream = ItemExport.getExportDownloadInputStream(fileName, context.getCurrentUser());
+ this.compressedExportSize = ItemExport.getExportFileSize(fileName);
+ this.compressedExportName = fileName;
+ }
+ catch (Exception e)
+ {
+ throw new ProcessingException("Unable to read bitstream.",e);
+ }
+ }
+
+
+ /**
+ * Write the actual data out to the response.
+ *
+ * Some implementation notes,
+ *
+ * 1) We set a short expires time just in the hopes of preventing someone
+ * from overloading the server by clicking reload a bunch of times. I
+ * realize that this is nowhere near 100% effective but it may help in some
+ * cases and shouldn't hurt anything.
+ *
+ */
+ public void generate() throws IOException, SAXException,
+ ProcessingException
+ {
+ if (this.compressedExportInputStream == null)
+ return;
+
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int length = -1;
+
+ response.setDateHeader("Expires", System.currentTimeMillis()
+ + expires);
+ response.setHeader("Content-disposition","attachement; filename=" + this.compressedExportName );
+ // Turn off partial downloads, they cause problems
+ // and are only rarely used. Specifically some windows pdf
+ // viewers are incapable of handling this request. By
+ // uncommenting the following two lines you will turn this feature back on.
+ // response.setHeader("Accept-Ranges", "bytes");
+ // String ranges = request.getHeader("Range");
+ String ranges = null;
+
+
+ ByteRange byteRange = null;
+ if (ranges != null)
+ {
+ try
+ {
+ ranges = ranges.substring(ranges.indexOf('=') + 1);
+ byteRange = new ByteRange(ranges);
+ }
+ catch (NumberFormatException e)
+ {
+ byteRange = null;
+ if (response instanceof HttpResponse)
+ {
+ // Respond with status 416 (Request range not
+ // satisfiable)
+ ((HttpResponse) response).setStatus(416);
+ }
+ }
+ }
+
+ if (byteRange != null)
+ {
+ String entityLength;
+ String entityRange;
+ if (this.compressedExportSize != -1)
+ {
+ entityLength = "" + this.compressedExportSize;
+ entityRange = byteRange.intersection(
+ new ByteRange(0, this.compressedExportSize)).toString();
+ }
+ else
+ {
+ entityLength = "*";
+ entityRange = byteRange.toString();
+ }
+
+ response.setHeader("Content-Range", entityRange + "/"
+ + entityLength);
+ if (response instanceof HttpResponse)
+ {
+ // Response with status 206 (Partial content)
+ ((HttpResponse) response).setStatus(206);
+ }
+
+ int pos = 0;
+ int posEnd;
+ while ((length = this.compressedExportInputStream.read(buffer)) > -1)
+ {
+ posEnd = pos + length - 1;
+ ByteRange intersection = byteRange
+ .intersection(new ByteRange(pos, posEnd));
+ if (intersection != null)
+ {
+ out.write(buffer, (int) intersection.getStart()
+ - pos, (int) intersection.length());
+ }
+ pos += length;
+ }
+ }
+ else
+ {
+ response.setHeader("Content-Length", String
+ .valueOf(this.compressedExportSize));
+
+ while ((length = this.compressedExportInputStream.read(buffer)) > -1)
+ {
+ out.write(buffer, 0, length);
+ }
+ out.flush();
+ }
+ }
+
+ /**
+ * Returns the mime-type of the bitstream.
+ */
+ public String getMimeType()
+ {
+ return ItemExport.COMPRESSED_EXPORT_MIME_TYPE;
+ }
+
+ /**
+ * Recycle
+ */
+ public void recycle() {
+ this.response = null;
+ this.request = null;
+ this.compressedExportInputStream = null;
+ this.compressedExportSize = 0;
+
+ }
+
+
+}
diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Administrative/sitemap.xmap b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Administrative/sitemap.xmap
index ae23dd227f..6ea3471041 100644
--- a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Administrative/sitemap.xmap
+++ b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Administrative/sitemap.xmap
@@ -105,6 +105,7 @@ to administer DSpace.
+
@@ -777,6 +778,10 @@ to administer DSpace.
+
+
+
+
@@ -796,6 +801,10 @@ to administer DSpace.
+
+
+
+
diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/ArtifactBrowser/sitemap.xmap b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/ArtifactBrowser/sitemap.xmap
index 056a10c02a..8afcebb8a8 100644
--- a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/ArtifactBrowser/sitemap.xmap
+++ b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/ArtifactBrowser/sitemap.xmap
@@ -161,6 +161,14 @@ and searching the repository.
+
+
+
+
+
+
+
+
diff --git a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml
index d40b237b72..e9f878ad2a 100644
--- a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml
+++ b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml
@@ -320,7 +320,9 @@
The file is restricted
The file you are attempting to access is a restricted file and requires credentials to view. Please login below to access the file.
-
+
+ This export archive is restricted.
+ The export archive you are attempting to access is a restricted resource and requires credentials to view. Please login below to access the export archive.
Administrative
@@ -811,6 +816,24 @@
Control Panel
Statistics
+
+ My Exports
+
+
+
+ Export Archive
+ The item id provided is invalid.
+ The collection id provided is invalid.
+ The community id provided is invalid
+ The item requested was not found.
+ The collection requested was not found.
+ The community requested was not found.
+ The item was exported successfully. You should receive an e-mail when the archive is ready for download. You can also use the 'My Exports' link to view a list of your available archives.
+ The collection was exported successfully. You should receive an e-mail when the archive is ready for download. You can also use the 'My Exports' link to view a list of your available archives.
+ The community was exported successfully. You should receive an e-mail when the archive is ready for download. You can also use the 'My Exports' link to view a list of your available archives.
+ Available export archives for download:
+
+
Manage E-people
diff --git a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/sitemap.xmap b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/sitemap.xmap
index 1d924f5bdd..f9911f9269 100644
--- a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/sitemap.xmap
+++ b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/sitemap.xmap
@@ -158,6 +158,7 @@
3600000
+
@@ -261,6 +262,16 @@
+
+
+
+
+
+
+
+
diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg
index 2fe98edafb..8a341b6f9a 100644
--- a/dspace/config/dspace.cfg
+++ b/dspace/config/dspace.cfg
@@ -578,6 +578,27 @@ checker.retention.default=10y
checker.retention.CHECKSUM_MATCH=8w
+### Item export and download settings ###
+# The directory where the exports will be done and compressed
+org.dspace.app.itemexport.work.dir = ${dspace.dir}/exports
+
+# The directory where the compressed files will reside and be read by the downloader
+org.dspace.app.itemexport.download.dir = ${dspace.dir}/exports/download
+
+# The length og time in hours each archive should live for. When new archives are
+# created this entry is used to delete old ones
+org.dspace.app.itemexport.life.span.hours = 48
+
+# The maximum size in Megabytes the export should be. This is enforced before the
+# compression. Each bitstream's size in each item being exported is added up, if their
+# cummulative sizes are more than this entry the export is not kicked off
+org.dspace.app.itemexport.max.size = 200
+
+
+
+
+
+
#---------------------------------------------------------------#
#--------------JSPUI & XMLUI CONFIGURATIONS---------------------#
#---------------------------------------------------------------#
diff --git a/dspace/config/dstat.map b/dspace/config/dstat.map
index 82776a69dc..140049ee13 100644
--- a/dspace/config/dstat.map
+++ b/dspace/config/dstat.map
@@ -99,4 +99,5 @@ remove_subcommunity=Sub Community Removed
show_feedback_form=Feedback Form Displayed
create_dc_type=New Dublin Core Type Created
remove_template_item=Item Template Removed
-withdraw_item=Item Withdrawn
\ No newline at end of file
+withdraw_item=Item Withdrawn
+download_export_archive = Download Export Archive
\ No newline at end of file
diff --git a/dspace/modules/jspui/src/main/resources/Messages.properties b/dspace/modules/jspui/src/main/resources/Messages.properties
index 803922f4bf..3c6e86f70e 100644
--- a/dspace/modules/jspui/src/main/resources/Messages.properties
+++ b/dspace/modules/jspui/src/main/resources/Messages.properties
@@ -598,6 +598,7 @@ jsp.mydspace.main.heading3 = Tasks in the P
jsp.mydspace.main.heading4 = Unfinished Submissions
jsp.mydspace.main.heading5 = Submissions In Workflow Process
jsp.mydspace.main.heading6 = Authorization Groups I'm a Member Of
+jsp.mydspace.main.heading7 = Export Archives Available for Download
jsp.mydspace.main.item = Item
jsp.mydspace.main.link = See Your Subscriptions
jsp.mydspace.main.perform.button = Perform This Task
@@ -656,6 +657,7 @@ jsp.mydspace.subscriptions.title = Your Subscript
jsp.mydspace.subscriptions.unsub.button = Unsubscribe
jsp.mydspace.task-complete.text1 = The task is complete, and notification has been sent to the appropriate people.
jsp.mydspace.task-complete.title = Thank You
+jsp.mydspace.main.export.archive.title = Download Export Archive {0}
jsp.register.already-registered.info1 = Our records show that you've already registered with DSpace and have an active account with us.
jsp.register.already-registered.info2 = You can set a new password if you've forgotten it.
jsp.register.already-registered.info4 = If you're having trouble logging in, please contact us.
|