/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*
* University Of Edinburgh (EDINA)
* Scotland
*
*
* File Name : IMSDisseminator.java
* Author : gwaller
* Approver : Gareth Waller
*
* Notes :
*
*
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* HISTORY
* -------
*
* $LastChangedRevision$
* $LastChangedDate$
* $LastChangedBy$
*/
package uk.ac.jorum.packager;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.crosswalk.MetadataValidationException;
import org.dspace.content.packager.PackageDisseminator;
import org.dspace.content.packager.PackageException;
import org.dspace.content.packager.PackageIngester;
import org.dspace.content.packager.PackageParameters;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.jdom.Document;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
/**
* @author gwaller
* @author cgormley
*/
public class IMSDisseminator implements PackageDisseminator {
/** log4j category */
private static Logger log = Logger.getLogger(IMSDisseminator.class);
/**
* This method copies all entries from the supplied ZipInputStream into the supplied ZipOutputStream and
* potentially overrides the manifest in the source Zip. If a newManifest stream is supplied, this will
* be stored in the new Zip rather than the one found in the source input stream
* @param newManifest can be null. If non null, this manifest stream will be read and stored in the new Zip
* @param in the source Zip to copy from. NOTE: this must be closed by the caller!!
* @param out the ZipOutputStream to write to. NOTE: this must be closed by the caller!!
* @throws PackageException
*/
public static void copyPackageOverrideManifest(InputStream newManifest, ZipInputStream in, ZipOutputStream out)
throws PackageException {
ZipEntry inEntry;
try {
while ((inEntry = in.getNextEntry()) != null) {
ZipEntry outEntry = new ZipEntry(inEntry.getName());
out.putNextEntry(outEntry);
if (inEntry.getName().equals(IMSIngester.MANIFEST_FILE)) {
// Found manifest - do we copy this one or the one supplied via params
if (newManifest != null) {
// Now copy the bytes
copyStream(newManifest, out);
continue;
}
}
// Now copy the bytes
copyStream(in, out);
}
} catch (IOException e) {
throw new PackageException(e);
}
}
/**
* This copies bytes from one stream to the other.
* NOTE: it does not close any of the streams - that is the reposability of the caller
* @param in
* @param out
* @throws IOException
*/
public static void copyStream(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read = 0;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.flush();
}
/* (non-Javadoc)
* @see org.dspace.content.packager.PackageDisseminator#disseminate(org.dspace.core.Context, org.dspace.content.DSpaceObject, org.dspace.content.packager.PackageParameters, java.io.OutputStream)
*/
public void disseminate(Context context, DSpaceObject object, PackageParameters params, OutputStream out)
throws PackageException, CrosswalkException, AuthorizeException, SQLException, IOException {
// We can only disseminate an item as an IMS package at this stage - need to look at seeing if we could build a
// package containing all items in collection/community
if (!(object instanceof Item)) {
throw new PackageException("Error: Only a DSpace Item can be disseminated as an IMS package");
}
Item item = (Item) object;
XMLManifest manifest = new IMSManifest(item);
//Set the metdata format to use for the dissemination crosswalk
manifest.setExportMetadataFormat(params.getProperty(Constants.METADATA_FORMAT_LABEL,
Constants.SupportedDisseminationMetadataFormats.QDC.toString()));
// Populate this XMLManifest instance with either a brand new manifest or an update of the original manifest
manifest.populate();
// Create the ZIP file
ZipOutputStream zipOut = new ZipOutputStream(out);
InputStream in = null;
try {
//All we need to do is save each bitstream in the content bundle as a zipEntry, then add an imsmanifest.xml
// Remember - do not need to look at the URL_BUNDLEs - only physical files should have a Zip entry
Bundle[] contentBundles = item.getBundles(Constants.CONTENT_BUNDLE_NAME);
if (contentBundles.length > 0) {
//Get the content streams and copy to ZipEntry via buffer
Bitstream[] bitstreams = item.getBundles(Constants.CONTENT_BUNDLE_NAME)[0].getBitstreams();
for (Bitstream bitstream : bitstreams) {
in = bitstream.retrieve();
if (in != null)
// NOTE: createZipEntry closes the input stream 'in'
createZipEntry(zipOut, in, bitstream.getName());
}
}
// Add the manifest
in = manifest.getXmlAsStream();
if (in != null)
// NOTE: createZipEntry closes the input stream 'in'
createZipEntry(zipOut, in, IMSIngester.MANIFEST_FILE);
} catch (Exception e) {
throw new PackageException("Error writing the zip ", e);
}
finally {
if (in != null)
in.close();
// Complete the ZIP file
if (zipOut != null)
zipOut.close();
}
log.info("Completed IMS package export");
/*Bundle archivedPackageBundles[] = item.getBundles(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE);
if (archivedPackageBundles != null && archivedPackageBundles.length > 0){
// Return the first bitstream in the first archived bundle
Bitstream streams[] = archivedPackageBundles[0].getBitstreams();
if (streams != null && streams.length > 0){
InputStream in = streams[0].retrieve();
try{
copyStream(in, out);
} finally{
try {in.close();} catch (Exception x){}
}
} else {
throw new PackageException(Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE + " bundle found but contains no bitstreams");
}
} else {
// Archived package not found - need to create the zip
throw new PackageException("Not implemented - Complete IMS Package Generation is not implemented yet");
}*/
}
/**
* Wries a new zip entry to the ZipOutputStream and also closes the InputStream in
* @param zipOut
* @param in the input stream to copy from. NOTE: this is closed by this method
* @param entryName
* @throws IOException
*/
private void createZipEntry(ZipOutputStream zipOut, InputStream in, String entryName) throws IOException {
// Create a buffer for reading the files
byte[] buf = new byte[1024];
try {
log.debug("Writing zipEntry " + entryName);
zipOut.putNextEntry(new ZipEntry(entryName));
// Transfer bytes from the file to the ZIP entry
int len;
while ((len = in.read(buf)) > 0) {
zipOut.write(buf, 0, len);
}
} finally {
// Close the current entry
zipOut.closeEntry();
// Close the stream
in.close();
}
}
/* (non-Javadoc)
* @see org.dspace.content.packager.PackageDisseminator#getMIMEType(org.dspace.content.packager.PackageParameters)
*/
public String getMIMEType(PackageParameters params) {
return "application/zip";
}
}