/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*
* University Of Edinburgh (EDINA)
* Scotland
*
*
* File Name : AnonItemExport.java
* Author : Ian Fieldhouse (ianfi)
* Approver : Gareth Waller
*
* Notes :
*
*
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* HISTORY
* -------
*
* $LastChangedRevision$
* $LastChangedDate$
* $LastChangedBy$
*/
package org.dspace.app.itemexport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.FutureTask;
import javax.mail.MessagingException;
import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.core.LogManager;
public class AnonItemExport extends ItemExport {
/**
* used for export download
*/
public static final String COMPRESSED_EXPORT_MIME_TYPE = "application/zip";
/** log4j logger */
private static Logger log = Logger.getLogger(AnonItemExport.class);
/**
* Convenience methot to create export a single Community, Collection, or
* Item
*
* @param dso
* - the dspace object to export
* @param context
* - the dspace context
* @param email
* - email to use
* @throws Exception
*/
public static void createDownloadableExport(DSpaceObject dso,
Context context, String email, boolean migrate) throws Exception
{
ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1);
list.add(dso);
processDownloadableExport(list, context, email, 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 email
* - email address send confirmation email to
* @throws Exception
*/
private static void processDownloadableExport(List<DSpaceObject> dsObjects,
Context context, final String userEmail, boolean toMigrate) throws Exception
{
final String email = userEmail;
final boolean migrate = toMigrate;
/*
* before we create a new export archive lets delete the 'expired' archives
*/
deleteOldExportArchives();
/*
* keep track of the cumulative 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.ITEM)
{
Item item = (Item) dso;
// get all the bundles in the item
Bundle[] bundles = item.getBundles();
for (Bundle bundle : bundles)
{
// GWaller 11/02/09 IssueID #192 Ignore bundles storing archived packages
String bundleName = bundle.getName();
if (bundleName.startsWith(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE) || bundleName.startsWith(Constants.BACKUP_CONTENT_PACKAGE_BUNDLE)){
// simply go to the next bundle
continue;
}
// get all the bitstreams in the bundle
Bitstream[] bitstreams = bundle.getBitstreams();
for (Bitstream bit : bitstreams)
{
// add up the size
size += bit.getSize();
}
}
items.add(item.getID());
}
else
{
// nothing to do just ignore this type of DSPaceObject
}
}
/*
* check the size of all the bitstreams against the configuration file entry if it exists
*/
String megaBytes = ConfigurationManager.getProperty("org.dspace.app.itemexport.max.size");
if (megaBytes != null)
{
float maxSize = 0;
try
{
maxSize = Float.parseFloat(megaBytes);
}
catch (Exception e)
{
// ignore...configuration entry may not be present
}
if (maxSize > 0)
{
if (maxSize < (size / 1048576.00))
{ // a megabyte
throw new ItemExportException(ItemExportException.EXPORT_TOO_LARGE,
"The overall size of this export is too large. Please contact your administrator for more information.");
}
}
}
/*
* if we have any items to process then create a task and use the
* executor service to submit it to the thread pool
*/
if (items.size() > 0)
{
FutureTask<Integer> task = new FutureTask<Integer>(new CallableExport(email, items, migrate));
es.submit(task);
}
}
/**
* Create a file name based on the date
*
* @param email
* - email address of user 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, String email, Date date) throws Exception
{
// to format the date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MMM_dd");
String downloadDir = getExportDownloadDirectory();
// used to avoid name collision
int count = 1;
boolean exists = true;
String fileName = null;
while (exists)
{
String myString = Thread.currentThread().toString();
String md5String = null;
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(myString.getBytes());
md5String = new String(Hex.encodeHex(digest.digest()));
}
catch (Exception e) {
log.warn("Cannot create MD5 hash of thread info", e);
}
fileName = type + "_export_" + sdf.format(date) + "_" + count + "_" + md5String + "_" + "anon";
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
* string representing anonymous users to create a download directory name
*
* @return String representing a directory in the form of
* org.dspace.app.itemexport.download.dir/anon
* @throws Exception
*/
public static String getExportDownloadDirectory()
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") + "anon";
}
/**
* Used to read the export archived. Intended for download.
*
* @param fileName
* the name of the file to download
* @return an input stream of the file to be downloaded
* @throws Exception
*/
public static InputStream getExportDownloadInputStream(String fileName) throws Exception
{
File file = new File(getExportDownloadDirectory() + 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
{
File file = new File(
getExportDownloadDirectory()
+ System.getProperty("file.separator") + fileName);
if (!file.exists() || !file.isFile())
{
throw new FileNotFoundException("The file "
+ getExportDownloadDirectory()
+ System.getProperty("file.separator") + fileName
+ " does not exist.");
}
return file.length();
}
public static long getExportFileLastModified(String fileName)
throws Exception
{
File file = new File(getExportDownloadDirectory()
+ System.getProperty("file.separator") + fileName);
if (!file.exists() || !file.isFile())
{
throw new FileNotFoundException("The file "
+ getExportDownloadDirectory()
+ System.getProperty("file.separator") + fileName
+ " does not exist.");
}
return file.lastModified();
}
/**
* 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 pruning
*
* @throws Exception
*/
public static void deleteOldExportArchives() 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());
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 accomplish 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 userEmail
* - email address of user 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, String userEmail, String fileName) throws MessagingException
{
try
{
Locale supportedLocale = I18nUtil.getDefaultLocale();
Email email = ConfigurationManager.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_success"));
email.addRecipient(userEmail);
email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/anonexportdownload/" + 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 accomplish this
* communication with email instead. Send an error email if the export
* archive fails
*
* @param email
* - email address to send the error message to
* @param error
* - the error message
* @throws MessagingException
*/
public static void emailErrorMessage(String userEmail , String error)
throws MessagingException
{
log.warn("An error occured during item export, the user will be notified. " + error);
try
{
Locale supportedLocale = I18nUtil.getDefaultLocale();
// GWaller IssueID #611 26/1/11 Use the export_error template on error not export_success
Email email = ConfigurationManager.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_error"));
email.addRecipient(userEmail);
email.addArgument(error);
email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/feedback");
email.send();
}
catch (Exception e)
{
log.warn("error during item export error notification", e);
}
}
}