/**
* 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.IOException;
import java.sql.SQLException;
import org.jdom.Element;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.crosswalk.MetadataValidationException;
import org.dspace.core.Context;
import org.dspace.core.Constants;
/**
* Subclass of the METS packager framework to ingest a DSpace
* Archival Information Package (AIP). The AIP is intended to be, foremost,
* a _complete_ and _accurate_ representation of one object in the DSpace
* object model. An AIP contains all of the information needed to restore
* the object precisely in another DSpace archive instance.
* <p>
* This ingester recognizes two distinct types of AIPs: "Manifest-Only" and "External".
* The Manifest-Only AIP, which is selected by specifying a PackageParameters
* key "manifestOnly" with the value "true", refers to all its contents by
* reference only. For Community or Collection AIPs this means all references to their
* child objects are just via Handles. For Item AIPs all Bitreams are just
* referenced by their asset store location instead of finding them in the "package".
* The Manifest-Only AIP package format is simply a METS XML document serialized into a file.
* <p>
* An "external" AIP (the default), is a conventional Zip-file based package
* that includes copies of all bitstreams referenced by the object as well
* as a serialized METS XML document in the path "mets.xml".
*
* Configuration keys:
*
* # instructs which xwalk plugin to use for a given type of metadata
* mets.dspaceAIP.ingest.crosswalk.{mdSecName} = {pluginName}
* mets.dspaceAIP.ingest.crosswalk.DC = QDC
* mets.dspaceAIP.ingest.crosswalk.DSpaceDepositLicense = NULLSTREAM
*
* # Option to save METS manifest in the item: (default is false)
* mets.default.ingest.preserveManifest = false
*
* @author Larry Stone
* @author Tim Donohue
* @version $Revision: 1.1 $
*
* @see AbstractMETSIngester
* @see AbstractPackageIngester
* @see PackageIngester
* @see org.dspace.content.packager.METSManifest
*/
public class DSpaceAIPIngester
extends AbstractMETSIngester
{
/** log4j category */
private static Logger log = Logger.getLogger(DSpaceAIPIngester.class);
/**
* Ensure it's an AIP generated by the complementary AIP disseminator.
*/
@Override
void checkManifest(METSManifest manifest)
throws MetadataValidationException
{
String profile = manifest.getProfile();
if (profile == null)
{
throw new MetadataValidationException("Cannot accept METS with no PROFILE attribute!");
}
else if (!profile.equals(DSpaceAIPDisseminator.PROFILE_1_0))
{
throw new MetadataValidationException("METS has unacceptable PROFILE attribute, profile=" + profile);
}
}
/**
* Choose DMD section(s) to crosswalk.
* <p>
* The algorithm is:<br>
* 1. Use whatever the <code>dmd</code> parameter specifies as the primary DMD.<br>
* 2. If (1) is unspecified, find DIM (preferably) or MODS as primary DMD.<br>
* 3. If (1) or (2) succeeds, crosswalk it and ignore all other DMDs with
* same GROUPID<br>
* 4. Crosswalk remaining DMDs not eliminated already.
*
* @param context
* The relevant DSpace Context.
* @param dso
* DSpace object
* @param manifest
* the METSManifest
* @param callback
* the MdrefManager (manages all external metadata files
* referenced by METS <code>mdref</code> elements)
* @param dmds
* array of Elements, each a METS <code>dmdSec</code> that
* applies to the Item as a whole.
* @param params
* Packager Parameters
* @throws PackageValidationException validation error
* @throws CrosswalkException if crosswalk error
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
@Override
public void crosswalkObjectDmd(Context context, DSpaceObject dso,
METSManifest manifest,
MdrefManager callback,
Element dmds[], PackageParameters params)
throws CrosswalkException, PackageValidationException,
AuthorizeException, SQLException, IOException
{
int found = -1;
// Check to see what dmdSec the user specified in the 'dmd' parameter
String userDmd = null;
if (params != null)
{
userDmd = params.getProperty("dmd");
}
if (userDmd != null && userDmd.length() > 0)
{
for (int i = 0; i < dmds.length; ++i)
{
if (userDmd.equalsIgnoreCase(manifest.getMdType(dmds[i])))
{
found = i;
}
}
}
// DIM is preferred, if nothing specified by user
if (found == -1)
{
// DIM is preferred for AIP
for (int i = 0; i < dmds.length; ++i)
{
//NOTE: METS standard actually says this should be DIM (all uppercase). But,
// just in case, we're going to be a bit more forgiving.
if ("DIM".equalsIgnoreCase(manifest.getMdType(dmds[i])))
{
found = i;
}
}
}
// MODS is acceptable otehrwise..
if (found == -1)
{
for (int i = 0; i < dmds.length; ++i)
{
//NOTE: METS standard actually says this should be MODS (all uppercase). But,
// just in case, we're going to be a bit more forgiving.
if ("MODS".equalsIgnoreCase(manifest.getMdType(dmds[i])))
{
found = i;
}
}
}
String groupID = null;
if (found >= 0)
{
manifest.crosswalkItemDmd(context, params, dso, dmds[found], callback);
groupID = dmds[found].getAttributeValue("GROUPID");
if (groupID != null)
{
for (int i = 0; i < dmds.length; ++i)
{
String g = dmds[i].getAttributeValue("GROUPID");
if (g != null && !g.equals(groupID))
{
manifest.crosswalkItemDmd(context, params, dso, dmds[i], callback);
}
}
}
}
// otherwise take the first. Don't xwalk more than one because
// each xwalk _adds_ metadata, and could add duplicate fields.
else if (dmds.length > 0)
{
manifest.crosswalkItemDmd(context, params, dso, dmds[0], callback);
}
// it's an error if there is nothing to crosswalk:
else
{
throw new MetadataValidationException("DSpaceAIPIngester: Could not find an acceptable object-wide DMD section in manifest.");
}
}
/**
* Ignore license when restoring an manifest-only AIP, since it should
* be a bitstream in the AIP already.
* Otherwise: Check item for license first; then, take deposit
* license supplied by explicit argument next, else use collection's
* default deposit license.
* Normally the rightsMD crosswalks should provide a license.
*
* @param context
* The relevant DSpace Context.
* @param item
* item to add license to
* @param collection
* collection to get the default license from
* @param params
* Packager Parameters
* @throws PackageValidationException validation error
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
@Override
public void addLicense(Context context, Item item, String license,
Collection collection, PackageParameters params)
throws PackageValidationException,
AuthorizeException, SQLException, IOException
{
boolean newLicense = false;
if (!params.restoreModeEnabled())
{
//AIP is not being restored/replaced, so treat it like a SIP -- every new SIP needs a new license
newLicense = true;
}
// Add deposit license if there isn't one in the object,
// and it's not a restoration of an "manifestOnly" AIP:
if (!params.getBooleanProperty("manifestOnly", false) &&
PackageUtils.findDepositLicense(context, item) == null)
{
newLicense = true;
}
if (newLicense)
{
PackageUtils.addDepositLicense(context, license, item, collection);
}
}
/**
* Last change to fix up a DSpace Object.
* <p>
* For AIPs, if the object is an Item, we may want to make sure all of its
* metadata fields already exist in the database (otherwise, the database
* will throw errors when we attempt to save/update the Item)
* </p>
*
* @param context
* The relevant DSpace Context.
* @param dso
* DSpace object
* @param params
* Packager Parameters
* @throws PackageValidationException
* Failure when importing or exporting a package
* caused by invalid unacceptable package format or contents
* @throws CrosswalkException
* Superclass for more-specific crosswalk exceptions.
* @throws IOException
* A general class of exceptions produced by failed or interrupted I/O operations.
* @throws SQLException
* An exception that provides information on a database access error or other errors.
* @throws AuthorizeException
* Exception indicating the current user of the context does not have permission
* to perform a particular action.
*/
@Override
public void finishObject(Context context, DSpaceObject dso, PackageParameters params)
throws PackageValidationException, CrosswalkException,
AuthorizeException, SQLException, IOException
{
//Metadata fields are now required before adding, so this logic isn't needed anymore
/*if (dso.getType()==Constants.ITEM)
{
// Check if 'createMetadataFields' option is enabled (default=true)
// This defaults to true as by default we should attempt to restore as much metadata as we can.
// When 'createMetadataFields' is set to false, an ingest will fail if it attempts to ingest content to a missing metadata field.
if (params.getBooleanProperty("createMetadataFields", true))
{
// We want to verify that all the Metadata Fields we've crosswalked
// actually *exist* in the DB. If not, we'll try to create them
createMissingMetadataFields(context, (Item) dso);
}
}
*/
}
/**
* Nothing extra to do to bitstream after ingestion.
* @throws MetadataValidationException if validation error
* @throws IOException if IO error
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
@Override
public void finishBitstream(Context context,
Bitstream bs,
Element mfile,
METSManifest manifest,
PackageParameters params)
throws MetadataValidationException, SQLException, AuthorizeException, IOException
{
// nothing to do.
}
/**
* Return the type of DSpaceObject in this package; it is
* in the TYPE attribute of the mets:mets element.
* @return type
* @throws PackageValidationException if package validation error
*/
@Override
public int getObjectType(METSManifest manifest)
throws PackageValidationException
{
Element mets = manifest.getMets();
String typeStr = mets.getAttributeValue("TYPE");
if (typeStr == null || typeStr.length() == 0)
{
throw new PackageValidationException("Manifest is missing the required mets@TYPE attribute.");
}
if (typeStr.startsWith("DSpace "))
{
typeStr = typeStr.substring(7);
}
int type = Constants.getTypeID(typeStr);
if (type < 0)
{
throw new PackageValidationException("Manifest has unrecognized value in mets@TYPE attribute: " + typeStr);
}
return type;
}
/**
* Name used to distinguish DSpace Configuration entries for this subclass.
* @return config name
*/
@Override
public String getConfigurationName()
{
return "dspaceAIP";
}
/**
* 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()
{
String parentHelp = super.getParameterHelp();
//Return superclass help info, plus the extra parameters/options that this class supports
return parentHelp +
"\n\n" +
"* createMetadataFields=[boolean] " +
"If true, ingest attempts to create any missing metadata fields." +
"If false, ingest will fail if a metadata field is encountered which doesn't already exist. (default = true)" +
"\n\n" +
"* dmd=[dmdSecType] " +
"Type of the METS <dmdSec> which should be used to restore item metadata (defaults to DIM, then MODS)";
}
}