/** * 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.content.packager; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import edu.harvard.hul.ois.mets.AmdSec; import edu.harvard.hul.ois.mets.BinData; import edu.harvard.hul.ois.mets.Checksumtype; import edu.harvard.hul.ois.mets.Div; import edu.harvard.hul.ois.mets.DmdSec; import edu.harvard.hul.ois.mets.MdRef; import edu.harvard.hul.ois.mets.FLocat; import edu.harvard.hul.ois.mets.FileGrp; import edu.harvard.hul.ois.mets.FileSec; import edu.harvard.hul.ois.mets.Fptr; import edu.harvard.hul.ois.mets.Mptr; import edu.harvard.hul.ois.mets.Loctype; import edu.harvard.hul.ois.mets.MdWrap; import edu.harvard.hul.ois.mets.Mdtype; import edu.harvard.hul.ois.mets.Mets; import edu.harvard.hul.ois.mets.MetsHdr; import edu.harvard.hul.ois.mets.StructMap; import edu.harvard.hul.ois.mets.TechMD; import edu.harvard.hul.ois.mets.SourceMD; import edu.harvard.hul.ois.mets.DigiprovMD; import edu.harvard.hul.ois.mets.RightsMD; import edu.harvard.hul.ois.mets.helper.MdSec; import edu.harvard.hul.ois.mets.XmlData; import edu.harvard.hul.ois.mets.helper.Base64; import edu.harvard.hul.ois.mets.helper.MetsElement; import edu.harvard.hul.ois.mets.helper.MetsException; import edu.harvard.hul.ois.mets.helper.MetsValidator; import edu.harvard.hul.ois.mets.helper.MetsWriter; import edu.harvard.hul.ois.mets.helper.PreformedXML; import java.io.File; import java.io.FileOutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import org.apache.log4j.Logger; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeManager; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Community; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.ItemIterator; import org.dspace.content.crosswalk.AbstractPackagerWrappingCrosswalk; import org.dspace.content.crosswalk.CrosswalkException; import org.dspace.content.crosswalk.CrosswalkObjectNotSupported; import org.dspace.content.crosswalk.DisseminationCrosswalk; import org.dspace.content.crosswalk.StreamDisseminationCrosswalk; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.PluginManager; import org.dspace.core.Utils; import org.dspace.license.CreativeCommons; import org.jdom.Element; import org.jdom.Namespace; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; /** * Base class for disseminator of * METS (Metadata Encoding & Transmission Standard) Package.<br> * See <a href="http://www.loc.gov/standards/mets/">http://www.loc.gov/standards/mets/</a> * <p> * This is a generic packager framework intended to be subclassed to create * packagers for more specific METS "profiles". METS is an * abstract and flexible framework that can encompass many * different kinds of metadata and inner package structures. * <p> * <b>Package Parameters:</b><br> * <ul> * <li><code>manifestOnly</code> -- if true, generate a standalone XML * document of the METS manifest instead of a complete package. Any * other metadata (such as licenses) will be encoded inline. * Default is <code>false</code>.</li> * * <li><code>unauthorized</code> -- this determines what is done when the * packager encounters a Bundle or Bitstream it is not authorized to * read. By default, it just quits with an AuthorizeException. * If this option is present, it must be one of the following values: * <ul> * <li><code>skip</code> -- simply exclude unreadable content from package.</li> * <li><code>zero</code> -- include unreadable bitstreams as 0-length files; * unreadable Bundles will still cause authorize errors.</li></ul></li> * </ul> * * @author Larry Stone * @author Robert Tansley * @author Tim Donohue * @version $Revision$ */ public abstract class AbstractMETSDisseminator extends AbstractPackageDisseminator { /** log4j category */ private static Logger log = Logger.getLogger(AbstractMETSDisseminator.class); // JDOM xml output writer - indented format for readability. private static XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); // for gensym() private int idCounter = 1; /** * Default date/time (in milliseconds since epoch) to set for Zip Entries * for DSpace Objects which don't have a Last Modified date. If we don't * set our own date/time, then it will default to current system date/time. * This is less than ideal, as it causes the md5 checksum of Zip file to * change whenever Zip is regenerated (even if compressed files are unchanged) * 1036368000 seconds * 1000 = Nov 4, 2002 GMT (the date DSpace 1.0 was released) */ private static final int DEFAULT_MODIFIED_DATE = 1036368000 * 1000; /** * Suffix for Template objects (e.g. Item Templates) */ protected static final String TEMPLATE_TYPE_SUFFIX = " Template"; /** * Wrapper for a table of streams to add to the package, such as * mdRef'd metadata. Key is relative pathname of file, value is * <code>InputStream</code> with contents to put in it. Some * superclasses will put streams in this table when adding an mdRef * element to e.g. a rightsMD segment. */ protected static class MdStreamCache { private Map<MdRef,InputStream> extraFiles = new HashMap<MdRef,InputStream>(); public void addStream(MdRef key, InputStream md) { extraFiles.put(key, md); } public Map<MdRef,InputStream> getMap() { return extraFiles; } public void close() throws IOException { for (InputStream is : extraFiles.values()) { is.close(); } } } /** * Make a new unique ID symbol with specified prefix. * @param prefix the prefix of the identifier, constrained to XML ID schema * @return a new string identifier unique in this session (instance). */ protected synchronized String gensym(String prefix) { return prefix + "_" + String.valueOf(idCounter++); } /** * Resets the unique ID counter used by gensym() method to * determine the @ID values of METS tags. */ protected synchronized void resetCounter() { idCounter = 1; } @Override public String getMIMEType(PackageParameters params) { return (params != null && (params.getBooleanProperty("manifestOnly", false))) ? "text/xml" : "application/zip"; } /** * Export the object (Item, Collection, or Community) as a * "package" on the indicated OutputStream. Package is any serialized * representation of the item, at the discretion of the implementing * class. It does not have to include content bitstreams. * <p> * Use the <code>params</code> parameter list to adjust the way the * package is made, e.g. including a "<code>metadataOnly</code>" * parameter might make the package a bare manifest in XML * instead of a Zip file including manifest and contents. * <p> * Throws an exception of the chosen object is not acceptable or there is * a failure creating the package. * * @param context DSpace context. * @param object DSpace object (item, collection, etc) * @param params Properties-style list of options specific to this packager * @param pkgFile File where export package should be written * @throws PackageValidationException if package cannot be created or there is * a fatal error in creating it. */ @Override public void disseminate(Context context, DSpaceObject dso, PackageParameters params, File pkgFile) throws PackageValidationException, CrosswalkException, AuthorizeException, SQLException, IOException { // Reset our 'unique' ID counter back to 1 (in case a previous dissemination was run) // This ensures that the @ID attributes of METS tags always begin at '1', which // also ensures that the Checksums don't change because of accidental @ID value changes. resetCounter(); FileOutputStream outStream = null; try { //Make sure our package file exists if(!pkgFile.exists()) { PackageUtils.createFile(pkgFile); } //Open up an output stream to write to package file outStream = new FileOutputStream(pkgFile); // Generate a true manifest-only "package", no external files/data & no need to zip up if (params != null && params.getBooleanProperty("manifestOnly", false)) { Mets manifest = makeManifest(context, dso, params, null); //only validate METS if specified (default = true) if(params.getBooleanProperty("validate", true)) { manifest.validate(new MetsValidator()); } manifest.write(new MetsWriter(outStream)); } else { // make a Zip-based package writeZipPackage(context, dso, params, outStream); }//end if/else }//end try catch (MetsException e) { String errorMsg = "Error exporting METS for DSpace Object, type=" + Constants.typeText[dso.getType()] + ", handle=" + dso.getHandle() + ", dbID=" + String.valueOf(dso.getID()); // We don't pass up a MetsException, so callers don't need to // know the details of the METS toolkit log.error(errorMsg,e); throw new PackageValidationException(errorMsg, e); } finally { //Close stream / stop writing to file if (outStream != null) { outStream.close(); } } } /** * Make a Zipped up METS package for the given DSpace Object * * @param context DSpace Context * @param dso The DSpace Object * @param params Parameters to the Packager script * @param pkg Package output stream * @throws PackageValidationException * @throws AuthorizeException * @throws SQLException * @throws IOException */ protected void writeZipPackage(Context context, DSpaceObject dso, PackageParameters params, OutputStream pkg) throws PackageValidationException, CrosswalkException, MetsException, AuthorizeException, SQLException, IOException { long lmTime = 0; if (dso.getType() == Constants.ITEM) { lmTime = ((Item) dso).getLastModified().getTime(); } // map of extra streams to put in Zip (these are located during makeManifest()) MdStreamCache extraStreams = new MdStreamCache(); ZipOutputStream zip = new ZipOutputStream(pkg); zip.setComment("METS archive created by DSpace " + Util.getSourceVersion()); Mets manifest = makeManifest(context, dso, params, extraStreams); // copy extra (metadata, license, etc) bitstreams into zip, update manifest if (extraStreams != null) { for (Map.Entry<MdRef, InputStream> ment : extraStreams.getMap().entrySet()) { MdRef ref = ment.getKey(); // Both Deposit Licenses & CC Licenses which are referenced as "extra streams" may already be // included in our Package (if their bundles are already included in the <filSec> section of manifest). // So, do a special check to see if we need to link up extra License <mdRef> entries to the bitstream in the <fileSec>. // (this ensures that we don't accidentally add the same License file to our package twice) linkLicenseRefsToBitstreams(context, params, dso, ref); //If this 'mdRef' is NOT already linked up to a file in the package, // then its file must be missing. So, we are going to add a new // file to the Zip package. if(ref.getXlinkHref()==null || ref.getXlinkHref().isEmpty()) { InputStream is = ment.getValue(); // create a hopefully unique filename within the Zip String fname = gensym("metadata"); // link up this 'mdRef' to point to that file ref.setXlinkHref(fname); if (log.isDebugEnabled()) { log.debug("Writing EXTRA stream to Zip: " + fname); } //actually add the file to the Zip package ZipEntry ze = new ZipEntry(fname); if (lmTime != 0) { ze.setTime(lmTime); } else //Set a default modified date so that checksum of Zip doesn't change if Zip contents are unchanged { ze.setTime(DEFAULT_MODIFIED_DATE); } zip.putNextEntry(ze); Utils.copy(is, zip); zip.closeEntry(); is.close(); } } } // write manifest after metadata. ZipEntry me = new ZipEntry(METSManifest.MANIFEST_FILE); if (lmTime != 0) { me.setTime(lmTime); } else //Set a default modified date so that checksum of Zip doesn't change if Zip contents are unchanged { me.setTime(DEFAULT_MODIFIED_DATE); } zip.putNextEntry(me); // can only validate now after fixing up extraStreams // note: only validate METS if specified (default = true) if(params.getBooleanProperty("validate", true)) { manifest.validate(new MetsValidator()); } manifest.write(new MetsWriter(zip)); zip.closeEntry(); //write any bitstreams associated with DSpace object to zip package addBitstreamsToZip(context, dso, params, zip); zip.close(); } /** * Add Bitstreams associated with a given DSpace Object into an * existing ZipOutputStream * @param context DSpace Context * @param dso The DSpace Object * @param params Parameters to the Packager script * @param zip Zip output */ protected void addBitstreamsToZip(Context context, DSpaceObject dso, PackageParameters params, ZipOutputStream zip) throws PackageValidationException, AuthorizeException, SQLException, IOException { // how to handle unauthorized bundle/bitstream: String unauth = (params == null) ? null : params.getProperty("unauthorized"); // copy all non-meta bitstreams into zip if (dso.getType() == Constants.ITEM) { Item item = (Item)dso; //get last modified time long lmTime = ((Item)dso).getLastModified().getTime(); Bundle bundles[] = item.getBundles(); for (int i = 0; i < bundles.length; i++) { if (includeBundle(bundles[i])) { // unauthorized bundle? if (!AuthorizeManager.authorizeActionBoolean(context, bundles[i], Constants.READ)) { if (unauth != null && (unauth.equalsIgnoreCase("skip"))) { log.warn("Skipping Bundle[\""+bundles[i].getName()+"\"] because you are not authorized to read it."); continue; } else { throw new AuthorizeException("Not authorized to read Bundle named \"" + bundles[i].getName() + "\""); } } Bitstream[] bitstreams = bundles[i].getBitstreams(); for (int k = 0; k < bitstreams.length; k++) { boolean auth = AuthorizeManager.authorizeActionBoolean(context, bitstreams[k], Constants.READ); if (auth || (unauth != null && unauth.equalsIgnoreCase("zero"))) { String zname = makeBitstreamURL(bitstreams[k], params); ZipEntry ze = new ZipEntry(zname); if (log.isDebugEnabled()) { log.debug(new StringBuilder().append("Writing CONTENT stream of bitstream(").append(bitstreams[k].getID()).append(") to Zip: ").append(zname).append(", size=").append(bitstreams[k].getSize()).toString()); } if (lmTime != 0) { ze.setTime(lmTime); } else //Set a default modified date so that checksum of Zip doesn't change if Zip contents are unchanged { ze.setTime(DEFAULT_MODIFIED_DATE); } ze.setSize(auth ? bitstreams[k].getSize() : 0); zip.putNextEntry(ze); if (auth) { InputStream input = bitstreams[k].retrieve(); Utils.copy(input, zip); input.close(); } else { log.warn("Adding zero-length file for Bitstream, SID=" + String.valueOf(bitstreams[k].getSequenceID()) + ", not authorized for READ."); } zip.closeEntry(); } else if (unauth != null && unauth.equalsIgnoreCase("skip")) { log.warn("Skipping Bitstream, SID="+String.valueOf(bitstreams[k].getSequenceID())+", not authorized for READ."); } else { throw new AuthorizeException("Not authorized to read Bitstream, SID="+String.valueOf(bitstreams[k].getSequenceID())); } } } } } // Coll, Comm just add logo bitstream to content if there is one else if (dso.getType() == Constants.COLLECTION || dso.getType() == Constants.COMMUNITY) { Bitstream logoBs = dso.getType() == Constants.COLLECTION ? ((Collection)dso).getLogo() : ((Community)dso).getLogo(); if (logoBs != null) { String zname = makeBitstreamURL(logoBs, params); ZipEntry ze = new ZipEntry(zname); if (log.isDebugEnabled()) { log.debug("Writing CONTENT stream of bitstream(" + String.valueOf(logoBs.getID()) + ") to Zip: " + zname + ", size=" + String.valueOf(logoBs.getSize())); } ze.setSize(logoBs.getSize()); //Set a default modified date so that checksum of Zip doesn't change if Zip contents are unchanged ze.setTime(DEFAULT_MODIFIED_DATE); zip.putNextEntry(ze); Utils.copy(logoBs.retrieve(), zip); zip.closeEntry(); } } } // set metadata type - if Mdtype.parse() gets exception, // that means it's not in the MDTYPE vocabulary, so use OTHER. protected void setMdType(MdWrap mdWrap, String mdtype) { try { mdWrap.setMDTYPE(Mdtype.parse(mdtype)); } catch (MetsException e) { mdWrap.setMDTYPE(Mdtype.OTHER); mdWrap.setOTHERMDTYPE(mdtype); } } // set metadata type - if Mdtype.parse() gets exception, // that means it's not in the MDTYPE vocabulary, so use OTHER. protected void setMdType(MdRef mdRef, String mdtype) { try { mdRef.setMDTYPE(Mdtype.parse(mdtype)); } catch (MetsException e) { mdRef.setMDTYPE(Mdtype.OTHER); mdRef.setOTHERMDTYPE(mdtype); } } /** * Create an element wrapped around a metadata reference (either mdWrap * or mdRef); i.e. dmdSec, techMd, sourceMd, etc. Checks for * XML-DOM oriented crosswalk first, then if not found looks for * stream crosswalk of the same name. * * @param context DSpace Context * @param dso DSpace Object we are generating METS manifest for * @param mdSecClass class of mdSec (TechMD, RightsMD, DigiProvMD, etc) * @param typeSpec Type of metadata going into this mdSec (e.g. MODS, DC, PREMIS, etc) * @param params the PackageParameters * @param extraStreams list of extra files which need to be added to final dissemination package * * @return mdSec element or null if xwalk returns empty results. * * @throws SQLException * @throws PackageValidationException * @throws CrosswalkException * @throws IOException * @throws AuthorizeException */ protected MdSec makeMdSec(Context context, DSpaceObject dso, Class mdSecClass, String typeSpec, PackageParameters params, MdStreamCache extraStreams) throws SQLException, PackageValidationException, CrosswalkException, IOException, AuthorizeException { try { //create our metadata element (dmdSec, techMd, sourceMd, rightsMD etc.) MdSec mdSec = (MdSec) mdSecClass.newInstance(); mdSec.setID(gensym(mdSec.getLocalName())); String parts[] = typeSpec.split(":", 2); String xwalkName, metsName; //determine the name of the crosswalk to use to generate metadata // for dmdSecs this is the part *after* the colon in the 'type' (see getDmdTypes()) // for all other mdSecs this is usually just corresponds to type name. if (parts.length > 1) { metsName = parts[0]; xwalkName = parts[1]; } else { metsName = typeSpec; xwalkName = typeSpec; } // First, check to see if the crosswalk we are using is a normal DisseminationCrosswalk boolean xwalkFound = PluginManager.hasNamedPlugin(DisseminationCrosswalk.class, xwalkName); if(xwalkFound) { // Find the crosswalk we will be using to generate the metadata for this mdSec DisseminationCrosswalk xwalk = (DisseminationCrosswalk) PluginManager.getNamedPlugin(DisseminationCrosswalk.class, xwalkName); if (xwalk.canDisseminate(dso)) { // Check if our Crosswalk actually wraps another Packager Plugin if(xwalk instanceof AbstractPackagerWrappingCrosswalk) { // If this crosswalk wraps another Packager Plugin, we can pass it our Packaging Parameters // (which essentially allow us to customize the output of the crosswalk) AbstractPackagerWrappingCrosswalk wrapper = (AbstractPackagerWrappingCrosswalk) xwalk; wrapper.setPackagingParameters(params); } //For a normal DisseminationCrosswalk, we will be expecting an XML (DOM) based result. // So, we are going to wrap this XML result in an <mdWrap> element MdWrap mdWrap = new MdWrap(); setMdType(mdWrap, metsName); XmlData xmlData = new XmlData(); if (crosswalkToMetsElement(xwalk, dso, xmlData) != null) { mdWrap.getContent().add(xmlData); mdSec.getContent().add(mdWrap); return mdSec; } else { return null; } } else { return null; } } // If we didn't find the correct crosswalk, we will check to see if this is // a StreamDisseminationCrosswalk -- a Stream crosswalk disseminates to an OutputStream else { StreamDisseminationCrosswalk sxwalk = (StreamDisseminationCrosswalk) PluginManager.getNamedPlugin(StreamDisseminationCrosswalk.class, xwalkName); if (sxwalk != null) { if (sxwalk.canDisseminate(context, dso)) { // Check if our Crosswalk actually wraps another Packager Plugin if(sxwalk instanceof AbstractPackagerWrappingCrosswalk) { // If this crosswalk wraps another Packager Plugin, we can pass it our Packaging Parameters // (which essentially allow us to customize the output of the crosswalk) AbstractPackagerWrappingCrosswalk wrapper = (AbstractPackagerWrappingCrosswalk) sxwalk; wrapper.setPackagingParameters(params); } // Disseminate crosswalk output to an outputstream ByteArrayOutputStream disseminateOutput = new ByteArrayOutputStream(); sxwalk.disseminate(context, dso, disseminateOutput); // Convert output to an inputstream, so we can write to manifest or Zip file ByteArrayInputStream crosswalkedStream = new ByteArrayInputStream(disseminateOutput.toByteArray()); //If we are capturing extra files to put into a Zip package if(extraStreams!=null) { //Create an <mdRef> -- we'll just reference the file by name in Zip package MdRef mdRef = new MdRef(); //add the crosswalked Stream to list of files to add to Zip package later extraStreams.addStream(mdRef, crosswalkedStream); //set properties on <mdRef> // Note, filename will get set on this <mdRef> later, // when we process all the 'extraStreams' mdRef.setMIMETYPE(sxwalk.getMIMEType()); setMdType(mdRef, metsName); mdRef.setLOCTYPE(Loctype.URL); mdSec.getContent().add(mdRef); } else { //If we are *not* capturing extra streams to add to Zip package later, // that means we are likely only generating a METS manifest // (i.e. manifestOnly = true) // In this case, the best we can do is take the crosswalked // Stream, base64 encode it, and add in an <mdWrap> field // First, create our <mdWrap> MdWrap mdWrap = new MdWrap(); mdWrap.setMIMETYPE(sxwalk.getMIMEType()); setMdType(mdWrap, metsName); // Now, create our <binData> and add base64 encoded contents to it. BinData binData = new BinData(); Base64 base64 = new Base64(crosswalkedStream); binData.getContent().add(base64); mdWrap.getContent().add(binData); mdSec.getContent().add(mdWrap); } return mdSec; } else { return null; } } else { throw new PackageValidationException("Cannot find " + xwalkName + " crosswalk plugin, either DisseminationCrosswalk or StreamDisseminationCrosswalk"); } } } catch (InstantiationException e) { throw new PackageValidationException("Error instantiating Mdsec object: "+ e.toString(), e); } catch (IllegalAccessException e) { throw new PackageValidationException("Error instantiating Mdsec object: "+ e.toString(), e); } } // add either a techMd or sourceMd element to amdSec. // mdSecClass determines which type. // mdTypes[] is array of "[metsName:]PluginName" strings, maybe empty. protected void addToAmdSec(AmdSec fAmdSec, String mdTypes[], Class mdSecClass, Context context, DSpaceObject dso, PackageParameters params, MdStreamCache extraStreams) throws SQLException, PackageValidationException, CrosswalkException, IOException, AuthorizeException { for (int i = 0; i < mdTypes.length; ++i) { MdSec md = makeMdSec(context, dso, mdSecClass, mdTypes[i], params, extraStreams); if (md != null) { fAmdSec.getContent().add(md); } } } // Create amdSec for any tech md's, return its ID attribute. protected String addAmdSec(Context context, DSpaceObject dso, PackageParameters params, Mets mets, MdStreamCache extraStreams) throws SQLException, PackageValidationException, CrosswalkException, IOException, AuthorizeException { String techMdTypes[] = getTechMdTypes(context, dso, params); String rightsMdTypes[] = getRightsMdTypes(context, dso, params); String sourceMdTypes[] = getSourceMdTypes(context, dso, params); String digiprovMdTypes[] = getDigiprovMdTypes(context, dso, params); // only bother if there are any sections to add if ((techMdTypes.length+sourceMdTypes.length+ digiprovMdTypes.length+rightsMdTypes.length) > 0) { String result = gensym("amd"); AmdSec fAmdSec = new AmdSec(); fAmdSec.setID(result); addToAmdSec(fAmdSec, techMdTypes, TechMD.class, context, dso, params, extraStreams); addToAmdSec(fAmdSec, rightsMdTypes, RightsMD.class, context, dso, params, extraStreams); addToAmdSec(fAmdSec, sourceMdTypes, SourceMD.class, context, dso, params, extraStreams); addToAmdSec(fAmdSec, digiprovMdTypes, DigiprovMD.class, context, dso, params, extraStreams); mets.getContent().add(fAmdSec); return result; } else { return null; } } // make the most "persistent" identifier possible, preferably a URN // based on the Handle. protected String makePersistentID(DSpaceObject dso) { String handle = dso.getHandle(); // If no Handle, punt to much-less-satisfactory database ID and type.. if (handle == null) { return "DSpace_DB_" + Constants.typeText[dso.getType()] + "_" + String.valueOf(dso.getID()); } else { return getHandleURN(handle); } } /** * Write out a METS manifest. * Mostly lifted from Rob Tansley's METS exporter. */ protected Mets makeManifest(Context context, DSpaceObject dso, PackageParameters params, MdStreamCache extraStreams) throws MetsException, PackageValidationException, CrosswalkException, AuthorizeException, SQLException, IOException { // Create the METS manifest in memory Mets mets = new Mets(); String identifier = "DB-ID-" + dso.getID(); if(dso.getHandle()!=null) { identifier = dso.getHandle().replace('/', '-'); } // this ID should be globally unique (format: DSpace_[objType]_[handle with slash replaced with a dash]) mets.setID("DSpace_" + Constants.typeText[dso.getType()] + "_" + identifier); // identifies the object described by this document mets.setOBJID(makePersistentID(dso)); mets.setTYPE(getObjectTypeString(dso)); // this is the signature by which the ingester will recognize // a document it can expect to interpret. mets.setPROFILE(getProfile()); MetsHdr metsHdr = makeMetsHdr(context, dso, params); if (metsHdr != null) { mets.getContent().add(metsHdr); } // add DMD sections // Each type element MAY be either just a MODS-and-crosswalk name, OR // a combination "MODS-name:crosswalk-name" (e.g. "DC:qDC"). String dmdTypes[] = getDmdTypes(context, dso, params); // record of ID of each dmdsec to make DMDID in structmap. String dmdId[] = new String[dmdTypes.length]; for (int i = 0; i < dmdTypes.length; ++i) { MdSec dmdSec = makeMdSec(context, dso, DmdSec.class, dmdTypes[i], params, extraStreams); if (dmdSec != null) { mets.getContent().add(dmdSec); dmdId[i] = dmdSec.getID(); } } // add object-wide technical/source MD segments, get ID string: // Put that ID in ADMID of first div in structmap. String objectAMDID = addAmdSec(context, dso, params, mets, extraStreams); // Create simple structMap: initial div represents the Object's // contents, its children are e.g. Item bitstreams (content only), // Collection's members, or Community's members. StructMap structMap = new StructMap(); structMap.setID(gensym("struct")); structMap.setTYPE("LOGICAL"); structMap.setLABEL("DSpace Object"); Div div0 = new Div(); div0.setID(gensym("div")); div0.setTYPE("DSpace Object Contents"); structMap.getContent().add(div0); // fileSec is optional, let object type create it if needed. FileSec fileSec = null; // Item-specific manifest - license, bitstreams as Files, etc. if (dso.getType() == Constants.ITEM) { // this tags file ID and group identifiers for bitstreams. String bitstreamIDstart = "bitstream_"; Item item = (Item)dso; // how to handle unauthorized bundle/bitstream: String unauth = (params == null) ? null : params.getProperty("unauthorized"); // fileSec - all non-metadata bundles go into fileGrp, // and each bitstream therein into a file. // Create the bitstream-level techMd and div's for structmap // at the same time so we can connect the IDREFs to IDs. fileSec = new FileSec(); Bundle[] bundles = item.getBundles(); for (int i = 0; i < bundles.length; i++) { if (!includeBundle(bundles[i])) { continue; } // unauthorized bundle? // NOTE: This must match the logic in disseminate() if (!AuthorizeManager.authorizeActionBoolean(context, bundles[i], Constants.READ)) { if (unauth != null && (unauth.equalsIgnoreCase("skip"))) { continue; } else { throw new AuthorizeException("Not authorized to read Bundle named \"" + bundles[i].getName() + "\""); } } Bitstream[] bitstreams = bundles[i].getBitstreams(); // Create a fileGrp, USE = permuted Bundle name FileGrp fileGrp = new FileGrp(); String bName = bundles[i].getName(); if ((bName != null) && !bName.equals("")) { fileGrp.setUSE(bundleToFileGrp(bName)); } // add technical metadata for a bundle String techBundID = addAmdSec(context, bundles[i], params, mets, extraStreams); if (techBundID != null) { fileGrp.setADMID(techBundID); } // watch for primary bitstream int primaryBitstreamID = -1; boolean isContentBundle = false; if ((bName != null) && bName.equals("ORIGINAL")) { isContentBundle = true; primaryBitstreamID = bundles[i].getPrimaryBitstreamID(); } // For each bitstream, add to METS manifest for (int bits = 0; bits < bitstreams.length; bits++) { // Check for authorization. Handle unauthorized // bitstreams to match the logic in disseminate(), // i.e. "unauth=zero" means include a 0-length bitstream, // "unauth=skip" means to ignore it (and exclude from // manifest). boolean auth = AuthorizeManager.authorizeActionBoolean(context, bitstreams[bits], Constants.READ); if (!auth) { if (unauth != null && unauth.equalsIgnoreCase("skip")) { continue; } else if (!(unauth != null && unauth.equalsIgnoreCase("zero"))) { throw new AuthorizeException("Not authorized to read Bitstream, SID=" + String.valueOf(bitstreams[bits].getSequenceID())); } } String sid = String.valueOf(bitstreams[bits].getSequenceID()); String fileID = bitstreamIDstart + sid; edu.harvard.hul.ois.mets.File file = new edu.harvard.hul.ois.mets.File(); file.setID(fileID); file.setSEQ(bitstreams[bits].getSequenceID()); fileGrp.getContent().add(file); // set primary bitstream in structMap if (bitstreams[bits].getID() == primaryBitstreamID) { Fptr fptr = new Fptr(); fptr.setFILEID(fileID); div0.getContent().add(0, fptr); } // if this is content, add to structmap too: if (isContentBundle) { div0.getContent().add(makeFileDiv(fileID, getObjectTypeString(bitstreams[bits]))); } /* * If we're in THUMBNAIL or TEXT bundles, the bitstream is * extracted text or a thumbnail, so we use the name to work * out which bitstream to be in the same group as */ String groupID = "GROUP_" + bitstreamIDstart + sid; if ((bundles[i].getName() != null) && (bundles[i].getName().equals("THUMBNAIL") || bundles[i].getName().startsWith("TEXT"))) { // Try and find the original bitstream, and chuck the // derived bitstream in the same group Bitstream original = findOriginalBitstream(item, bitstreams[bits]); if (original != null) { groupID = "GROUP_" + bitstreamIDstart + original.getSequenceID(); } } file.setGROUPID(groupID); file.setMIMETYPE(bitstreams[bits].getFormat().getMIMEType()); file.setSIZE(auth ? bitstreams[bits].getSize() : 0); // Translate checksum and type to METS String csType = bitstreams[bits].getChecksumAlgorithm(); String cs = bitstreams[bits].getChecksum(); if (auth && cs != null && csType != null) { try { file.setCHECKSUMTYPE(Checksumtype.parse(csType)); file.setCHECKSUM(cs); } catch (MetsException e) { log.warn("Cannot set bitstream checksum type="+csType+" in METS."); } } // FLocat: point to location of bitstream contents. FLocat flocat = new FLocat(); flocat.setLOCTYPE(Loctype.URL); flocat.setXlinkHref(makeBitstreamURL(bitstreams[bits], params)); file.getContent().add(flocat); // technical metadata for bitstream String techID = addAmdSec(context, bitstreams[bits], params, mets, extraStreams); if (techID != null) { file.setADMID(techID); } } fileSec.getContent().add(fileGrp); } } else if (dso.getType() == Constants.COLLECTION) { Collection collection = (Collection)dso; ItemIterator ii = collection.getItems(); while (ii.hasNext()) { //add a child <div> for each item in collection Item item = ii.next(); Div childDiv = makeChildDiv(getObjectTypeString(item), item, params); if(childDiv!=null) { div0.getContent().add(childDiv); } } // add metadata & info for Template Item, if exists Item templateItem = collection.getTemplateItem(); if(templateItem!=null) { String templateDmdId[] = new String[dmdTypes.length]; // index where we should add the first template item <dmdSec>. // Index = number of <dmdSecs> already added + number of <metsHdr> = # of dmdSecs + 1 // (Note: in order to be a valid METS file, all dmdSecs must be before the 1st amdSec) int dmdIndex = dmdTypes.length + 1; //For each type of dmdSec specified, // add a new dmdSec which contains the Template Item metadata // (Note: Template Items are only metadata -- they have no content files) for (int i = 0; i < dmdTypes.length; ++i) { MdSec templateDmdSec = makeMdSec(context, templateItem, DmdSec.class, dmdTypes[i], params, extraStreams); if (templateDmdSec != null) { mets.getContent().add(dmdIndex, templateDmdSec); dmdIndex++; templateDmdId[i] = templateDmdSec.getID(); } } //Now add a child <div> in structMap to represent that Template Item Div templateItemDiv = new Div(); templateItemDiv.setID(gensym("div")); templateItemDiv.setTYPE(getObjectTypeString(templateItem) + TEMPLATE_TYPE_SUFFIX); //Link up the dmdSec(s) for the Template Item to this <div> StringBuilder templateDmdIds = new StringBuilder(); for (String currdmdId : templateDmdId) { templateDmdIds.append(" ").append(currdmdId); } templateItemDiv.setDMDID(templateDmdIds.substring(1)); //add this child <div> before the listing of normal Items div0.getContent().add(0, templateItemDiv); } // add link to Collection Logo, if one exists Bitstream logoBs = collection.getLogo(); if (logoBs != null) { fileSec = new FileSec(); addLogoBitstream(logoBs, fileSec, div0, params); } } else if (dso.getType() == Constants.COMMUNITY) { // Subcommunities are directly under "DSpace Object Contents" <div>, // but are labeled as Communities. Community subcomms[] = ((Community)dso).getSubcommunities(); for (int i = 0; i < subcomms.length; ++i) { //add a child <div> for each subcommunity in this community Div childDiv = makeChildDiv(getObjectTypeString(subcomms[i]), subcomms[i], params); if(childDiv!=null) { div0.getContent().add(childDiv); } } // Collections are also directly under "DSpace Object Contents" <div>, // but are labeled as Collections. Collection colls[] = ((Community)dso).getCollections(); for (int i = 0; i < colls.length; ++i) { //add a child <div> for each collection in this community Div childDiv = makeChildDiv(getObjectTypeString(colls[i]), colls[i], params); if(childDiv!=null) { div0.getContent().add(childDiv); } } //add Community logo bitstream Bitstream logoBs = ((Community)dso).getLogo(); if (logoBs != null) { fileSec = new FileSec(); addLogoBitstream(logoBs, fileSec, div0, params); } } else if (dso.getType() == Constants.SITE) { // This is a site-wide <structMap>, which just lists the top-level // communities. Each top level community is referenced by a div. Community comms[] = Community.findAllTop(context); for (int i = 0; i < comms.length; ++i) { //add a child <div> for each top level community in this site Div childDiv = makeChildDiv(getObjectTypeString(comms[i]), comms[i], params); if(childDiv!=null) { div0.getContent().add(childDiv); } } } //Only add the <fileSec> to the METS file if it has content. A <fileSec> must have content. if (fileSec != null && fileSec.getContent()!=null && !fileSec.getContent().isEmpty()) { mets.getContent().add(fileSec); } mets.getContent().add(structMap); // set links to metadata for object -- after type-specific // code since that can add to the object metadata. StringBuilder dmdIds = new StringBuilder(); for (String currdmdId : dmdId) { dmdIds.append(" ").append(currdmdId); } div0.setDMDID(dmdIds.substring(1)); if (objectAMDID != null) { div0.setADMID(objectAMDID); } // Does subclass have something to add to structMap? addStructMap(context, dso, params, mets); return mets; } // Install logo bitstream into METS for Community, Collection. // Add a file element, and refer to it from an fptr in the first div // of the main structMap. protected void addLogoBitstream(Bitstream logoBs, FileSec fileSec, Div div0, PackageParameters params) { edu.harvard.hul.ois.mets.File file = new edu.harvard.hul.ois.mets.File(); String fileID = gensym("logo"); file.setID(fileID); file.setMIMETYPE(logoBs.getFormat().getMIMEType()); file.setSIZE(logoBs.getSize()); // Translate checksum and type to METS String csType = logoBs.getChecksumAlgorithm(); String cs = logoBs.getChecksum(); if (cs != null && csType != null) { try { file.setCHECKSUMTYPE(Checksumtype.parse(csType)); file.setCHECKSUM(cs); } catch (MetsException e) { log.warn("Cannot set bitstream checksum type="+csType+" in METS."); } } //Create <fileGroup USE="LOGO"> with a <FLocat> pointing at bitstream FLocat flocat = new FLocat(); flocat.setLOCTYPE(Loctype.URL); flocat.setXlinkHref(makeBitstreamURL(logoBs, params)); file.getContent().add(flocat); FileGrp fileGrp = new FileGrp(); fileGrp.setUSE("LOGO"); fileGrp.getContent().add(file); fileSec.getContent().add(fileGrp); // add fptr directly to div0 of structMap Fptr fptr = new Fptr(); fptr.setFILEID(fileID); div0.getContent().add(0, fptr); } // create <div> element pointing to a file protected Div makeFileDiv(String fileID, String type) { Div div = new Div(); div.setID(gensym("div")); div.setTYPE(type); Fptr fptr = new Fptr(); fptr.setFILEID(fileID); div.getContent().add(fptr); return div; } /** * Create a <div> element with <mptr> which references a child * object via its handle (and via a local file name, when recursively disseminating * all child objects). * @param type - type attr value for the <div> * @param dso - object for which to create the div * @param params * @return */ protected Div makeChildDiv(String type, DSpaceObject dso, PackageParameters params) { String handle = dso.getHandle(); //start <div> Div div = new Div(); div.setID(gensym("div")); div.setTYPE(type); //make sure we have a handle if (handle == null || handle.length()==0) { log.warn("METS Disseminator is skipping "+type+" without handle: " + dso.toString()); } else { //create <mptr> with handle reference Mptr mptr = new Mptr(); mptr.setID(gensym("mptr")); mptr.setLOCTYPE(Loctype.HANDLE); mptr.setXlinkHref(handle); div.getContent().add(mptr); } //determine file extension of child references, //based on whether we are exporting just a manifest or a full Zip pkg String childFileExtension = (params.getBooleanProperty("manifestOnly", false)) ? "xml" : "zip"; // Always create <mptr> with file-name reference to child package // This is what DSpace will expect the child package to be named during ingestion // (NOTE: without this reference, DSpace will be unable to restore any child objects during ingestion) Mptr mptr2 = new Mptr(); mptr2.setID(gensym("mptr")); mptr2.setLOCTYPE(Loctype.URL); //we get the name of the child package from the Packager -- as it is what will actually create this child pkg file mptr2.setXlinkHref(PackageUtils.getPackageName(dso, childFileExtension)); div.getContent().add(mptr2); return div; } // put handle in canonical URN format -- note that HandleManager's // canonicalize currently returns HTTP URL format. protected String getHandleURN(String handle) { if (handle.startsWith("hdl:")) { return handle; } return "hdl:"+handle; } /** * For a bitstream that's a thumbnail or extracted text, find the * corresponding bitstream it was derived from, in the ORIGINAL bundle. * * @param item * the item we're dealing with * @param derived * the derived bitstream * * @return the corresponding original bitstream (or null) */ protected static Bitstream findOriginalBitstream(Item item, Bitstream derived) throws SQLException { Bundle[] bundles = item.getBundles(); // Filename of original will be filename of the derived bitstream // minus the extension (last 4 chars - .jpg or .txt) String originalFilename = derived.getName().substring(0, derived.getName().length() - 4); // First find "original" bundle for (int i = 0; i < bundles.length; i++) { if ((bundles[i].getName() != null) && bundles[i].getName().equals("ORIGINAL")) { // Now find the corresponding bitstream Bitstream[] bitstreams = bundles[i].getBitstreams(); for (int bsnum = 0; bsnum < bitstreams.length; bsnum++) { if (bitstreams[bsnum].getName().equals(originalFilename)) { return bitstreams[bsnum]; } } } } // Didn't find it return null; } // Get result from crosswalk plugin and add it to the document, // including namespaces and schema. // returns the new/modified element upon success. private MetsElement crosswalkToMetsElement(DisseminationCrosswalk xwalk, DSpaceObject dso, MetsElement me) throws CrosswalkException, IOException, SQLException, AuthorizeException { try { // add crosswalk's namespaces and schemaLocation to this element: String raw = xwalk.getSchemaLocation(); String sloc[] = raw == null ? null : raw.split("\\s+"); Namespace ns[] = xwalk.getNamespaces(); for (int i = 0; i < ns.length; ++i) { String uri = ns[i].getURI(); if (sloc != null && sloc.length > 1 && uri.equals(sloc[0])) { me.setSchema(ns[i].getPrefix(), uri, sloc[1]); } else { me.setSchema(ns[i].getPrefix(), uri); } } // add result of crosswalk PreformedXML pXML = null; if (xwalk.preferList()) { List<Element> res = xwalk.disseminateList(dso); if (!(res == null || res.isEmpty())) { pXML = new PreformedXML(outputter.outputString(res)); } } else { Element res = xwalk.disseminateElement(dso); if (res != null) { pXML = new PreformedXML(outputter.outputString(res)); } } if (pXML != null) { me.getContent().add(pXML); return me; } return null; } catch (CrosswalkObjectNotSupported e) { // ignore this xwalk if object is unsupported. if (log.isDebugEnabled()) { log.debug("Skipping MDsec because of CrosswalkObjectNotSupported: dso=" + dso.toString() + ", xwalk=" + xwalk.getClass().getName()); } return null; } } /** * Cleanup our license file reference links, as Deposit Licenses & CC Licenses can be * added two ways (and we only want to add them to zip package *once*): * (1) Added as a normal Bitstream (assuming LICENSE and CC_LICENSE bundles will be included in pkg) * (2) Added via a 'rightsMD' crosswalk (as they are rights information/metadata on an Item) * <p> * So, if they are being added by *both*, then we want to just link the rightsMD <mdRef> entry so * that it points to the Bitstream location. This implementation is a bit 'hackish', but it's * the best we can do, as the Harvard METS API doesn't allow us to go back and crawl an entire * METS file to look for these inconsistencies/duplications. * * @param context current DSpace Context * @param params current Packager Parameters * @param dso current DSpace Object * @param ref the rightsMD <mdRef> element * @throws SQLException * @throws IOException * @throws AuthorizeException */ protected void linkLicenseRefsToBitstreams(Context context, PackageParameters params, DSpaceObject dso, MdRef mdRef) throws SQLException, IOException, AuthorizeException { //If this <mdRef> is a reference to a DSpace Deposit License if(mdRef.getMDTYPE()!=null && mdRef.getMDTYPE()==Mdtype.OTHER && mdRef.getOTHERMDTYPE()!=null && mdRef.getOTHERMDTYPE().equals("DSpaceDepositLicense")) { //Locate the LICENSE bundle Item i = (Item)dso; Bundle license[] = i.getBundles(Constants.LICENSE_BUNDLE_NAME); //Are we already including the LICENSE bundle's bitstreams in this package? if(license!=null && license.length>0 && includeBundle(license[0])) { //Since we are including the LICENSE bitstreams, lets find our LICENSE bitstream path & link to it. Bitstream licenseBs = PackageUtils.findDepositLicense(context, (Item)dso); mdRef.setXlinkHref(makeBitstreamURL(licenseBs, params)); } } //If this <mdRef> is a reference to a Creative Commons Textual License else if(mdRef.getMDTYPE() != null && mdRef.getMDTYPE() == Mdtype.OTHER && mdRef.getOTHERMDTYPE()!=null && mdRef.getOTHERMDTYPE().equals("CreativeCommonsText")) { //Locate the CC-LICENSE bundle Item i = (Item)dso; Bundle license[] = i.getBundles(CreativeCommons.CC_BUNDLE_NAME); //Are we already including the CC-LICENSE bundle's bitstreams in this package? if(license!=null && license.length>0 && includeBundle(license[0])) { //Since we are including the CC-LICENSE bitstreams, lets find our CC-LICENSE (textual) bitstream path & link to it. Bitstream ccText = CreativeCommons.getLicenseTextBitstream(i); mdRef.setXlinkHref(makeBitstreamURL(ccText, params)); } } //If this <mdRef> is a reference to a Creative Commons RDF License else if(mdRef.getMDTYPE() != null && mdRef.getMDTYPE() == Mdtype.OTHER && mdRef.getOTHERMDTYPE()!=null && mdRef.getOTHERMDTYPE().equals("CreativeCommonsRDF")) { //Locate the CC-LICENSE bundle Item i = (Item)dso; Bundle license[] = i.getBundles(CreativeCommons.CC_BUNDLE_NAME); //Are we already including the CC-LICENSE bundle's bitstreams in this package? if(license!=null && license.length>0 && includeBundle(license[0])) { //Since we are including the CC-LICENSE bitstreams, lets find our CC-LICENSE (RDF) bitstream path & link to it. Bitstream ccRdf = CreativeCommons.getLicenseRdfBitstream(i); mdRef.setXlinkHref(makeBitstreamURL(ccRdf, params)); } } } /** * Build a string which will be used as the "Type" of this object in * the METS manifest. * <P> * Default format is "DSpace [Type-as-string]". * * @param dso DSpaceObject to create type-string for * @return a string which will represent this object Type in METS * @see org.dspace.core.Constants */ public String getObjectTypeString(DSpaceObject dso) { //Format: "DSpace <Type-as-string>" (e.g. "DSpace ITEM", "DSpace COLLECTION", etc) return "DSpace " + Constants.typeText[dso.getType()]; } /** * Returns a user help string which should describe the * additional valid command-line options that this packager * implementation will accept when using the <code>-o</code> or * <code>--option</code> flags with the Packager script. * * @return a string describing additional command-line options available * with this packager */ @Override public String getParameterHelp() { return "* manifestOnly=[boolean] " + "If true, only export the METS manifest (mets.xml) and don't export content files (defaults to false)." + "\n\n" + "* unauthorized=[value] " + "If 'skip', skip over any files which the user doesn't have authorization to read. " + "If 'zero', create a zero-length file for any files the user doesn't have authorization to read. " + "By default, an AuthorizationException will be thrown for any files the user cannot read."; } /** * Get the URL by which the METS manifest refers to a Bitstream * member within the same package. In other words, this is generally * a relative path link to where the Bitstream file is within the Zipped * up package. * <p> * For a manifest-only METS, this is a reference to an HTTP URL where * the bitstream should be able to be downloaded from. * * @param bitstream the Bitstream * @param params Packager Parameters * @return String in URL format naming path to bitstream. */ public String makeBitstreamURL(Bitstream bitstream, PackageParameters params) { // if bare manifest, use external "persistent" URI for bitstreams if (params != null && (params.getBooleanProperty("manifestOnly", false))) { // Try to build a persistent(-ish) URI for bitstream // Format: {site-base-url}/bitstream/{item-handle}/{sequence-id}/{bitstream-name} try { // get handle of parent Item of this bitstream, if there is one: String handle = null; Bundle[] bn = bitstream.getBundles(); if (bn.length > 0) { Item bi[] = bn[0].getItems(); if (bi.length > 0) { handle = bi[0].getHandle(); } } if (handle != null) { return ConfigurationManager .getProperty("dspace.url") + "/bitstream/" + handle + "/" + String.valueOf(bitstream.getSequenceID()) + "/" + URLEncoder.encode(bitstream.getName(), "UTF-8"); } else { //no Handle assigned, so persistent(-ish) URI for bitstream is // Format: {site-base-url}/retrieve/{bistream-internal-id} return ConfigurationManager .getProperty("dspace.url") + "/retrieve/" + String.valueOf(bitstream.getID()); } } catch (SQLException e) { log.error("Database problem", e); } catch (UnsupportedEncodingException e) { log.error("Unknown character set", e); } // We should only get here if we failed to build a nice URL above // so, by default, we're just going to return the bitstream name. return bitstream.getName(); } else { String base = "bitstream_"+String.valueOf(bitstream.getID()); String ext[] = bitstream.getFormat().getExtensions(); return (ext.length > 0) ? base+"."+ext[0] : base; } } /** * Create metsHdr element - separate so subclasses can override. */ public abstract MetsHdr makeMetsHdr(Context context, DSpaceObject dso, PackageParameters params); /** * Returns name of METS profile to which this package conforms, e.g. * "DSpace METS DIP Profile 1.0" * @return string name of profile. */ public abstract String getProfile(); /** * Returns fileGrp's USE attribute value corresponding to a DSpace bundle name. * * @param bname name of DSpace bundle. * @return string name of fileGrp */ public abstract String bundleToFileGrp(String bname); /** * Get the types of Item-wide DMD to include in package. * Each element of the returned array is a String, which * MAY be just a simple name, naming both the Crosswalk Plugin and * the METS "MDTYPE", <em>or</em> a colon-separated pair consisting of * the METS name followed by a colon and the Crosswalk Plugin name. * E.g. the type string <code>"DC:qualifiedDublinCore"</code> tells it to * create a METS section with <code>MDTYPE="DC"</code> and use the plugin * named "qualifiedDublinCore" to obtain the data. * @param params the PackageParameters passed to the disseminator. * @return array of metadata type strings, never null. */ public abstract String [] getDmdTypes(Context context, DSpaceObject dso, PackageParameters params) throws SQLException, IOException, AuthorizeException; /** * Get the type string of the technical metadata to create for each * object and each Bitstream in an Item. The type string may be a * simple name or colon-separated compound as specified for * <code>getDmdTypes()</code> above. * @param params the PackageParameters passed to the disseminator. * @return array of metadata type strings, never null. */ public abstract String[] getTechMdTypes(Context context, DSpaceObject dso, PackageParameters params) throws SQLException, IOException, AuthorizeException; /** * Get the type string of the source metadata to create for each * object and each Bitstream in an Item. The type string may be a * simple name or colon-separated compound as specified for * <code>getDmdTypes()</code> above. * @param params the PackageParameters passed to the disseminator. * @return array of metadata type strings, never null. */ public abstract String[] getSourceMdTypes(Context context, DSpaceObject dso, PackageParameters params) throws SQLException, IOException, AuthorizeException; /** * Get the type string of the "digiprov" (digital provenance) * metadata to create for each object and each Bitstream in an Item. * The type string may be a simple name or colon-separated compound * as specified for <code>getDmdTypes()</code> above. * * @param params the PackageParameters passed to the disseminator. * @return array of metadata type strings, never null. */ public abstract String[] getDigiprovMdTypes(Context context, DSpaceObject dso, PackageParameters params) throws SQLException, IOException, AuthorizeException; /** * Get the type string of the "rights" (permission and/or license) * metadata to create for each object and each Bitstream in an Item. * The type string may be a simple name or colon-separated compound * as specified for <code>getDmdTypes()</code> above. * * @param params the PackageParameters passed to the disseminator. * @return array of metadata type strings, never null. */ public abstract String[] getRightsMdTypes(Context context, DSpaceObject dso, PackageParameters params) throws SQLException, IOException, AuthorizeException; /** * Add any additional <code>structMap</code> elements to the * METS document, as required by this subclass. A simple default * structure map which fulfills the minimal DSpace METS DIP/SIP * requirements is already present, so this does not need to do anything. * @param mets the METS document to which to add structMaps */ public abstract void addStructMap(Context context, DSpaceObject dso, PackageParameters params, Mets mets) throws SQLException, IOException, AuthorizeException, MetsException; /** * @return true when this bundle should be included as "content" * in the package.. e.g. DSpace SIP does not include metadata bundles. */ public abstract boolean includeBundle(Bundle bundle); }