org.dspace.content.packager.AbstractMETSIngester Maven / Gradle / Ivy
Show all versions of dspace-api Show documentation
/**
* 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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.InProgressSubmission;
import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.crosswalk.MetadataValidationException;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.BundleService;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.workflow.WorkflowException;
import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.jdom.Element;
/**
* Base class for package ingester of METS (Metadata Encoding and Transmission
* Standard) Packages.
* See
* http://www.loc.gov/standards/mets/.
*
* This is a generic packager framework intended to be subclassed to create
* ingesters for more specific METS "profiles". METS is an abstract and flexible
* framework that can encompass many different kinds of metadata and inner
* package structures.
*
*
* Package Parameters:
*
* validate
-- true/false attempt to schema-validate the METS
* manifest.
* manifestOnly
-- package consists only of a manifest
* document.
* ignoreHandle
-- true/false, ignore AIP's idea of handle
* when ingesting.
* ignoreParent
-- true/false, ignore AIP's idea of parent
* when ingesting.
*
*
* Configuration Properties:
*
* mets.CONFIGNAME.ingest.preserveManifest
- if true,
* the METS manifest itself is preserved in a bitstream named
* mets.xml
in the METADATA
bundle. If it is
* false (the default), the manifest is discarded after ingestion.
*
* mets.CONFIGNAME.ingest.manifestBitstreamFormat
- short name
* of the bitstream format to apply to the manifest; MUST be specified when
* preserveManifest is true.
*
* mets.default.ingest.crosswalk.MD_SEC_NAME
= PLUGIN_NAME
* Establishes a default crosswalk plugin for the given type of metadata in a
* METS mdSec (e.g. "DC", "MODS"). The plugin may be either a stream or
* XML-oriented ingestion crosswalk. Subclasses can override the default mapping
* with their own, substituting their configurationName for "default" in the
* configuration property key above.
*
* mets.CONFIGNAME.ingest.useCollectionTemplate
- if
* true, when an item is created, use the collection template. If it is
* false (the default), any existing collection template is ignored.
*
*
* @author Larry Stone
* @author Tim Donohue
* @see org.dspace.content.packager.METSManifest
* @see AbstractPackageIngester
* @see PackageIngester
*/
public abstract class AbstractMETSIngester extends AbstractPackageIngester {
/**
* log4j category
*/
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AbstractMETSIngester.class);
protected final BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
protected final BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance()
.getBitstreamFormatService();
protected final BundleService bundleService = ContentServiceFactory.getInstance().getBundleService();
protected final CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
protected final CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
protected final ItemService itemService = ContentServiceFactory.getInstance().getItemService();
protected final HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
protected final WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance()
.getWorkspaceItemService();
protected final ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
/**
*
* An instance of ZipMdrefManager holds the state needed to retrieve the
* contents of an external metadata stream referenced by an
* mdRef
element in a Zipped up METS manifest.
*
* Initialize it with the Content (ORIGINAL) Bundle containing all of the
* metadata bitstreams. Match an mdRef by finding the bitstream with the
* same name.
*/
protected static final class MdrefManager implements METSManifest.Mdref {
private File packageFile = null;
private final PackageParameters params;
// constructor initializes from package file
private MdrefManager(File packageFile, PackageParameters params) {
super();
this.packageFile = packageFile;
this.params = params;
}
/**
* Make the contents of an external resource mentioned in an
* mdRef
element available as an InputStream
.
* See the METSManifest.MdRef
interface for details.
*
* @param mdref the METS mdRef element to locate the input for.
* @return the input stream of its content.
* @throws MetadataValidationException if validation error
* @throws IOException if IO error
* @see METSManifest
*/
@Override
public InputStream getInputStream(Element mdref)
throws MetadataValidationException, IOException {
String path = METSManifest.getFileName(mdref);
if (packageFile == null) {
throw new MetadataValidationException(
"Failed referencing mdRef element, because there is no package specified.");
}
// Use the 'getFileInputStream()' method from the
// AbstractMETSIngester to retrieve the inputstream for the
// referenced external metadata file.
return AbstractMETSIngester.getFileInputStream(packageFile, params,
path);
}
} // end MdrefManager class
/**
* Create a new DSpace object out of a METS content package. All contents
* are dictated by the METS manifest. Package is a ZIP archive (or
* optionally bare manifest XML document). In a Zip, all files relative to
* top level and the manifest (as per spec) in mets.xml.
*
* @param context DSpace context.
* @param parent parent under which to create new object (may be null -- in
* which case ingester must determine parent from package or
* throw an error).
* @param pkgFile The package file to ingest
* @param params Properties-style list of options (interpreted by each
* packager).
* @param license may be null, which takes default license.
* @return DSpaceObject created by ingest.
* @throws PackageValidationException if package validation error
* if package is unacceptable or there is a fatal error turning
* it into a DSpaceObject.
* @throws CrosswalkException if crosswalk error
* @throws AuthorizeException if authorization error
* @throws SQLException if database error
* @throws IOException if IO error
* @throws WorkflowException if workflow error
*/
@Override
public DSpaceObject ingest(Context context, DSpaceObject parent,
File pkgFile, PackageParameters params, String license)
throws PackageValidationException, CrosswalkException,
AuthorizeException, SQLException, IOException, WorkflowException {
// parsed out METS Manifest from the file.
METSManifest manifest = null;
// new DSpace object created
DSpaceObject dso = null;
try {
log.info(LogHelper.getHeader(context, "package_parse",
"Parsing package for ingest, file=" + pkgFile.getName()));
// Parse our ingest package, extracting out the METS manifest in the
// package
manifest = parsePackage(context, pkgFile, params);
// must have a METS Manifest to ingest anything
if (manifest == null) {
throw new PackageValidationException(
"No METS Manifest found (filename="
+ METSManifest.MANIFEST_FILE
+ "). Package is unacceptable!");
}
// validate our manifest
checkManifest(manifest);
// if we are not restoring an object (i.e. we are submitting a new
// object) then, default the 'ignoreHandle' option to true (as a new
// object should get a new handle by default)
if (!params.restoreModeEnabled()
&& !params.containsKey("ignoreHandle")) { // ignore the handle in the manifest, and instead create a new
// handle
params.addProperty("ignoreHandle", "true");
}
// if we have a Parent Object, default 'ignoreParent' option to True
// (this will ignore the Parent specified in manifest)
if (parent != null && !params
.containsKey("ignoreParent")) { // ignore the parent in the manifest, and instead use the
// specified parent object
params.addProperty("ignoreParent", "true");
}
// Actually ingest the object described by the METS Manifest
dso = ingestObject(context, parent, manifest, pkgFile, params,
license);
//if ingestion was successful
if (dso != null) {
// Log whether we finished an ingest (create new obj) or a restore
// (restore previously existing obj)
String action = "package_ingest";
if (params.restoreModeEnabled()) {
action = "package_restore";
}
log.info(LogHelper.getHeader(context, action,
"Created new Object, type="
+ Constants.typeText[dso.getType()] + ", handle="
+ dso.getHandle() + ", dbID="
+ String.valueOf(dso.getID())));
// Check if the Packager is currently running recursively.
// If so, this means the Packager will attempt to recursively
// ingest all referenced child packages.
if (params.recursiveModeEnabled()) {
// Retrieve list of all Child object METS file paths from the
// current METS manifest.
// This is our list of known child packages
String[] childFilePaths = manifest.getChildMetsFilePaths();
// Save this list to our AbstractPackageIngester (and note which
// DSpaceObject the pkgs relate to).
// NOTE: The AbstractPackageIngester itself will perform the
// recursive ingest call, based on these child pkg references
for (int i = 0; i < childFilePaths.length; i++) {
addPackageReference(dso, childFilePaths[i]);
}
}
} //end if dso not null
return dso;
} catch (SQLException se) {
// no need to really clean anything up,
// transaction rollback will get rid of it anyway.
dso = null;
// Pass this exception on to the next handler.
throw se;
}
}
/**
* Parse a given input package, ultimately returning the METS manifest out
* of the package. METS manifest is assumed to be a file named 'mets.xml'
*
* @param context DSpace Context
* @param pkgFile package to parse
* @param params Ingestion parameters
* @return parsed out METSManifest
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
* @throws MetadataValidationException if metadata validation error
*/
protected METSManifest parsePackage(Context context, File pkgFile,
PackageParameters params) throws IOException, SQLException,
AuthorizeException, MetadataValidationException {
// whether or not to validate the METSManifest before processing
// (default=false)
// (Even though it's preferable to validate -- it's costly and takes a
// lot of time, unless you cache schemas locally)
boolean validate = params.getBooleanProperty("validate", false);
// parsed out METS Manifest from the file.
METSManifest manifest = null;
// try to locate the METS Manifest in package
// 1. read "package" stream: it will be either bare Manifest
// or Package contents into bitstreams, depending on params:
if (params.getBooleanProperty("manifestOnly", false)) {
// parse the bare METS manifest and sanity-check it.
manifest = METSManifest.create(new FileInputStream(pkgFile),
validate, getConfigurationName());
} else {
try (ZipFile zip = new ZipFile(pkgFile)) {
// Retrieve the manifest file entry (named mets.xml)
ZipEntry manifestEntry = zip.getEntry(METSManifest.MANIFEST_FILE);
if (manifestEntry != null) {
// parse the manifest and sanity-check it.
manifest = METSManifest.create(zip.getInputStream(manifestEntry),
validate, getConfigurationName());
}
}
}
// return our parsed out METS manifest
return manifest;
}
/**
* Ingest/import a single DSpace Object, based on the associated METS
* Manifest and the parameters passed to the METSIngester
*
* @param context DSpace Context
* @param parent Parent DSpace Object
* @param manifest the parsed METS Manifest
* @param pkgFile the full package file (which may include content files if a
* zip)
* @param params Parameters passed to METSIngester
* @param license DSpace license agreement
* @return completed result as a DSpace object
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
* @throws CrosswalkException if crosswalk error
* @throws MetadataValidationException if metadata validation error
* @throws WorkflowException if workflow error
* @throws PackageValidationException if package validation error
*/
protected DSpaceObject ingestObject(Context context, DSpaceObject parent,
METSManifest manifest, File pkgFile, PackageParameters params,
String license) throws IOException, SQLException,
AuthorizeException, CrosswalkException,
PackageValidationException, WorkflowException {
// type of DSpace Object (one of the type constants)
int type;
// -- Step 1 --
// Extract basic information (type, parent, handle) about DSpace object
// represented by manifest
type = getObjectType(manifest);
// if no parent passed in (or ignoreParent is false),
// attempt to determine parent DSpace object from manifest
if (type != Constants.SITE
&& (parent == null || !params.getBooleanProperty(
"ignoreParent", false))) {
try {
// get parent object from manifest
parent = getParentObject(context, manifest);
} catch (UnsupportedOperationException e) {
//If user specified to skip item ingest if any "missing parent" error message occur
if (params.getBooleanProperty("skipIfParentMissing", false)) {
//log a warning instead of throwing an error
log.warn(LogHelper.getHeader(context, "package_ingest",
"SKIPPING ingest of object '" + manifest.getObjID()
+ "' as parent DSpace Object could not be found. "
+ "If you are running a recursive ingest, it is likely this " +
"object will be created as soon as its parent is created."));
//return a null object (nothing ingested as parent was missing)
return null;
} else {
//else, throw exception upward to display to user
throw e;
}
}
}
String handle = null;
// if we are *not* ignoring the handle in manifest (i.e. ignoreHandle is
// false)
if (!params.getBooleanProperty("ignoreHandle", false)) {
// get handle from manifest
handle = getObjectHandle(manifest);
}
UUID uuid = getObjectID(manifest);
// -- Step 2 --
// Create our DSpace Object based on info parsed from manifest, and
// packager params
DSpaceObject dso;
try {
dso = PackageUtils.createDSpaceObject(context, parent,
type, handle, uuid, params);
} catch (SQLException sqle) {
throw new PackageValidationException("Exception while ingesting "
+ pkgFile.getPath(), sqle);
}
// if we are uninitialized, throw an error -- something's wrong!
if (dso == null) {
throw new PackageValidationException(
"Unable to initialize object specified by package (type='"
+ type + "', handle='" + handle + "' and parent='"
+ parent.getHandle() + "').");
}
// -- Step 3 --
// Run our Administrative metadata crosswalks!
// initialize callback object which will retrieve external inputstreams
// for any 's found in METS
MdrefManager callback = new MdrefManager(pkgFile, params);
// Crosswalk the sourceMD first, so that we make sure to fill in
// submitter info (and any other initial applicable info)
manifest.crosswalkObjectSourceMD(context, params, dso, callback);
// Next, crosswalk techMD, digiprovMD, rightsMD
manifest.crosswalkObjectOtherAdminMD(context, params, dso, callback);
// -- Step 4 --
// Run our Descriptive metadata (dublin core, etc) crosswalks!
crosswalkObjectDmd(context, dso, manifest, callback, manifest
.getItemDmds(), params);
// For Items, also sanity-check the metadata for minimum requirements.
if (type == Constants.ITEM) {
PackageUtils.checkItemMetadata((Item) dso);
}
// -- Step 5 --
// Add all content files as bitstreams on new DSpace Object
if (type == Constants.ITEM) {
Item item = (Item) dso;
//Check if this item is still in a user's workspace.
//It should be, as we haven't completed its install yet.
WorkspaceItem wsi = workspaceItemService.findByItem(context, item);
// Get collection this item is being submitted to
Collection collection = item.getOwningCollection();
if (collection == null) {
// Get the collection this workspace item belongs to
if (wsi != null) {
collection = wsi.getCollection();
}
}
// save manifest as a bitstream in Item if desired
if (preserveManifest()) {
addManifestBitstream(context, item, manifest);
}
// save all other bitstreams in Item
addBitstreams(context, item, manifest, pkgFile, params, callback);
// have subclass manage license since it may be extra package file.
addLicense(context, item, license, collection, params);
// Subclass hook for final checks and rearrangements
// (this allows subclasses to do some final validation / changes as
// necessary)
finishObject(context, dso, params);
// Finally, if item is still in the workspace, then we actually need
// to install it into the archive & assign its handle.
if (wsi != null) {
// Finish creating the item. This actually assigns the handle,
// and will either install item immediately or start a workflow, based on params
PackageUtils.finishCreateItem(context, wsi, handle, params);
}
} else if (type == Constants.COLLECTION || type == Constants.COMMUNITY) {
// Add logo if one is referenced from manifest
addContainerLogo(context, dso, manifest, pkgFile, params);
if (type == Constants.COLLECTION) {
//Add template item if one is referenced from manifest (only for Collections)
addTemplateItem(context, dso, manifest, pkgFile, params, callback);
}
// Subclass hook for final checks and rearrangements
// (this allows subclasses to do some final validation / changes as
// necessary)
finishObject(context, dso, params);
} else if (type == Constants.SITE) {
// Do nothing by default -- Crosswalks will handle anything necessary to ingest at Site-level
// Subclass hook for final checks and rearrangements
// (this allows subclasses to do some final validation / changes as
// necessary)
finishObject(context, dso, params);
} else {
throw new PackageValidationException(
"Unknown DSpace Object type in package, type="
+ String.valueOf(type));
}
// -- Step 6 --
// Finish things up!
// Update the object to make sure all changes are committed
PackageUtils.updateDSpaceObject(context, dso);
return dso;
}
/**
* Replace the contents of a single DSpace Object, based on the associated
* METS Manifest and the parameters passed to the METSIngester.
*
* @param context DSpace Context
* @param dso DSpace Object to replace
* @param manifest the parsed METS Manifest
* @param pkgFile the full package file (which may include content files if a
* zip)
* @param params Parameters passed to METSIngester
* @param license DSpace license agreement
* @return completed result as a DSpace object
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
* @throws CrosswalkException if crosswalk error
* @throws MetadataValidationException if metadata validation error
* @throws PackageValidationException if package validation error
*/
protected DSpaceObject replaceObject(Context context, DSpaceObject dso,
METSManifest manifest, File pkgFile, PackageParameters params,
String license) throws IOException, SQLException,
AuthorizeException, CrosswalkException,
MetadataValidationException, PackageValidationException {
// -- Step 1 --
// Before going forward with the replace, let's verify these objects are
// of the same TYPE! (We don't want to go around trying to replace a
// COMMUNITY with an ITEM -- that's dangerous.)
int manifestType = getObjectType(manifest);
if (manifestType != dso.getType()) {
throw new PackageValidationException(
"The object type of the METS manifest ("
+ Constants.typeText[manifestType]
+ ") does not match up with the object type ("
+ Constants.typeText[dso.getType()]
+ ") of the DSpaceObject to be replaced!");
}
if (log.isDebugEnabled()) {
log.debug("Object to be replaced (handle=" + dso.getHandle()
+ ") is " + Constants.typeText[dso.getType()] + " id="
+ dso.getID());
}
// -- Step 2 --
// Clear out current object (as we are replacing all its contents &
// metadata)
// remove all files attached to this object
// (For communities/collections this just removes the logo bitstream)
PackageUtils.removeAllBitstreams(context, dso);
// clear out all metadata values associated with this object
PackageUtils.clearAllMetadata(context, dso);
// TODO -- We are currently NOT clearing out the following during a
// replace. So, even after a replace, the following information may be
// retained in the system:
// o Rights/Permissions in system or on objects
// o Collection item templates or Content Source info (e.g. OAI
// Harvesting collections)
// o Item status (embargo, withdrawn) or mappings to other collections
// -- Step 3 --
// Run our Administrative metadata crosswalks!
// initialize callback object which will retrieve external inputstreams
// for any s found in METS
MdrefManager callback = new MdrefManager(pkgFile, params);
// Crosswalk the sourceMD first, so that we make sure to fill in
// submitter info (and any other initial applicable info)
manifest.crosswalkObjectSourceMD(context, params, dso, callback);
// Next, crosswalk techMD, digiprovMD, rightsMD
manifest.crosswalkObjectOtherAdminMD(context, params, dso, callback);
// -- Step 4 --
// Add all content files as bitstreams on new DSpace Object
if (dso.getType() == Constants.ITEM) {
Item item = (Item) dso;
// save manifest as a bitstream in Item if desired
if (preserveManifest()) {
addManifestBitstream(context, item, manifest);
}
// save all other bitstreams in Item
addBitstreams(context, item, manifest, pkgFile, params, callback);
// have subclass manage license since it may be extra package file.
Collection owningCollection = (Collection) ContentServiceFactory.getInstance().getDSpaceObjectService(dso)
.getParentObject(context, dso);
if (owningCollection == null) {
//We are probably dealing with an item that isn't archived yet
InProgressSubmission inProgressSubmission = workspaceItemService.findByItem(context, item);
if (inProgressSubmission == null) {
inProgressSubmission = WorkflowServiceFactory.getInstance().getWorkflowItemService()
.findByItem(context, item);
}
owningCollection = inProgressSubmission.getCollection();
}
addLicense(context, item, license, owningCollection
, params);
// FIXME ?
// should set lastModifiedTime e.g. when ingesting AIP.
// maybe only do it in the finishObject() callback for AIP.
} else if (dso.getType() == Constants.COLLECTION || dso.getType() == Constants.COMMUNITY) {
// Add logo if one is referenced from manifest
addContainerLogo(context, dso, manifest, pkgFile, params);
} else if (dso.getType() == Constants.SITE) {
// Do nothing -- Crosswalks will handle anything necessary to replace at Site-level
}
// -- Step 5 --
// Run our Descriptive metadata (dublin core, etc) crosswalks!
crosswalkObjectDmd(context, dso, manifest, callback, manifest
.getItemDmds(), params);
// For Items, also sanity-check the metadata for minimum requirements.
if (dso.getType() == Constants.ITEM) {
PackageUtils.checkItemMetadata((Item) dso);
}
// -- Step 6 --
// Finish things up!
// Subclass hook for final checks and rearrangements
// (this allows subclasses to do some final validation / changes as
// necessary)
finishObject(context, dso, params);
// Update the object to make sure all changes are committed
PackageUtils.updateDSpaceObject(context, dso);
return dso;
}
/**
* Add Bitstreams to an Item, based on the files listed in the METS Manifest
*
* @param context DSpace Context
* @param item DSpace Item
* @param manifest METS Manifest
* @param pkgFile the full package file (which may include content files if a
* zip)
* @param params Ingestion Parameters
* @param mdRefCallback MdrefManager storing info about mdRefs in manifest
* @throws SQLException if database error
* @throws IOException if IO error
* @throws AuthorizeException if authorization error
* @throws MetadataValidationException if metadata validation error
* @throws CrosswalkException if crosswalk error
* @throws PackageValidationException if package validation error
*/
protected void addBitstreams(Context context, Item item,
METSManifest manifest, File pkgFile, PackageParameters params,
MdrefManager mdRefCallback) throws SQLException, IOException,
AuthorizeException, MetadataValidationException,
CrosswalkException, PackageValidationException {
// Step 1 -- find the ID of the primary or Logo bitstream in manifest
String primaryID = null;
Element primaryFile = manifest.getPrimaryOrLogoBitstream();
if (primaryFile != null) {
primaryID = primaryFile.getAttributeValue("ID");
if (log.isDebugEnabled()) {
log.debug("Got primary bitstream file ID=\"" + primaryID + "\"");
}
}
// Step 2 -- find list of all content files from manifest
// Loop through these files, and add them one by one to Item
List manifestContentFiles = manifest
.getContentFiles();
List manifestBundleFiles = manifest
.getBundleFiles();
boolean setPrimaryBitstream = false;
BitstreamFormat unknownFormat = bitstreamFormatService.findUnknown(context);
for (Iterator mi = manifestContentFiles.iterator(); mi
.hasNext(); ) {
Element mfile = mi.next();
// basic validation -- check that it has an ID attribute
String mfileID = mfile.getAttributeValue("ID");
if (mfileID == null) {
throw new PackageValidationException(
"Invalid METS Manifest: file element without ID attribute.");
}
// retrieve path/name of file in manifest
String path = METSManifest.getFileName(mfile);
// extract the file input stream from package (or retrieve
// externally, if it is an externally referenced file)
InputStream fileStream = getFileInputStream(pkgFile, params, path);
// retrieve bundle name from manifest
String bundleName = METSManifest.getBundleName(mfile);
// Find or create the bundle where bitstream should be attached
Bundle bundle;
List bns = itemService.getBundles(item, bundleName);
if (CollectionUtils.isNotEmpty(bns)) {
bundle = bns.get(0);
} else {
bundle = bundleService.create(context, item, bundleName);
}
// Create the bitstream in the bundle & initialize its name
Bitstream bitstream = bitstreamService.create(context, bundle, fileStream);
bitstream.setName(context, path);
// Set bitstream sequence id, if known
String seqID = mfile.getAttributeValue("SEQ");
if (seqID != null && !seqID.isEmpty()) {
bitstream.setSequenceID(Integer.parseInt(seqID));
}
// crosswalk this bitstream's administrative metadata located in
// METS manifest (or referenced externally)
manifest.crosswalkBitstream(context, params, bitstream, mfileID,
mdRefCallback);
// is this the primary bitstream?
if (primaryID != null && mfileID.equals(primaryID)) {
bundle.setPrimaryBitstreamID(bitstream);
bundleService.update(context, bundle);
setPrimaryBitstream = true;
}
// Run any finishing activities -- this allows subclasses to
// change default bitstream information
finishBitstream(context, bitstream, mfile, manifest, params);
// Last-ditch attempt to divine the format, if crosswalk failed to
// set it:
// 1. attempt to guess from MIME type
// 2. if that fails, guess from "name" extension.
if (bitstream.getFormat(context).equals(unknownFormat)) {
if (log.isDebugEnabled()) {
log.debug("Guessing format of Bitstream left un-set: "
+ bitstream.toString());
}
String mimeType = mfile.getAttributeValue("MIMETYPE");
BitstreamFormat bf = (mimeType == null) ? null
: bitstreamFormatService.findByMIMEType(context, mimeType);
if (bf == null) {
bf = bitstreamFormatService.guessFormat(context, bitstream);
}
bitstreamService.setFormat(context, bitstream, bf);
}
bitstreamService.update(context, bitstream);
} // end for each manifest file
for (Iterator mi = manifestBundleFiles.iterator(); mi
.hasNext(); ) {
Element mfile = mi.next();
String bundleName = METSManifest.getBundleName(mfile, false);
Bundle bundle;
List bns = itemService.getBundles(item, bundleName);
if (CollectionUtils.isNotEmpty(bns)) {
bundle = bns.get(0);
} else {
bundle = bundleService.create(context, item, bundleName);
}
String mfileGrp = mfile.getAttributeValue("ADMID");
if (mfileGrp != null) {
manifest.crosswalkBundle(context, params, bundle, mfileGrp, mdRefCallback);
} else {
if (log.isDebugEnabled()) {
log.debug("Ingesting bundle with no ADMID, not crosswalking bundle metadata");
}
}
bundleService.update(context, bundle);
} // end for each manifest file
// Step 3 -- Sanity checks
// sanity check for primary bitstream
if (primaryID != null && !setPrimaryBitstream) {
log.warn("Could not find primary bitstream file ID=\"" + primaryID
+ "\" in manifest file \"" + pkgFile.getAbsolutePath()
+ "\"");
}
}
/**
* Save/Preserve the METS Manifest as a Bitstream attached to the given
* DSpace item.
*
* @param context DSpace Context
* @param item DSpace Item
* @param manifest The METS Manifest
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
* @throws PackageValidationException if package validation error
*/
protected void addManifestBitstream(Context context, Item item,
METSManifest manifest) throws IOException, SQLException,
AuthorizeException, PackageValidationException {
// We'll save the METS Manifest as part of the METADATA bundle.
Bundle mdBundle = bundleService.create(context, item, Constants.METADATA_BUNDLE_NAME);
// Create a Bitstream from the METS Manifest's content
Bitstream manifestBitstream = bitstreamService.create(context, mdBundle, manifest
.getMetsAsStream());
manifestBitstream.setName(context, METSManifest.MANIFEST_FILE);
manifestBitstream.setSource(context, METSManifest.MANIFEST_FILE);
bitstreamService.update(context, manifestBitstream);
// Get magic bitstream format to identify manifest.
String fmtName = getManifestBitstreamFormat();
if (fmtName == null) {
throw new PackageValidationException(
"Configuration Error: No Manifest BitstreamFormat configured for METS ingester type="
+ getConfigurationName());
}
BitstreamFormat manifestFormat = PackageUtils
.findOrCreateBitstreamFormat(context, fmtName,
"application/xml", fmtName + " package manifest");
manifestBitstream.setFormat(context, manifestFormat);
bitstreamService.update(context, manifestBitstream);
}
/**
* Add a Logo to a Community or Collection container object based on a METS
* Manifest.
*
* @param context DSpace Context
* @param dso DSpace Container Object
* @param manifest METS Manifest
* @param pkgFile the full package file (which may include content files if a
* zip)
* @param params Ingestion Parameters
* @throws SQLException if database error
* @throws IOException if IO error
* @throws AuthorizeException if authorization error
* @throws MetadataValidationException if metadata validation error
* @throws PackageValidationException if package validation error
*/
protected void addContainerLogo(Context context, DSpaceObject dso,
METSManifest manifest, File pkgFile, PackageParameters params)
throws SQLException, IOException, AuthorizeException,
MetadataValidationException, PackageValidationException {
Element logoRef = manifest.getPrimaryOrLogoBitstream();
// only continue if a logo specified in manifest
if (logoRef != null) {
// Find ID of logo file
String logoID = logoRef.getAttributeValue("ID");
// Loop through manifest content files to find actual logo file
for (Iterator mi = manifest
.getContentFiles().iterator(); mi.hasNext(); ) {
Element mfile = mi.next();
if (logoID.equals(mfile.getAttributeValue("ID"))) {
String path = METSManifest.getFileName(mfile);
// extract the file input stream from package (or retrieve
// externally, if it is an externally referenced file)
InputStream fileStream = getFileInputStream(pkgFile,
params, path);
// Add this logo to the Community/Collection
if (dso.getType() == Constants.COLLECTION) {
collectionService.setLogo(context, ((Collection) dso), fileStream);
} else {
communityService.setLogo(context, ((Community) dso), fileStream);
}
break;
}
} // end for each file in manifest
} // end if logo reference found
}
/**
* Add a Template Item to a Collection container object based on a METS
* Manifest.
*
* @param context DSpace Context
* @param dso DSpace Container Object
* @param manifest METS Manifest
* @param pkgFile the full package file (which may include content files if a
* zip)
* @param params Ingestion Parameters
* @param callback the MdrefManager (manages all external metadata files
* referenced by METS mdref
elements)
* @throws SQLException if database error
* @throws IOException if IO error
* @throws AuthorizeException if authorization error
* @throws MetadataValidationException if metadata validation error
* @throws PackageValidationException if package validation error
*/
protected void addTemplateItem(Context context, DSpaceObject dso,
METSManifest manifest, File pkgFile, PackageParameters params,
MdrefManager callback)
throws SQLException, IOException, AuthorizeException,
CrosswalkException, PackageValidationException {
//Template items only valid for collections
if (dso.getType() != Constants.COLLECTION) {
return;
}
Collection collection = (Collection) dso;
//retrieve list of all s representing child objects from manifest
List childObjList = manifest.getChildObjDivs();
if (childObjList != null && !childObjList.isEmpty()) {
Element templateItemDiv = null;
Iterator childIterator = childObjList.iterator();
//Search for the child with a type of "DSpace ITEM Template"
while (childIterator.hasNext()) {
Element childDiv = (Element) childIterator.next();
String childType = childDiv.getAttributeValue("TYPE");
//should be the only child of type "ITEM" with "Template" for a suffix
if (childType.contains(Constants.typeText[Constants.ITEM]) &&
childType.endsWith(AbstractMETSDisseminator.TEMPLATE_TYPE_SUFFIX)) {
templateItemDiv = childDiv;
break;
}
}
//If an Template Item was found, create it with the specified metadata
if (templateItemDiv != null) {
//make sure this templateItemDiv is associated with one or more dmdSecs
String templateDmdIds = templateItemDiv.getAttributeValue("DMDID");
if (templateDmdIds != null) {
//create our template item & get a reference to it
itemService.createTemplateItem(context, collection);
Item templateItem = collection.getTemplateItem();
//get a reference to the dmdSecs which describe the metadata for this template item
Element[] templateDmds = manifest.getDmdElements(templateDmdIds);
// Run our Descriptive metadata (dublin core, etc) crosswalks to add metadata to template item
crosswalkObjectDmd(context, templateItem, manifest, callback, templateDmds, params);
// update the template item to save metadata changes
PackageUtils.updateDSpaceObject(context, templateItem);
}
}
}
}
/**
* Replace an existing DSpace object with the contents of a METS-based
* package. All contents are dictated by the METS manifest. Package is a ZIP
* archive (or optionally bare manifest XML document). In a Zip, all files
* relative to top level and the manifest (as per spec) in mets.xml.
*
* This method is similar to ingest(), except that if the object already
* exists in DSpace, it is emptied of files and metadata. The METS-based
* package is then used to ingest new values for these.
*
* @param context DSpace Context
* @param dsoToReplace DSpace Object to be replaced (may be null if it will be
* specified in the METS manifest itself)
* @param pkgFile The package file to ingest
* @param params Parameters passed from the packager script
* @return DSpaceObject created by ingest.
* @throws PackageValidationException if package validation error
* if package is unacceptable or there is a fatal error turning
* it into a DSpace Object.
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
* @throws CrosswalkException if crosswalk error
* @throws WorkflowException if workflow error
*/
@Override
public DSpaceObject replace(Context context, DSpaceObject dsoToReplace,
File pkgFile, PackageParameters params)
throws PackageValidationException, CrosswalkException,
AuthorizeException, SQLException, IOException, WorkflowException {
// parsed out METS Manifest from the file.
METSManifest manifest = null;
// resulting DSpace Object
DSpaceObject dso = null;
try {
log.info(LogHelper.getHeader(context, "package_parse",
"Parsing package for replace, file=" + pkgFile.getName()));
// Parse our ingest package, extracting out the METS manifest in the
// package
manifest = parsePackage(context, pkgFile, params);
// must have a METS Manifest to replace anything
if (manifest == null) {
throw new PackageValidationException(
"No METS Manifest found (filename="
+ METSManifest.MANIFEST_FILE
+ "). Package is unacceptable!");
}
// It's possible that the object to replace will be passed in as
// null. Let's determine the handle of the object to replace.
if (dsoToReplace == null) {
// since we don't know what we are replacing, we'll have to
// try to determine it from the parsed manifest
// Handle of object described by METS should be in OBJID
String handleURI = manifest.getObjID();
String handle = decodeHandleURN(handleURI);
try {
// Attempt to resolve this handle to an existing object
dsoToReplace = handleService.resolveToObject(context,
handle);
} catch (IllegalStateException ie) {
// We don't care if this errors out -- we can continue
// whether or not an object exists with this handle.
}
}
// NOTE: At this point, it's still possible we don't have an object
// to replace. This could happen when there is actually no existing
// object in DSpace using that handle. (In which case, we're
// actually just doing a "restore" -- so we aren't going to throw an
// error or complain.)
// If we were unable to find the object to replace, then assume we
// are restoring it
if (dsoToReplace == null) {
// As this object doesn't already exist, we will perform an
// ingest of a new object in order to restore it
// NOTE: passing 'null' as parent object in order to force
// ingestObject() method to determine parent using manifest.
dso = ingestObject(context, null, manifest, pkgFile, params,
null);
//if ingestion was successful
if (dso != null) {
// Log that we created an object
log.info(LogHelper.getHeader(context, "package_replace",
"Created new Object, type="
+ Constants.typeText[dso.getType()]
+ ", handle=" + dso.getHandle() + ", dbID="
+ String.valueOf(dso.getID())));
}
} else {
// otherwise, we found the DSpaceObject to replace -- so, replace it!
// Actually replace the object described by the METS Manifest.
// NOTE: This will perform an in-place replace of all metadata
// and files currently associated with the object.
dso = replaceObject(context, dsoToReplace, manifest, pkgFile,
params, null);
// Log that we replaced an object
log.info(LogHelper.getHeader(context, "package_replace",
"Replaced Object, type="
+ Constants.typeText[dso.getType()]
+ ", handle=" + dso.getHandle() + ", dbID="
+ String.valueOf(dso.getID())));
}
//if ingest/restore/replace successful
if (dso != null) {
// Check if the Packager is currently running recursively.
// If so, this means the Packager will attempt to recursively
// replace all referenced child packages.
if (params.recursiveModeEnabled()) {
// Retrieve list of all Child object METS file paths from the
// current METS manifest.
// This is our list of known child packages.
String[] childFilePaths = manifest.getChildMetsFilePaths();
// Save this list to our AbstractPackageIngester (and note which
// DSpaceObject the pkgs relate to)
// NOTE: The AbstractPackageIngester itself will perform the
// recursive ingest call, based on these child pkg references.
for (int i = 0; i < childFilePaths.length; i++) {
addPackageReference(dso, childFilePaths[i]);
}
}
}
return dso;
} catch (SQLException se) {
// no need to really clean anything up,
// transaction rollback will get rid of it anyway, and will also
// restore everything to previous state.
dso = null;
// Pass this exception on to the next handler.
throw se;
}
}
// whether or not to save manifest as a bitstream in METADATA bundle.
protected boolean preserveManifest() {
return configurationService.getBooleanProperty(
"mets." + getConfigurationName() + ".ingest.preserveManifest",
false);
}
// return short name of manifest bitstream format
protected String getManifestBitstreamFormat() {
return configurationService.getProperty(
"mets." + getConfigurationName() + ".ingest.manifestBitstreamFormat");
}
// whether or not to use Collection Templates when creating a new item
protected boolean useCollectionTemplate() {
return configurationService.getBooleanProperty(
"mets." + getConfigurationName() + ".ingest.useCollectionTemplate",
false);
}
/**
* Parse the hdl: URI/URN format into a raw Handle.
*
* @param value handle URI string
* @return raw handle (with 'hdl:' prefix removed)
*/
protected String decodeHandleURN(String value) {
if (value != null && value.startsWith("hdl:")) {
return value.substring(4);
} else {
return null;
}
}
/**
* Remove an existing DSpace Object (called during a replace)
*
* @param context context
* @param dso DSpace Object
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
protected void removeObject(Context context, DSpaceObject dso)
throws AuthorizeException, SQLException, IOException {
if (log.isDebugEnabled()) {
log.debug("Removing object " + Constants.typeText[dso.getType()]
+ " id=" + dso.getID());
}
switch (dso.getType()) {
case Constants.ITEM:
Item item = (Item) dso;
itemService.delete(context, item);
break;
case Constants.COLLECTION:
Collection collection = (Collection) dso;
collectionService.delete(context, collection);
break;
case Constants.COMMUNITY:
// Just remove the Community entirely
communityService.delete(context, (Community) dso);
break;
default:
break;
}
}
/**
* Determines what parent DSpace object is referenced in this METS doc.
*
* This is a default implementation which assumes the parent will be
* specified in a <structMap LABEL="Parent">. You should override this
* method if your METS manifest specifies the parent object in another
* location.
*
* @param context DSpace Context
* @param manifest METS manifest
* @return a DSpace Object which is the parent (or null, if not found)
* @throws PackageValidationException if package validation error
* if parent reference cannot be found in manifest
* @throws MetadataValidationException if metadata validation error
* @throws SQLException if database error
*/
public DSpaceObject getParentObject(Context context, METSManifest manifest)
throws PackageValidationException, MetadataValidationException,
SQLException {
DSpaceObject parent = null;
// look for a Parent Object link in manifest
String parentLink = manifest.getParentOwnerLink();
// verify we have a valid Parent Object
if (parentLink != null && parentLink.length() > 0) {
parent = handleService.resolveToObject(context, parentLink);
if (parent == null) {
throw new UnsupportedOperationException(
"Could not find a parent DSpaceObject referenced as '"
+ parentLink
+ "' in the METS Manifest for object "
+ manifest.getObjID()
+ ". A parent DSpaceObject must be specified from either the 'packager' command or noted in " +
"the METS Manifest itself.");
}
} else {
throw new UnsupportedOperationException(
"Could not find a parent DSpaceObject where we can ingest the packaged object "
+ manifest.getObjID()
+ ". A parent DSpaceObject must be specified from either the 'packager' command or noted in the " +
"METS Manifest itself.");
}
return parent;
}
/**
* Determines the handle of the DSpace object represented in this METS doc.
*
* This is a default implementation which assumes the handle of the DSpace
* Object can be found in the <mets> @OBJID attribute. You should
* override this method if your METS manifest specifies the handle in
* another location.
*
* If no handle was found then null is returned.
*
* @param manifest METS manifest
* @return handle as a string (or null, if not found)
* @throws PackageValidationException if package validation error
* if handle cannot be found in manifest
* @throws MetadataValidationException if validation error
* @throws SQLException if database error
*/
public String getObjectHandle(METSManifest manifest)
throws PackageValidationException, MetadataValidationException,
SQLException {
// retrieve handle URI from manifest
String handleURI = manifest.getObjID();
// decode this URI (by removing the 'hdl:' prefix)
String handle = decodeHandleURN(handleURI);
return handle;
}
/**
* Retrieve the inputStream for a File referenced from a specific path
* within a METS package.
*
* If the packager is set to 'manifest-only' (i.e. pkgFile is just a
* manifest), we assume the file is available for download via a URL.
*
* Otherwise, the pkgFile is a Zip, so the file should be retrieved from
* within that Zip package.
*
* @param pkgFile the full package file (which may include content files if a
* zip)
* @param params Parameters passed to METSIngester
* @param path the File path (either path in Zip package or a URL)
* @return the InputStream for the file
* @throws MetadataValidationException if validation error
* @throws IOException if IO error
*/
protected static InputStream getFileInputStream(File pkgFile,
PackageParameters params, String path)
throws MetadataValidationException, IOException {
// If this is a manifest only package (i.e. not a zip file)
if (params.getBooleanProperty("manifestOnly", false)) {
// NOTE: since we are only dealing with a METS manifest,
// we will assume all external files are available via URLs.
try {
// attempt to open a connection to given URL
URL fileURL = new URL(path);
URLConnection connection = fileURL.openConnection();
// open stream to access file contents
return connection.getInputStream();
} catch (IOException io) {
log
.error("Unable to retrieve external file from URL '"
+ path
+ "' for manifest-only METS package. All externally referenced files must be " +
"retrievable via URLs.");
// pass exception upwards
throw io;
}
} else {
// open the Zip package
ZipFile zipPackage = new ZipFile(pkgFile);
// Retrieve the manifest file entry by name
ZipEntry manifestEntry = zipPackage.getEntry(path);
// Get inputStream associated with this file
if (manifestEntry != null) {
return zipPackage.getInputStream(manifestEntry);
} else {
throw new MetadataValidationException("Manifest file references file '"
+ path + "' not included in the zip.");
}
}
}
/**
* Returns a user help string which should describe the
* additional valid command-line options that this packager
* implementation will accept when using the -o
or
* --option
flags with the Packager script.
*
* @return a string describing additional command-line options available
* with this packager
*/
@Override
public String getParameterHelp() {
return "* ignoreHandle=[boolean] " +
"If true, the ingester will ignore any Handle specified in the METS manifest itself, and instead create a" +
" new Handle during the ingest process (this is the default when running in Submit mode, using the -s " +
"flag). " +
"If false, the ingester attempts to restore the Handles specified in the METS manifest (this is the " +
"default when running in Restore/replace mode, using the -r flag). " +
"\n\n" +
"* ignoreParent=[boolean] " +
"If true, the ingester will ignore any Parent object specified in the METS manifest itself, and instead " +
"ingest under a new Parent object (this is the default when running in Submit mode, using the -s flag). " +
"The new Parent object must be specified via the -p flag. " +
"If false, the ingester attempts to restore the object directly under its old Parent (this is the default" +
" when running in Restore/replace mode, using the -r flag). " +
"\n\n" +
"* manifestOnly=[boolean] " +
"Specify true if the ingest package consists of just a METS manifest (mets.xml), without any content " +
"files (defaults to false)." +
"\n\n" +
"* validate=[boolean] " +
"If true, enable XML validation of METS file using schemas in document (default is true).";
}
/**
* Profile-specific tests to validate manifest. The implementation can
* access the METS document through the manifest
variable, an
* instance of METSManifest
.
*
* @throws MetadataValidationException if metadata validation error
* if there is a fatal problem with the METS document's
* conformance to the expected profile.
*/
abstract void checkManifest(METSManifest manifest)
throws MetadataValidationException;
/**
* Select the dmdSec
element(s) to apply to the Item. The
* implementation is responsible for choosing which (if any) of the metadata
* sections to crosswalk to get the descriptive metadata for the item being
* ingested. It is responsible for calling the crosswalk, using the
* manifest's helper i.e.
* manifest.crosswalkItemDmd(context,item,dmdElement,callback);
* (The callback
argument is a reference to itself since the
* class also implements the METSManifest.MdRef
interface to
* fetch package files referenced by mdRef elements.)
*
* Note that item
and manifest
are available as
* protected fields from the superclass.
*
* @param context the DSpace context
* @param dso DSpace Object
* @param manifest the METSManifest
* @param callback the MdrefManager (manages all external metadata files
* referenced by METS mdref
elements)
* @param dmds array of Elements, each a METS dmdSec
that
* applies to the Item as a whole.
* @param params Packager Parameters
* @throws CrosswalkException if crosswalk error
* @throws PackageValidationException if package validation error
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
public abstract void crosswalkObjectDmd(Context context, DSpaceObject dso,
METSManifest manifest, MdrefManager callback, Element dmds[],
PackageParameters params) throws CrosswalkException,
PackageValidationException, AuthorizeException, SQLException,
IOException;
/**
* Add license(s) to Item based on contents of METS and other policies. The
* implementation of this method controls exactly what licenses are added to
* the new item, including the DSpace deposit license. It is given the
* collection (which is the source of a default deposit license), an
* optional user-supplied deposit license (in the form of a String), and the
* METS manifest. It should invoke manifest.getItemRightsMD()
* to get an array of rightsMd
elements which might contain
* other license information of interest, e.g. a Creative Commons license.
*
* This framework does not add any licenses by default.
*
* Note that crosswalking rightsMD sections can also add a deposit or CC
* license to the object.
*
* @param context the DSpace context
* @param item Item
* @param collection DSpace Collection to which the item is being submitted.
* @param license optional user-supplied Deposit License text (may be null)
* @param params Packager Parameters
* @throws PackageValidationException if package validation error
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
public abstract void addLicense(Context context, Item item, String license,
Collection collection, PackageParameters params)
throws PackageValidationException, AuthorizeException,
SQLException, IOException;
/**
* Hook for final "finishing" operations on the new Object. This method is
* called when the new Object is otherwise complete and ready to be
* returned. The implementation should use this opportunity to make whatever
* final checks and modifications are necessary.
*
* @param context the DSpace context
* @param dso the DSpace Object
* @param params the Packager Parameters
* @throws CrosswalkException if crosswalk error
* @throws PackageValidationException if package validation error
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
public abstract void finishObject(Context context, DSpaceObject dso,
PackageParameters params) throws PackageValidationException,
CrosswalkException, AuthorizeException, SQLException, IOException;
/**
* Determines what type of DSpace object is represented in this METS doc.
*
* @param manifest METS manifest
* @return one of the object types in Constants.
* @throws PackageValidationException if package validation error
*/
public abstract int getObjectType(METSManifest manifest)
throws PackageValidationException;
/**
* Subclass-dependent final processing on a Bitstream; could include fixing
* up the name, bundle, other attributes.
*
* @param context context
* @param manifest METS manifest
* @param bs bitstream
* @param mfile element
* @param params package params
* @throws MetadataValidationException if validation error
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
public abstract void finishBitstream(Context context, Bitstream bs,
Element mfile, METSManifest manifest, PackageParameters params)
throws MetadataValidationException, SQLException,
AuthorizeException, IOException;
/**
* Returns keyword that makes the configuration keys of this subclass
* unique, e.g. if it returns NAME, the key would be:
* "mets.NAME.ingest.preserveManifest = true"
*
* @return name
*/
public abstract String getConfigurationName();
public UUID getObjectID(METSManifest manifest)
throws PackageValidationException {
Element mets = manifest.getMets();
String idStr = mets.getAttributeValue("ID");
if (idStr == null || idStr.length() == 0) {
throw new PackageValidationException("Manifest is missing the required mets@ID attribute.");
}
if (idStr.contains("DB-ID-")) {
idStr = idStr.substring(idStr.lastIndexOf("DB-ID-") + 6, idStr.length());
}
try {
return UUID.fromString(idStr);
} catch (IllegalArgumentException ignored) {
//do nothing
}
return null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy