/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ package org.dspace.app.itemexport; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.mail.MessagingException; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.itemexport.service.ItemExportService; import org.dspace.content.*; import org.dspace.content.Collection; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.I18nUtil; import org.dspace.core.LogManager; import org.dspace.core.Utils; import org.dspace.core.Email; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.handle.service.HandleService; import org.springframework.beans.factory.annotation.Autowired; /** * Item exporter to create simple AIPs for DSpace content. Currently exports * individual items, or entire collections. For instructions on use, see * printUsage() method. * <P> * ItemExport creates the simple AIP package that the importer also uses. It * consists of: * <P> * /exportdir/42/ (one directory per item) / dublin_core.xml - qualified dublin * core in RDF schema / contents - text file, listing one file per line / file1 * - files contained in the item / file2 / ... * <P> * issues -doesn't handle special characters in metadata (needs to turn {@code &'s} into * {@code &}, etc.) * <P> * Modified by David Little, UCSD Libraries 12/21/04 to allow the registration * of files (bitstreams) into DSpace. * * @author David Little * @author Jay Paz */ public class ItemExportServiceImpl implements ItemExportService { protected final int SUBDIR_LIMIT = 0; @Autowired(required = true) protected BitstreamService bitstreamService; @Autowired(required = true) protected CommunityService communityService; @Autowired(required = true) protected EPersonService ePersonService; @Autowired(required = true) protected ItemService itemService; @Autowired(required = true) protected HandleService handleService; /** log4j logger */ private Logger log = Logger.getLogger(ItemExportServiceImpl.class); protected ItemExportServiceImpl() { } @Override public void exportItem(Context c, Iterator<Item> i, String destDirName, int seqStart, boolean migrate, boolean excludeBitstreams) 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 = Integer.valueOf(subDirSuffix++).toString(); fullPath = destDirName + File.separatorChar + subdir; counter = 0; if (!new File(fullPath).mkdirs()) { throw new IOException("Error, can't make dir " + fullPath); } } System.out.println("Exporting item to " + mySequenceNumber); exportItem(c, i.next(), fullPath, mySequenceNumber, migrate, excludeBitstreams); mySequenceNumber++; } } protected void exportItem(Context c, Item myItem, String destDirName, int seqStart, boolean migrate, boolean excludeBitstreams) 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() + (myItem.getHandle() != null ? ", handle " + myItem.getHandle() : "") + " to " + itemDir); if (itemDir.exists()) { throw new Exception("Directory " + destDir + "/" + seqStart + " already exists!"); } if (itemDir.mkdir()) { // make it this far, now start exporting writeMetadata(c, myItem, itemDir, migrate); writeBitstreams(c, myItem, itemDir, excludeBitstreams); if (!migrate) { writeHandle(c, myItem, itemDir); } } else { throw new Exception("Error, can't make dir " + itemDir); } } else { throw new Exception("Error, directory " + destDirName + " doesn't exist!"); } } /** * Discover the different schemas in use and output a separate metadata XML * file for each schema. * * @param c DSpace context * @param i DSpace Item * @param destDir destination directory * @param migrate Whether to use the migrate option or not * @throws Exception if error */ protected void writeMetadata(Context c, Item i, File destDir, boolean migrate) throws Exception { Set<String> schemas = new HashSet<String>(); List<MetadataValue> dcValues = itemService.getMetadata(i, Item.ANY, Item.ANY, Item.ANY, Item.ANY); for (MetadataValue metadataValue : dcValues) { schemas.add(metadataValue.getMetadataField().getMetadataSchema().getName()); } // Save each of the schemas into it's own metadata file for (String schema : schemas) { writeMetadata(c, schema, i, destDir, migrate); } } /** * output the item's dublin core into the item directory * @param c DSpace context * @param schema schema * @param i DSpace Item * @param destDir destination directory * @param migrate Whether to use the migrate option or not * @throws Exception if error */ protected void writeMetadata(Context c, String schema, Item i, File destDir, boolean migrate) throws Exception { String filename; if (schema.equals(MetadataSchema.DC_SCHEMA)) { filename = "dublin_core.xml"; } else { filename = "metadata_" + schema + ".xml"; } File outFile = new File(destDir, filename); System.out.println("Attempting to create file " + outFile); if (outFile.createNewFile()) { BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(outFile)); List<MetadataValue> dcorevalues = itemService.getMetadata(i, schema, Item.ANY, Item.ANY, Item.ANY); // XML preamble byte[] utf8 = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n" .getBytes("UTF-8"); out.write(utf8, 0, utf8.length); String dcTag = "<dublin_core schema=\"" + schema + "\">\n"; utf8 = dcTag.getBytes("UTF-8"); out.write(utf8, 0, utf8.length); String dateIssued = null; String dateAccessioned = null; for (MetadataValue dcv : dcorevalues) { MetadataField metadataField = dcv.getMetadataField(); String qualifier = metadataField.getQualifier(); if (qualifier == null) { qualifier = "none"; } String language = dcv.getLanguage(); if (language != null) { language = " language=\"" + language + "\""; } else { language = ""; } utf8 = (" <dcvalue element=\"" + metadataField.getElement() + "\" " + "qualifier=\"" + qualifier + "\"" + language + ">" + Utils.addEntities(dcv.getValue()) + "</dcvalue>\n") .getBytes("UTF-8"); if ((!migrate) || (migrate && !( ("date".equals(metadataField.getElement()) && "issued".equals(qualifier)) || ("date".equals(metadataField.getElement()) && "accessioned".equals(qualifier)) || ("date".equals(metadataField.getElement()) && "available".equals(qualifier)) || ("identifier".equals(metadataField.getElement()) && "uri".equals(qualifier) && (dcv.getValue() != null && dcv.getValue().startsWith("http://hdl.handle.net/" + handleService.getPrefix() + "/"))) || ("description".equals(metadataField.getElement()) && "provenance".equals(qualifier)) || ("format".equals(metadataField.getElement()) && "extent".equals(qualifier)) || ("format".equals(metadataField.getElement()) && "mimetype".equals(qualifier))))) { out.write(utf8, 0, utf8.length); } // Store the date issued and accession to see if they are different // because we need to keep date.issued if they are, when migrating if (("date".equals(metadataField.getElement()) && "issued".equals(qualifier))) { dateIssued = dcv.getValue(); } if (("date".equals(metadataField.getElement()) && "accessioned".equals(qualifier))) { dateAccessioned = dcv.getValue(); } } // When migrating, only keep date.issued if it is different to date.accessioned if ((migrate) && (dateIssued != null) && (dateAccessioned != null) && (!dateIssued.equals(dateAccessioned))) { utf8 = (" <dcvalue element=\"date\" " + "qualifier=\"issued\">" + Utils.addEntities(dateIssued) + "</dcvalue>\n") .getBytes("UTF-8"); out.write(utf8, 0, utf8.length); } utf8 = "</dublin_core>\n".getBytes("UTF-8"); out.write(utf8, 0, utf8.length); out.close(); } else { throw new Exception("Cannot create dublin_core.xml in " + destDir); } } /** * create the file 'handle' which contains the handle assigned to the item * @param c DSpace Context * @param i DSpace Item * @param destDir destination directory * @throws Exception if error */ protected 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 * @param excludeBitstreams * whether to exclude bitstreams * @throws Exception if error * if there is any problem writing to the export directory */ protected void writeBitstreams(Context c, Item i, File destDir, boolean excludeBitstreams) throws Exception { File outFile = new File(destDir, "contents"); if (outFile.createNewFile()) { PrintWriter out = new PrintWriter(new FileWriter(outFile)); List<Bundle> bundles = i.getBundles(); for (Bundle bundle : bundles) { // bundles can have multiple bitstreams now... List<Bitstream> bitstreams = bundle.getBitstreams(); String bundleName = bundle.getName(); for (Bitstream bitstream : bitstreams) { String myName = bitstream.getName(); String oldName = myName; String description = bitstream.getDescription(); if (!StringUtils.isEmpty(description)) { description = "\tdescription:" + description; } else { description = ""; } String primary = ""; if (bitstream.equals(bundle.getPrimaryBitstream())) { primary = "\tprimary:true "; } int myPrefix = 1; // only used with name conflict boolean isDone = false; // done when bitstream is finally // written while (!excludeBitstreams && !isDone) { if (myName.contains(File.separator)) { String dirs = myName.substring(0, myName .lastIndexOf(File.separator)); File fdirs = new File(destDir + File.separator + dirs); if (!fdirs.exists() && !fdirs.mkdirs()) { log.error("Unable to create destination directory"); } } File fout = new File(destDir, myName); if (fout.createNewFile()) { InputStream is = bitstreamService.retrieve(c, bitstream); FileOutputStream fos = new FileOutputStream(fout); Utils.bufferedCopy(is, fos); // close streams is.close(); fos.close(); isDone = true; } else { myName = myPrefix + "_" + oldName; // keep // appending // numbers to the // filename until // unique myPrefix++; } } // write the manifest file entry if (bitstreamService.isRegisteredBitstream(bitstream)) { out.println("-r -s " + bitstream.getStoreNumber() + " -f " + myName + "\tbundle:" + bundleName + primary + description); } else { out.println(myName + "\tbundle:" + bundleName + primary + description); } } } // close the contents file out.close(); } else { throw new Exception("Cannot create contents in " + destDir); } } @Override public void exportAsZip(Context context, Iterator<Item> items, String destDirName, String zipFileName, int seqStart, boolean migrate, boolean excludeBitstreams) throws Exception { String workDir = getExportWorkDirectory() + System.getProperty("file.separator") + zipFileName; File wkDir = new File(workDir); if (!wkDir.exists() && !wkDir.mkdirs()) { log.error("Unable to create working direcory"); } File dnDir = new File(destDirName); if (!dnDir.exists() && !dnDir.mkdirs()) { log.error("Unable to create destination directory"); } // export the items using normal export method exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams); // now zip up the export directory created above zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName); } @Override public void createDownloadableExport(DSpaceObject dso, Context context, boolean migrate) throws Exception { EPerson eperson = context.getCurrentUser(); ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1); list.add(dso); processDownloadableExport(list, context, eperson == null ? null : eperson.getEmail(), migrate); } @Override public void createDownloadableExport(List<DSpaceObject> dsObjects, Context context, boolean migrate) throws Exception { EPerson eperson = context.getCurrentUser(); processDownloadableExport(dsObjects, context, eperson == null ? null : eperson.getEmail(), migrate); } @Override public void createDownloadableExport(DSpaceObject dso, Context context, String additionalEmail, boolean migrate) throws Exception { ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1); list.add(dso); processDownloadableExport(list, context, additionalEmail, migrate); } @Override public void createDownloadableExport(List<DSpaceObject> dsObjects, Context context, String additionalEmail, boolean migrate) throws Exception { processDownloadableExport(dsObjects, context, additionalEmail, migrate); } /** * Does the work creating a List with all the Items in the Community or * Collection It then kicks off a new Thread to export the items, zip the * export directory and send confirmation email * * @param dsObjects * - List of dspace objects to process * @param context * - the dspace context * @param additionalEmail * - email address to cc in addition the the current user email * @param toMigrate Whether to use the migrate option or not * @throws Exception if error */ protected void processDownloadableExport(List<DSpaceObject> dsObjects, Context context, final String additionalEmail, boolean toMigrate) throws Exception { final EPerson eperson = context.getCurrentUser(); final boolean migrate = toMigrate; // before we create a new export archive lets delete the 'expired' // archives //deleteOldExportArchives(eperson.getID()); deleteOldExportArchives(); // keep track of the commulative size of all bitstreams in each of the // items // it will be checked against the config file entry double size = 0; final HashMap<String, List<UUID>> itemsMap = new HashMap<>(); for (DSpaceObject dso : dsObjects) { if (dso.getType() == Constants.COMMUNITY) { Community community = (Community) dso; // get all the collections in the community List<Collection> collections = communityService.getAllCollections(context, community); for (Collection collection : collections) { ArrayList<UUID> items = new ArrayList<>(); // get all the items in each collection Iterator<Item> iitems = itemService.findByCollection(context, collection); try { while (iitems.hasNext()) { Item item = iitems.next(); // get all the bundles in the item List<Bundle> bundles = item.getBundles(); for (Bundle bundle : bundles) { // get all the bitstreams in each bundle List<Bitstream> bitstreams = bundle.getBitstreams(); for (Bitstream bitstream : bitstreams) { // add up the size size += bitstream.getSize(); } } items.add(item.getID()); } } finally { if (items.size() > 0) { itemsMap.put("collection_"+collection.getID(), items); } } } } else if (dso.getType() == Constants.COLLECTION) { Collection collection = (Collection) dso; ArrayList<UUID> items = new ArrayList<>(); // get all the items in the collection Iterator<Item> iitems = itemService.findByCollection(context, collection); try { while (iitems.hasNext()) { Item item = iitems.next(); // get all thebundles in the item List<Bundle> bundles = item.getBundles(); for (Bundle bundle : bundles) { // get all the bitstreams in the bundle List<Bitstream> bitstreams = bundle.getBitstreams(); for (Bitstream bitstream : bitstreams) { // add up the size size += bitstream.getSize(); } } items.add(item.getID()); } } finally { if (items.size() > 0) { itemsMap.put("collection_"+collection.getID(), items); } } } else if (dso.getType() == Constants.ITEM) { Item item = (Item) dso; // get all the bundles in the item List<Bundle> bundles = item.getBundles(); for (Bundle bundle : bundles) { // get all the bitstreams in the bundle List<Bitstream> bitstreams = bundle.getBitstreams(); for (Bitstream bitstream : bitstreams) { // add up the size size += bitstream.getSize(); } } ArrayList<UUID> items = new ArrayList<>(); items.add(item.getID()); itemsMap.put("item_"+item.getID(), items); } 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 && maxSize < (size / 1048576.00)) { // a megabyte throw new ItemExportException(ItemExportException.EXPORT_TOO_LARGE, "The overall size of this export is too large. Please contact your administrator for more information."); } } // if we have any items to process then kick off anonymous thread if (itemsMap.size() > 0) { Thread go = new Thread() { @Override public void run() { Context context = null; Iterator<Item> iitems = null; try { // create a new dspace context context = new Context(); // ignore auths context.turnOffAuthorisationSystem(); String fileName = assembleFileName("item", eperson, new Date()); String workParentDir = getExportWorkDirectory() + System.getProperty("file.separator") + fileName; String downloadDir = getExportDownloadDirectory(eperson); File dnDir = new File(downloadDir); if (!dnDir.exists() && !dnDir.mkdirs()) { log.error("Unable to create download directory"); } Iterator<String> iter = itemsMap.keySet().iterator(); while(iter.hasNext()) { String keyName = iter.next(); List<UUID> uuids = itemsMap.get(keyName); List<Item> items = new ArrayList<Item>(); for (UUID uuid : uuids) { items.add(itemService.find(context, uuid)); } iitems = items.iterator(); String workDir = workParentDir + System.getProperty("file.separator") + keyName; File wkDir = new File(workDir); if (!wkDir.exists() && !wkDir.mkdirs()) { log.error("Unable to create working directory"); } // export the items using normal export method exportItem(context, iitems, workDir, 1, migrate, false); } // now zip up the export directory created above zip(workParentDir, downloadDir + System.getProperty("file.separator") + fileName + ".zip"); // email message letting user know the file is ready for // download emailSuccessMessage(context, eperson, fileName + ".zip"); // return to enforcing auths context.restoreAuthSystemState(); } catch (Exception e1) { try { emailErrorMessage(eperson, e1.getMessage()); } catch (Exception e) { // wont throw here } throw new IllegalStateException(e1); } finally { // Make sure the database connection gets closed in all conditions. try { context.complete(); } catch (SQLException sqle) { context.abort(); } } } }; go.isDaemon(); go.start(); } else { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); emailErrorMessage(eperson, I18nUtil.getMessage("org.dspace.app.itemexport.no-result", supportedLocale)); } } @Override public String assembleFileName(String type, EPerson eperson, Date date) throws Exception { // to format the date SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MMM_dd"); String downloadDir = getExportDownloadDirectory(eperson); // used to avoid name collision int count = 1; boolean exists = true; String fileName = null; while (exists) { fileName = type + "_export_" + sdf.format(date) + "_" + count + "_" + eperson.getID(); exists = new File(downloadDir + System.getProperty("file.separator") + fileName + ".zip") .exists(); count++; } return fileName; } @Override public String getExportDownloadDirectory(EPerson ePerson) 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."); } File result = new File(downloadDir + System.getProperty("file.separator") + ePerson.getID()); if(!result.exists() && ePerson.getLegacyId()!=null) { //Check for the old identifier result = new File(downloadDir + System.getProperty("file.separator") + ePerson.getLegacyId()); } return result.getAbsolutePath(); } @Override public 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; } @Override public InputStream getExportDownloadInputStream(String fileName, EPerson eperson) throws Exception { File file = new File(getExportDownloadDirectory(eperson) + System.getProperty("file.separator") + fileName); if (file.exists()) { return new FileInputStream(file); } else { return null; } } @Override public long getExportFileSize(Context context, String fileName) throws Exception { String strID = fileName.substring(fileName.lastIndexOf('_') + 1, fileName.lastIndexOf('.')); EPerson ePerson = getEPersonFromString(context, strID); File file = new File( getExportDownloadDirectory(ePerson) + System.getProperty("file.separator") + fileName); if (!file.exists() || !file.isFile()) { throw new FileNotFoundException("The file " + getExportDownloadDirectory(ePerson) + System.getProperty("file.separator") + fileName + " does not exist."); } return file.length(); } /** * Attempt to find an EPerson based on string ID * @param context DSpace context * @param strID string identifier * @return EPerson object (if found) * @throws SQLException if database error */ protected EPerson getEPersonFromString(Context context, String strID) throws SQLException { EPerson eperson; try{ UUID ePersonId = UUID.fromString(strID); eperson = ePersonService.find(context, ePersonId); }catch (Exception e){ eperson = ePersonService.findByLegacyId(context, Integer.parseInt(strID)); } return eperson; } @Override public long getExportFileLastModified(Context context, String fileName) throws Exception { String strID = fileName.substring(fileName.lastIndexOf('_') + 1, fileName.lastIndexOf('.')); EPerson ePerson = getEPersonFromString(context, strID); File file = new File( getExportDownloadDirectory(ePerson) + System.getProperty("file.separator") + fileName); if (!file.exists() || !file.isFile()) { throw new FileNotFoundException("The file " + getExportDownloadDirectory(ePerson) + System.getProperty("file.separator") + fileName + " does not exist."); } return file.lastModified(); } @Override public 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 (strID.equals(eperson.getID().toString())) { return true; } } catch (Exception e) { return false; } return false; } @Override public List<String> getExportsAvailable(EPerson eperson) throws Exception { File downloadDir = new File(getExportDownloadDirectory(eperson)); if (!downloadDir.exists() || !downloadDir.isDirectory()) { return null; } List<String> fileNames = new ArrayList<String>(); for (String fileName : downloadDir.list()) { if (fileName.contains("export") && fileName.endsWith(".zip")) { fileNames.add(fileName); } } if (fileNames.size() > 0) { return fileNames; } return null; } @Override public void deleteOldExportArchives(EPerson eperson) 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(eperson)); if (downloadDir.exists()) { File[] files = downloadDir.listFiles(); for (File file : files) { if (file.lastModified() < now.getTimeInMillis()) { if (!file.delete()) { log.error("Unable to delete export file"); } } } } } @Override public 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(ConfigurationManager.getProperty("org.dspace.app.itemexport.download.dir")); if (downloadDir.exists()) { // Get a list of all the sub-directories, potentially one for each ePerson. File[] dirs = downloadDir.listFiles(); for (File dir : dirs) { // For each sub-directory delete any old files. File[] files = dir.listFiles(); for (File file : files) { if (file.lastModified() < now.getTimeInMillis()) { if (!file.delete()) { log.error("Unable to delete old files"); } } } // If the directory is now empty then we delete it too. if (dir.listFiles().length == 0) { if (!dir.delete()) { log.error("Unable to delete directory"); } } } } } @Override public void emailSuccessMessage(Context context, EPerson eperson, String fileName) throws MessagingException { try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_success")); email.addRecipient(eperson.getEmail()); email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/exportdownload/" + fileName); email.addArgument(ConfigurationManager.getProperty("org.dspace.app.itemexport.life.span.hours")); email.send(); } catch (Exception e) { log.warn(LogManager.getHeader(context, "emailSuccessMessage", "cannot notify user of export"), e); } } @Override public void emailErrorMessage(EPerson eperson, String error) throws MessagingException { log.warn("An error occurred during item export, the user will be notified. " + error); try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_error")); email.addRecipient(eperson.getEmail()); email.addArgument(error); email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/feedback"); email.send(); } catch (Exception e) { log.warn("error during item export error notification", e); } } @Override public 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.createNewFile()) { log.warn("Target file already exists: " + targetFile.getName()); } FileOutputStream fos = new FileOutputStream(tempFileName); cpZipOutputStream = new ZipOutputStream(fos); cpZipOutputStream.setLevel(9); zipFiles(cpFile, strSource, tempFileName, cpZipOutputStream); cpZipOutputStream.finish(); cpZipOutputStream.close(); cpZipOutputStream = null; // Fix issue on Windows with stale file handles open before trying to delete them System.gc(); deleteDirectory(cpFile); if (!targetFile.renameTo(new File(target))) { log.error("Unable to rename file"); } } finally { if (cpZipOutputStream != null) { cpZipOutputStream.close(); } } } /** * * @param cpFile file * @param strSource source location * @param strTarget target location * @param cpZipOutputStream current zip outputstream * @throws Exception if error */ protected void zipFiles(File cpFile, String strSource, String strTarget, ZipOutputStream cpZipOutputStream) throws Exception { int byteCount; final int DATA_BLOCK_SIZE = 2048; FileInputStream cpFileInputStream = null; if (cpFile.isDirectory()) { File[] fList = cpFile.listFiles(); for (File aFList : fList) { zipFiles(aFList, 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()); } finally { if (cpFileInputStream != null) { cpFileInputStream.close(); } cpZipOutputStream.closeEntry(); } } } /** * Delete a directory * @param path directory path * @return true if successful, false otherwise */ protected boolean deleteDirectory(File path) { if (path.exists()) { File[] files = path.listFiles(); for (File file : files) { if (file.isDirectory()) { deleteDirectory(file); } else { if (!file.delete()) { log.error("Unable to delete file: " + file.getName()); } } } } return (path.delete()); } }