Merge pull request #312 from tuub/DS-1535

DS-1535: DOI support for dspace-api
This commit is contained in:
Mark H. Wood
2013-10-16 07:04:59 -07:00
25 changed files with 5126 additions and 1 deletions

View File

@@ -433,6 +433,11 @@
<artifactId>rome-modules</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>gr.ekt</groupId>
<artifactId>biblio-transformation-engine</artifactId>

View File

@@ -84,6 +84,48 @@ public class HandleManager
return url;
}
/**
* Try to detect a handle in a URL.
* @param context DSpace context
* @param url The URL
* @return The handle or null if the handle couldn't be extracted of a URL
* or if the extracted handle couldn't be found.
* @throws SQLException If a database error occurs
*/
public static String resolveUrlToHandle(Context context, String url)
throws SQLException
{
String dspaceUrl = ConfigurationManager.getProperty("dspace.url")
+ "/handle/";
String handleResolver = ConfigurationManager.getProperty("handle.canonical.prefix");
String handle = null;
if (url.startsWith(dspaceUrl))
{
handle = url.substring(dspaceUrl.length());
}
if (url.startsWith(handleResolver))
{
handle = url.substring(handleResolver.length());
}
if (null == handle)
{
return null;
}
// remove trailing slashes
while (handle.startsWith("/"))
{
handle = handle.substring(1);
}
TableRow dbhandle = findHandleInternal(context, handle);
return (null == dbhandle) ? null : handle;
}
/**
* Transforms handle into the canonical form <em>hdl:handle</em>.

View File

@@ -0,0 +1,103 @@
/**
* 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.identifier;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.dspace.identifier.doi.DOIIdentifierException;
/**
* DOI identifiers.
*
* @author Pascal-Nicolas Becker
*/
public class DOI
implements Identifier
{
public static final String SCHEME = "doi:";
public static final String RESOLVER = "http://dx.doi.org";
/**
* This method helps to convert a DOI into a URL. It takes DOIs in one of
* the following formats and returns it as URL (f.e.
* http://dx.doi.org/10.123/456). Allowed formats are:
* <ul>
* <li>doi:10.123/456</li>
* <li>10.123/456</li>
* <li>http://dx.doi.org/10.123/456</li>
* </ul>
*
* @param identifier A DOI that should be returned in external form.
* @return A String containing a URL to the official DOI resolver.
* @throws IllegalArgumentException If identifier is null or an empty String.
* @throws IdentifierException If identifier could not be recognized as valid DOI.
*/
public static String DOIToExternalForm(String identifier)
throws IdentifierException
{
if (null == identifier)
throw new IllegalArgumentException("Identifier is null.", new NullPointerException());
if (identifier.isEmpty())
throw new IllegalArgumentException("Cannot format an empty identifier.");
if (identifier.startsWith(SCHEME))
return RESOLVER + "/" + identifier.substring(SCHEME.length());
if (identifier.startsWith("10.") && identifier.contains("/"))
return RESOLVER + "/" + identifier;
if (identifier.startsWith(RESOLVER + "/10."))
return identifier;
throw new IdentifierException(identifier + "does not seem to be a DOI.");
}
public static String DOIFromExternalFormat(String identifier)
throws DOIIdentifierException
{
Pattern pattern = Pattern.compile("^" + RESOLVER + "/+(10\\..*)$");
Matcher matcher = pattern.matcher(identifier);
if (matcher.find())
{
return SCHEME + matcher.group(1);
}
throw new DOIIdentifierException("Cannot recognize DOI!",
DOIIdentifierException.UNRECOGNIZED);
}
/**
* Recognize format of DOI and return it with leading doi-Scheme.
* @param identifier Identifier to format, following format are accepted:
* f.e. 10.123/456, doi:10.123/456, http://dx.doi.org/10.123/456.
* @return Given Identifier with DOI-Scheme, f.e. doi:10.123/456.
* @throws IllegalArgumentException If identifier is empty or null.
* @throws DOIIdentifierException If DOI could not be recognized.
*/
public static String formatIdentifier(String identifier)
throws DOIIdentifierException
{
if (null == identifier) {
throw new IllegalArgumentException("Identifier is null.", new NullPointerException());
}
if (identifier.startsWith(DOI.SCHEME)) {
return identifier;
}
if (identifier.isEmpty()) {
throw new IllegalArgumentException("Cannot format an empty identifier.");
}
if (identifier.startsWith("10.") && identifier.contains("/")) {
return DOI.SCHEME + identifier;
}
if (identifier.startsWith(RESOLVER + "/10.")) {
return DOI.SCHEME + identifier.substring(18);
}
throw new DOIIdentifierException(identifier + "does not seem to be a DOI.",
DOIIdentifierException.UNRECOGNIZED);
}
}

View File

@@ -0,0 +1,997 @@
/**
* 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.identifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCValue;
import org.dspace.content.DSpaceObject;
import org.dspace.content.FormatIdentifier;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
/**
* Provide service for DOIs using DataCite.
*
* <p>This class handles reservation, registration and deletion of DOIs using
* the direct API from {@link <a href="http://www.datacite.org">DataCite</a>}.
* Please pay attention that some members of DataCite offer special services
* and want their customers to use special APIs. If you are unsure ask your
* registration agency.</p>
*
* <p>Any identifier a method of this class returns is a string in the following format: doi:10.123/456.</p>
*
* @author Pascal-Nicolas Becker
*/
public class DOIIdentifierProvider
extends IdentifierProvider
{
private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class);
/**
* A DOIConnector connects the DOIIdentifierProvider to the API of the DOI
* registration agency needed to register DOIs. To register DOIs we have to
* care about two APIs: the <link>IdentifierProvider</link> API of DSpace
* and the API of the DOI registration agency. The DOIIdentifierProvider
* manages the DOI database table, generates new DOIs, stores them as
* metadata in DSpace items and so on. To register DOIs at DOI registration
* agencies it uses a DOIConnector. A DOI connector has to register and
* reserve DOIs using the API of the DOI registration agency. If requested
* by the registration agency it has to convert and send metadata of the
* DSpace items.
*/
private DOIConnector connector;
static final String CFG_PREFIX = "identifier.doi.prefix";
static final String CFG_NAMESPACE_SEPARATOR = "identifier.doi.namespaceseparator";
// Metadata field name elements
// TODO: move these to MetadataSchema or some such?
public static final String MD_SCHEMA = "dc";
public static final String DOI_ELEMENT = "identifier";
public static final String DOI_QUALIFIER = "uri";
public static final Integer TO_BE_REGISTERED = 1;
public static final Integer TO_BE_RESERVERED = 2;
public static final Integer IS_REGISTERED = 3;
public static final Integer IS_RESERVED = 4;
public static final Integer UPDATE_RESERVERED = 5;
public static final Integer UPDATE_REGISTERED = 6;
public static final Integer UPDATE_BEFORE_REGISTERATION = 7;
public static final Integer TO_BE_DELETED = 8;
public static final Integer DELETED = 9;
/**
* Prefix of DOI namespace. Set in dspace.cfg.
*/
private String PREFIX;
/**
* Part of DOI to separate several applications that generate DOIs.
* E.g. it could be 'dspace/' if DOIs generated by DSpace should have the form
* prefix/dspace/uniqueString. Set it to the empty String if DSpace must
* generate DOIs directly after the DOI Prefix. Set in dspace.cfg.
*/
private String NAMESPACE_SEPARATOR;
protected String getPrefix()
{
if (null == this.PREFIX)
{
this.PREFIX = this.configurationService.getProperty(CFG_PREFIX);
if (null == this.PREFIX)
{
log.warn("Cannot find DOI prefix in configuration!");
throw new RuntimeException("Unable to load DOI prefix from "
+ "configuration. Cannot find property " +
CFG_PREFIX + ".");
}
}
return this.PREFIX;
}
protected String getNamespaceSeparator()
{
if (null == this.NAMESPACE_SEPARATOR)
{
this.NAMESPACE_SEPARATOR = this.configurationService.getProperty(CFG_NAMESPACE_SEPARATOR);
if (null == this.NAMESPACE_SEPARATOR)
{
this.NAMESPACE_SEPARATOR = "";
}
}
return this.NAMESPACE_SEPARATOR;
}
@Required
public void setDOIConnector(DOIConnector connector)
{
this.connector = connector;
}
/**
* This identifier provider supports identifiers of type
* {@link org.dspace.identifier.DOI}.
* @param identifier to check if it will be supported by this provider.
* @return
*/
@Override
public boolean supports(Class<? extends Identifier> identifier)
{
return DOI.class.isAssignableFrom(identifier);
}
/**
* This identifier provider supports identifiers in the following format:
* <ul>
* <li>doi:10.123/456</li>
* <li>10.123/456</li>
* <li>http://dx.doi.org/10.123/456</li>
* </ul>
* @param identifier to check if it is in a supported format.
* @return
*/
@Override
public boolean supports(String identifier)
{
try {
DOI.formatIdentifier(identifier);
} catch (IdentifierException e) {
return false;
} catch (IllegalArgumentException e)
{
return false;
}
return true;
}
@Override
public String register(Context context, DSpaceObject dso)
throws IdentifierException
{
String doi = mint(context, dso);
// register tries to reserve doi if it's not already.
// So we don't have to reserve it here.
this.register(context, dso, doi);
return doi;
}
@Override
public void register(Context context, DSpaceObject dso, String identifier)
throws IdentifierException
{
String doi = DOI.formatIdentifier(identifier);
TableRow doiRow = null;
// search DOI in our db
try
{
doiRow = loadOrCreateDOI(context, dso, doi);
} catch (SQLException ex) {
log.error("Error in databse connection: " + ex.getMessage());
throw new RuntimeException("Error in database conncetion.", ex);
}
if (DELETED == doiRow.getIntColumn("status") ||
TO_BE_DELETED == doiRow.getIntColumn("status"))
{
throw new DOIIdentifierException("You tried to register a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
// Check status of DOI
if (IS_REGISTERED == doiRow.getIntColumn("status"))
{
return;
}
// change status of DOI
doiRow.setColumn("status", TO_BE_REGISTERED);
try {
DatabaseManager.update(context, doiRow);
context.commit();
}
catch (SQLException sqle)
{
log.warn("SQLException while changing status of DOI {} to be registered.", doi);
throw new RuntimeException(sqle);
}
}
/**
* @param context
* @param dso DSpaceObject the DOI should be reserved for. Some metadata of
* this object will be send to the registration agency.
* @param identifier DOI to register in a format that
* {@link FormatIdentifier(String)} accepts.
* @throws IdentifierException If the format of {@code identifier} was
* unrecognized or if it was impossible to
* reserve the DOI (registration agency denied
* for some reason, see logs).
* @throws IllegalArgumentException If {@code identifier} is a DOI already
* registered for another DSpaceObject then
* {@code dso}.
* @see IdentifierProvider.reserve(Context, DSpaceObject, String)
*/
@Override
public void reserve(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException
{
String doi = DOI.formatIdentifier(identifier);
TableRow doiRow = null;
try {
// if the doi is in our db already loadOrCreateDOI just returns.
// if it is not loadOrCreateDOI safes the doi.
doiRow = loadOrCreateDOI(context, dso, doi);
}
catch (SQLException sqle)
{
throw new RuntimeException(sqle);
}
if (!doiRow.isColumnNull("status")) {
return;
}
doiRow.setColumn("status", TO_BE_RESERVERED);
try
{
DatabaseManager.update(context, doiRow);
}
catch (SQLException sqle)
{
throw new RuntimeException(sqle);
}
}
public void reserveOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException
{
String doi = DOI.formatIdentifier(identifier);
// get TableRow and ensure DOI belongs to dso regarding our db
TableRow doiRow = loadOrCreateDOI(context, dso, doi);
if (DELETED == doiRow.getIntColumn("status") ||
TO_BE_DELETED == doiRow.getIntColumn("status"))
{
throw new DOIIdentifierException("You tried to reserve a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
// check if DOI is reserved at the registration agency
if (connector.isDOIReserved(context, doi))
{
// if doi is registered for this object we still should check its
// status in our database (see below).
// if it is registered for another object we should notify an admin
if (!connector.isDOIReserved(context, dso, doi))
{
log.warn("DOI {} is reserved for another object already.", doi);
throw new DOIIdentifierException(DOIIdentifierException.DOI_ALREADY_EXISTS);
}
}
else
{
connector.reserveDOI(context, dso, doi);
}
doiRow.setColumn("status", IS_RESERVED);
DatabaseManager.update(context, doiRow);
}
public void registerOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException
{
String doi = DOI.formatIdentifier(identifier);
// get TableRow and ensure DOI belongs to dso regarding our db
TableRow doiRow = loadOrCreateDOI(context, dso, doi);
if (DELETED == doiRow.getIntColumn("status") ||
TO_BE_DELETED == doiRow.getIntColumn("status"))
{
throw new DOIIdentifierException("You tried to register a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
// check if the DOI is already registered online
if (connector.isDOIRegistered(context, doi))
{
// if doi is registered for this object we still should check its
// status in our database (see below).
// if it is registered for another object we should notify an admin
if (!connector.isDOIRegistered(context, dso, doi))
{
// DOI is reserved for another object
log.warn("DOI {} is registered for another object already.", doi);
throw new DOIIdentifierException(DOIIdentifierException.DOI_ALREADY_EXISTS);
}
}
else
{
// check if doi is reserved for this specific dso
if (!connector.isDOIReserved(context, dso, doi))
{
// check if doi is already reserved for another dso
if (connector.isDOIReserved(context, doi))
{
log.warn("Trying to register DOI {}, that is reserved for "
+ "another dso.", doi);
throw new DOIIdentifierException("Trying to register a DOI "
+ "that is reserved for another object.",
DOIIdentifierException.DOI_ALREADY_EXISTS);
}
connector.reserveDOI(context, dso, doi);
}
// register DOI Online
try {
connector.registerDOI(context, dso, doi);
}
catch (DOIIdentifierException die)
{
// do we have to reserve DOI before we can register it?
if (die.getCode() == DOIIdentifierException.REGISTER_FIRST)
{
this.reserveOnline(context, dso, identifier);
connector.registerDOI(context, dso, doi);
}
else
{
throw die;
}
}
}
// safe DOI as metadata of the item
try {
saveDOIToObject(context, dso, doi);
}
catch (AuthorizeException ae)
{
throw new IdentifierException("Not authorized to save a DOI as metadata of an dso!", ae);
}
catch (SQLException sqle)
{
throw new RuntimeException(sqle);
}
doiRow.setColumn("status", IS_REGISTERED);
DatabaseManager.update(context, doiRow);
}
public void updateMetadata(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException
{
String doi = DOI.formatIdentifier(identifier);
TableRow doiRow = null;
doiRow = loadOrCreateDOI(context, dso, doi);
if (DELETED == doiRow.getIntColumn("status") ||
TO_BE_DELETED == doiRow.getIntColumn("status"))
{
throw new DOIIdentifierException("You tried to register a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
if (IS_REGISTERED == doiRow.getIntColumn("status"))
{
doiRow.setColumn("status", UPDATE_REGISTERED);
}
else if (TO_BE_REGISTERED == doiRow.getIntColumn("status"))
{
doiRow.setColumn("status", UPDATE_BEFORE_REGISTERATION);
}
else if (IS_RESERVED == doiRow.getIntColumn("status"))
{
doiRow.setColumn("status", UPDATE_RESERVERED);
}
else
{
return;
}
DatabaseManager.update(context, doiRow);
}
public void updateMetadataOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, SQLException
{
String doi = DOI.formatIdentifier(identifier);
// ensure DOI belongs to dso regarding our db
TableRow doiRow = null;
try
{
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi", doi.substring(DOI.SCHEME.length()));
}
catch (SQLException sqle)
{
log.warn("SQLException while searching a DOI in our db.", sqle);
throw new RuntimeException("Unable to retrieve information about "+
"a DOI out of database.", sqle);
}
if (null == doiRow)
{
log.error("Cannot update metadata for DOI {}: unable to find it in "
+ "our db.", doi);
throw new DOIIdentifierException("Unable to find DOI.",
DOIIdentifierException.DOI_DOES_NOT_EXIST);
}
if (doiRow.getIntColumn("resource_id") != dso.getID() ||
doiRow.getIntColumn("resource_type_id") != dso.getType())
{
log.error("Refuse to update metadata of DOI {} with the metadata of "
+ " an object ({}/{}) the DOI is not dedicated to.",
new String[] {doi, dso.getTypeText(), Integer.toString(dso.getID())});
throw new DOIIdentifierException("Cannot update DOI metadata: "
+ "DOI and DSpaceObject does not match!",
DOIIdentifierException.MISMATCH);
}
if (DELETED == doiRow.getIntColumn("status") ||
TO_BE_DELETED == doiRow.getIntColumn("status"))
{
throw new DOIIdentifierException("You tried to register a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
// check if doi is reserved for this specific dso
if (connector.isDOIReserved(context, identifier))
{
// check if doi is reserved for this specific dso
if (!connector.isDOIReserved(context, dso, doi))
{
log.warn("Trying to update metadata for DOI {}, that is reserved"
+ " for another dso.", doi);
throw new DOIIdentifierException("Trying to update metadta for "
+ "a DOI that is reserved for another object.",
DOIIdentifierException.DOI_ALREADY_EXISTS);
}
}
connector.updateMetadata(context, dso, doi);
if (UPDATE_REGISTERED == doiRow.getIntColumn("status"))
{
doiRow.setColumn("status", IS_REGISTERED);
}
else if (UPDATE_BEFORE_REGISTERATION == doiRow.getIntColumn("status"))
{
doiRow.setColumn("status", TO_BE_REGISTERED);
}
else if (UPDATE_RESERVERED == doiRow.getIntColumn("status"))
{
doiRow.setColumn("status", IS_RESERVED);
}
DatabaseManager.update(context, doiRow);
}
@Override
public String mint(Context context, DSpaceObject dso)
throws IdentifierException
{
String doi = null;
try
{
doi = getDOIByObject(context, dso);
}
catch (SQLException e)
{
log.error("Error while attemping to retrieve information about a DOI for "
+ dso.getTypeText() + " with ID " + dso.getID() + ".");
throw new RuntimeException("Error while attempting to retrieve " +
"information about a DOI for " + dso.getTypeText() +
" with ID " + dso.getID() + ".", e);
}
if (null == doi)
{
try
{
TableRow doiRow = loadOrCreateDOI(context, dso, null);
doi = DOI.SCHEME + doiRow.getStringColumn("doi");
}
catch (SQLException e)
{
log.error("Error while creating new DOI for Object of " +
"ResourceType {} with id {}.", dso.getType(), dso.getID());
throw new RuntimeException("Error while attempting to create a " +
"new DOI for " + dso.getTypeText() + " with ID " +
dso.getID() + ".", e);
}
}
return doi;
}
@Override
public DSpaceObject resolve(Context context, String identifier, String... attributes)
throws IdentifierNotFoundException, IdentifierNotResolvableException
{
String doi = null;
try {
doi = DOI.formatIdentifier(identifier);
} catch (IdentifierException e) {
throw new IdentifierNotResolvableException(e);
}
try
{
DSpaceObject dso = getObjectByDOI(context, doi);
if (null == dso)
{
throw new IdentifierNotFoundException();
}
return dso;
}
catch (SQLException sqle)
{
log.error("SQLException while searching a DOI in our db.", sqle);
throw new RuntimeException("Unable to retrieve information about "+
"a DOI out of database.", sqle);
}
catch (IdentifierException e)
{
throw new IdentifierNotResolvableException(e);
}
}
@Override
public String lookup(Context context, DSpaceObject dso)
throws IdentifierNotFoundException, IdentifierNotResolvableException
{
String doi = null;
try
{
doi = getDOIByObject(context, dso);
}
catch (SQLException e)
{
throw new RuntimeException("Error retrieving DOI out of database.", e);
}
if (null == doi)
{
throw new IdentifierNotFoundException("No DOI for DSpaceObject of type "
+ dso.getTypeText() + " with ID " + dso.getID() + " found.");
}
return doi;
}
@Override
public void delete(Context context, DSpaceObject dso)
throws IdentifierException
{
// delete all DOIs for this Item from our database.
try
{
String doi = getDOIByObject(context, dso);
while (null != doi)
{
this.delete(context, dso, doi);
doi = getDOIByObject(context, dso);
}
}
catch (SQLException ex)
{
log.error("Error while attemping to retrieve information about a DOI for "
+ dso.getTypeText() + " with ID " + dso.getID() + ".", ex);
throw new RuntimeException("Error while attempting to retrieve " +
"information about a DOI for " + dso.getTypeText() +
" with ID " + dso.getID() + ".", ex);
}
// delete all DOIs of this item out of its metadata
try {
String doi = getDOIOutOfObject(dso);
while (null != doi)
{
this.removeDOIFromObject(context, dso, doi);
doi = getDOIOutOfObject(dso);
}
}
catch (AuthorizeException ex)
{
log.error("Error while removing a DOI out of the metadata of an "
+ dso.getTypeText() + " with ID " + dso.getID() + ".", ex);
throw new RuntimeException("Error while removing a DOI out of the "
+ "metadata of an " + dso.getTypeText() + " with ID "
+ dso.getID() + ".", ex);
}
catch (SQLException ex)
{
log.error("Error while removing a DOI out of the metadata of an "
+ dso.getTypeText() + " with ID " + dso.getID() + ".", ex);
throw new RuntimeException("Error while removing a DOI out of the "
+ "metadata of an " + dso.getTypeText() + " with ID "
+ dso.getID() + ".", ex);
}
}
@Override
public void delete(Context context, DSpaceObject dso, String identifier)
throws IdentifierException
{
String doi = DOI.formatIdentifier(identifier);
TableRow doiRow = null;
try
{
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
}
catch (SQLException sqle)
{
throw new RuntimeException(sqle);
}
// check if DOI belongs to dso
if (null != doiRow)
{
if (doiRow.getIntColumn("resource_id") != dso.getID() ||
doiRow.getIntColumn("resource_type_id") != dso.getType())
{
throw new DOIIdentifierException("Trying to delete a DOI out of "
+ "an object that is not addressed by the DOI.",
DOIIdentifierException.MISMATCH);
}
}
// remove DOI from metadata
try
{
removeDOIFromObject(context, dso, doi);
}
catch (AuthorizeException ex)
{
log.error("Not authorized to delete a DOI out of an Item.", ex);
throw new DOIIdentifierException("Not authorized to delete DOI.",
ex, DOIIdentifierException.UNAUTHORIZED_METADATA_MANIPULATION);
}
catch (SQLException ex)
{
log.error("SQLException occured while deleting a DOI out of an item: "
+ ex.getMessage());
throw new RuntimeException("Error while deleting a DOI out of the " +
"metadata of an Item " + dso.getID(), ex);
}
// change doi status in db if necessary.
if (null != doiRow)
{
if(doiRow.isColumnNull("status"))
{
doiRow.setColumn("status", DELETED);
}
else
{
doiRow.setColumn("status", TO_BE_DELETED);
}
try {
DatabaseManager.update(context, doiRow);
context.commit();
}
catch (SQLException sqle)
{
log.warn("SQLException while changing status of DOI {} to be deleted.", doi);
throw new RuntimeException(sqle);
}
}
// DOI is a permanent identifier. DataCite for example does not delete
// DOIS. But it is possible to mark a DOI as "inactive".
}
public void deleteOnline(Context context, String identifier)
throws DOIIdentifierException
{
String doi = DOI.formatIdentifier(identifier);
TableRow doiRow = null;
try
{
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
}
catch (SQLException sqle)
{
throw new RuntimeException(sqle);
}
if(null == doiRow)
{
throw new DOIIdentifierException("This identifier: " + identifier
+ " isn't in our database",
DOIIdentifierException.DOI_DOES_NOT_EXIST);
}
if (TO_BE_DELETED != doiRow.getIntColumn("status"))
{
log.error("This identifier: {} couldn't be deleted. "
+ "Delete it first from metadata.",
DOI.SCHEME + doiRow.getStringColumn("doi"));
throw new IllegalArgumentException("Couldn't delete this identifier:"
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ ". Delete it first from metadata.");
}
connector.deleteDOI(context, doi);
doiRow.setColumn("status", DELETED);
try {
DatabaseManager.update(context, doiRow);
context.commit();
}
catch (SQLException sqle)
{
log.warn("SQLException while changing status of DOI {} deleted.", doi);
throw new RuntimeException(sqle);
}
}
/**
* Returns a DSpaceObject depending on its DOI.
* @param context the context
* @param identifier The DOI in a format that is accepted by
* {@link formatIdentifier(String)}.
* @return Null if the DOI couldn't be found or the associated DSpaceObject.
* @throws SQLException
* @throws IdentifierException If {@code identifier} is null or an empty string.
* @throws IllegalArgumentException If the identifier couldn't be recognized as DOI.
*/
public static DSpaceObject getObjectByDOI(Context context, String identifier)
throws SQLException, DOIIdentifierException, IllegalArgumentException
{
String doi = DOI.formatIdentifier(identifier);
TableRow doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
if (null == doiRow)
{
return null;
}
if (doiRow.isColumnNull("resource_type_id") ||
doiRow.isColumnNull("resource_id"))
{
log.error("Found DOI " + doi +
" in database, but no assigned Object could be found.");
throw new IllegalStateException("Found DOI " + doi +
" in database, but no assigned Object could be found.");
}
return DSpaceObject.find(context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
}
/**
* Search the database for a DOI, using the type and id of an DSpaceObject.
*
* @param context
* @param dso DSpaceObject to find doi for. DOIs with status TO_BE_DELETED will be
* ignored.
* @return The DOI as String or null if DOI was not found.
* @throws SQLException
*/
public static String getDOIByObject(Context context, DSpaceObject dso)
throws SQLException
{
String sql = "SELECT * FROM Doi WHERE resource_type_id = ? " +
"AND resource_id = ? AND ((status != ? AND status != ?) OR status IS NULL)";
TableRow doiRow = DatabaseManager.querySingleTable(context, "Doi", sql,
dso.getType(), dso.getID(), DOIIdentifierProvider.TO_BE_DELETED,
DOIIdentifierProvider.DELETED);
if (null == doiRow)
{
return null;
}
if (doiRow.isColumnNull("doi"))
{
log.error("A DOI with an empty doi column was found in the database. DSO-Type: "
+ dso.getTypeText() + ", ID: " + dso.getID() + ".");
throw new IllegalStateException("A DOI with an empty doi column " +
"was found in the database. DSO-Type: " + dso.getTypeText() +
", ID: " + dso.getID() + ".");
}
return DOI.SCHEME + doiRow.getStringColumn("doi");
}
/**
* Load a DOI from the database or creates it if it does not exist. This
* method can be used to ensure that a DOI exists in the database and to
* load the appropriate TableRow. As protected method we don't check if the
* DOI is in a decent format, use DOI.formatIdentifier(String) if necessary.
*
* @param context
* @param dso The DSpaceObject the DOI should be loaded or created for.
* @param doi A DOI or null if a DOI should be generated. The generated DOI
* can be found in the appropriate column for the TableRow.
* @return The database row of the object.
* @throws SQLException In case of an error using the database.
* @throws DOIIdentifierException If {@code doi} is not part of our prefix or
* DOI is registered for another object already.
*/
protected TableRow loadOrCreateDOI(Context context, DSpaceObject dso, String doi)
throws SQLException, DOIIdentifierException
{
TableRow doiRow = null;
if (null != doi)
{
// we expect DOIs to have the DOI-Scheme except inside the doi table:
doi = doi.substring(DOI.SCHEME.length());
// check if DOI is already in Database
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi", doi);
if (null != doiRow)
{
// check if DOI already belongs to dso
if (doiRow.getIntColumn("resource_id") == dso.getID() &&
doiRow.getIntColumn("resource_type_id") == dso.getType())
{
return doiRow;
}
else
{
throw new DOIIdentifierException("Trying to create a DOI " +
"that is already reserved for another object.",
DOIIdentifierException.DOI_ALREADY_EXISTS);
}
}
// check prefix
if (!doi.startsWith(this.getPrefix() + "/"))
{
throw new DOIIdentifierException("Trying to create a DOI " +
"that's not part of our Namespace!",
DOIIdentifierException.FOREIGN_DOI);
}
// prepare new doiRow
doiRow = DatabaseManager.create(context, "Doi");
}
else
{
// We need to generate a new DOI.
doiRow = DatabaseManager.create(context, "Doi");
doi = this.getPrefix() + "/" + this.getNamespaceSeparator() +
doiRow.getIntColumn("doi_id");
}
doiRow.setColumn("doi", doi);
doiRow.setColumn("resource_type_id", dso.getType());
doiRow.setColumn("resource_id", dso.getID());
doiRow.setColumnNull("status");
if (0 == DatabaseManager.update(context, doiRow))
{
throw new RuntimeException("Cannot save DOI to databse for unkown reason.");
}
return doiRow;
}
/**
* Loads a DOI out of the metadata of an DSpaceObject.
* @param dso
* @return The DOI or null if no DOI was found.
*/
public static String getDOIOutOfObject(DSpaceObject dso)
throws DOIIdentifierException {
// FIXME
if (!(dso instanceof Item))
{
throw new IllegalArgumentException("We currently support DOIs for "
+ "Items only, not for " + dso.getTypeText() + ".");
}
Item item = (Item)dso;
DCValue[] metadata = item.getMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
for (DCValue id : metadata)
{
if (id.value.startsWith(DOI.RESOLVER + "/10."))
{
return DOI.DOIFromExternalFormat(id.value);
}
}
return null;
}
/**
* Adds a DOI to the metadata of an item.
*
* @param context
* @param dso DSpaceObject the DOI should be added to.
* @param doi The DOI that should be added as metadata.
* @throws SQLException
* @throws AuthorizeException
*/
protected void saveDOIToObject(Context context, DSpaceObject dso, String doi)
throws SQLException, AuthorizeException, IdentifierException
{
// FIXME
if (!(dso instanceof Item))
{
throw new IllegalArgumentException("We currently support DOIs for "
+ "Items only, not for " + dso.getTypeText() + ".");
}
Item item = (Item) dso;
item.addMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, DOI.DOIToExternalForm(doi));
try
{
item.update();
context.commit();
} catch (SQLException ex) {
throw ex;
} catch (AuthorizeException ex) {
throw ex;
}
}
/**
* Removes a DOI out of the metadata of a DSpaceObject.
*
* @param context
* @param dso The DSpaceObject the DOI should be removed from.
* @param doi The DOI to remove out of the metadata.
* @throws AuthorizeException
* @throws SQLException
*/
protected void removeDOIFromObject(Context context, DSpaceObject dso, String doi)
throws AuthorizeException, SQLException, IdentifierException
{
// FIXME
if (!(dso instanceof Item))
{
throw new IllegalArgumentException("We currently support DOIs for "
+ "Items only, not for " + dso.getTypeText() + ".");
}
Item item = (Item)dso;
DCValue[] metadata = item.getMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
List<String> remainder = new ArrayList<String>();
for (DCValue id : metadata)
{
if (!id.value.equals(DOI.DOIToExternalForm(doi)))
{
remainder.add(id.value);
}
}
item.clearMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
item.addMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null,
remainder.toArray(new String[remainder.size()]));
try {
item.update();
context.commit();
} catch (SQLException e) {
throw e;
} catch (AuthorizeException e) {
throw e;
}
}
}

View File

@@ -36,23 +36,100 @@ public abstract class IdentifierProvider {
this.parentService = parentService;
}
/**
* Can this provider provide identifiers of a given type?
*
* @param identifier requested type.
* @return true if the given type is assignable from this provider's type.
*/
public abstract boolean supports(Class<? extends Identifier> identifier);
/**
* Can this provider provide identifiers of a given type?
*
* @param identifier requested type.
* @return true if this provider can provide the named type of identifier.
*/
public abstract boolean supports(String identifier);
/**
* Create and apply an identifier to a DSpaceObject.
*
* @param context
* @param item object to be named.
* @return existing identifier of {@code item} if it has one, else a new identifier.
* @throws IdentifierException
*/
public abstract String register(Context context, DSpaceObject item) throws IdentifierException;
/**
* Create an identifier for a DSpaceObject.
*
* @param context
* @param dso object to be named.
* @return existing identifier of {@code dso} if it has one, else a new identifier.
* @throws IdentifierException
*/
public abstract String mint(Context context, DSpaceObject dso) throws IdentifierException;
/**
* Find the object named by a given identifier.
*
* @param context
* @param identifier to be resolved.
* @param attributes additional information for resolving {@code identifier}.
* @return the named object.
* @throws IdentifierNotFoundException
* @throws IdentifierNotResolvableException
*/
public abstract DSpaceObject resolve(Context context, String identifier, String... attributes) throws IdentifierNotFoundException, IdentifierNotResolvableException;;
/**
* Return the identifier for a DSpaceObject.
*
* @param context
* @param object The object to be looked up.
* @return identifier for {@code object}.
* @throws IdentifierNotFoundException
* @throws IdentifierNotResolvableException
*/
public abstract String lookup(Context context, DSpaceObject object) throws IdentifierNotFoundException, IdentifierNotResolvableException;;
/**
* Unbind this type of identifier(s) from an object.
*
* @param context
* @param dso object to lose its identity.
* @throws IdentifierException
*/
public abstract void delete(Context context, DSpaceObject dso) throws IdentifierException;
/**
* Unbind the given identifier from an object.
*
* @param context
* @param dso object to be de-identified.
* @param identifier to be removed.
* @throws IdentifierException
*/
public abstract void delete(Context context, DSpaceObject dso, String identifier) throws IdentifierException;
/**
* Set an object's identifier.
*
* @param context
* @param dso object to be identified.
* @param identifier to be set on the object.
* @throws IdentifierException
*/
public abstract void reserve(Context context, DSpaceObject dso, String identifier) throws IdentifierException;
/**
* Create a specific identifier and apply it to an object.
*
* @param context
* @param object to be identified.
* @param identifier to be created.
*/
public abstract void register(Context context, DSpaceObject object, String identifier) throws IdentifierException;
}

View File

@@ -0,0 +1,107 @@
/**
* 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.identifier.doi;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
/**
* A DOIConnector handles all calls to the API of your DOI registry.
*
* Please pay attention to the method {@link #purgeCachedInformation()}!
*
* @author Pascal-Nicolas Becker
*/
public interface DOIConnector {
public boolean isDOIReserved(Context context, String doi)
throws DOIIdentifierException;
public boolean isDOIReserved(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException;
public boolean isDOIRegistered(Context context, String doi)
throws DOIIdentifierException;
public boolean isDOIRegistered(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException;
/**
* Sends the DELETE-Request to the DOI registry.
*
* <p>This method sends a request to "delete" a DOI. As DOIs are persistant
* identifiers they should never be deleted. For example, if you send a HTTP
* DELETE request to the DataCite Metadata API directly, it will set the DOI
* to inactive.</p>
*
* <p>A DOIConnector does not have to check whether the DOI is reserved,
* registered or not. It will only send the request and return the answer in
* form of a boolean weather the deletion was successful or not. It may even
* throw an DOIIdentifierException in case you are not allowed to delete a
* DOI, the DOI does not exist, ... So please be sure that the deletion of a
* DOI is conform with the rules of the registry and that the DOI is in the
* appropriate state (f.e. reserved but not registered).</p>
*
* @param context
* @param doi
* @return
* @throws DOIIdentifierException
*/
public void deleteDOI(Context context, String doi)
throws DOIIdentifierException;
/**
* Sends a request to the DOI registry to reserve a DOI.
*
* Please check on your own if the DOI is already reserved or even
* registered before you try to reserve it. You can use
* {@link isDOIRegistered} and {@link isDOIReserved} for it. The
* DOIConnector won't do any tests and throws an DOIIdentifierException in
* case of any problems with the DOI you want to reserve.
*
* @param context
* @param dso
* @param doi
* @return
* @throws DOIIdentifierException
*/
public void reserveDOI(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException;
/**
* Sends a request to the DOI registry to register a DOI.
*
* Please check on your own if the DOI is already reserved or even
* registered before you try to register it. You can use the methods
* {@code DOIConnector.isDOIRegistered(...)} and
* {@code DOIConnector.isDOIReserved(...)} for it. The DOIConnector won't
* do any tests and throws an DOIIdentifierException in case of any problems
* with the DOI you want to register.
*
* @param context
* @param dso
* @param doi
* @return
* @throws DOIIdentifierException
*/
public void registerDOI(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException;
/**
* Sends a request to the DOI registry to update Metadate for a DOI.
* The DOIConnector won't do any tests and throws an IdentifierException
* in case of any problems with the DOI you want to update the metadata.
*
* @param context
* @param dso
* @param doi
* @return
* @throws IdentifierException
*/
public void updateMetadata(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException;
}

View File

@@ -0,0 +1,108 @@
/**
* 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.identifier.doi;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.event.Consumer;
import org.dspace.event.Event;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.IdentifierNotFoundException;
import org.dspace.search.SearchConsumer;
import org.dspace.utils.DSpace;
/**
*
* @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de)
*/
public class DOIConsumer implements Consumer
{
/** log4j logger */
private static Logger log = Logger.getLogger(DOIConsumer.class);
@Override
public void initialize() throws Exception {
// nothing to do
// we can ask spring to give as a properly setuped instance of
// DOIIdentifierProvider. Doing so we don't have to configure it and
// can load it in consume method as this is not very expensive.
}
// as we use asynchronous metadata update, our updates are not very expensive.
// so we can do everything in the consume method.
@Override
public void consume(Context ctx, Event event) throws Exception {
if (event.getSubjectType() != Constants.ITEM)
{
log.warn("DOIConsumer should not have been given this kind of "
+ "subject in an event, skipping: " + event.toString());
return;
}
if (Event.MODIFY_METADATA != event.getEventType())
{
log.warn("DOIConsumer should not have been given this kind of "
+ "event type, skipping: " + event.toString());
return;
}
DSpaceObject dso = event.getSubject(ctx);
//FIXME
if (!(dso instanceof Item))
{
log.warn("DOIConsumer got an event whose subject was not an item, "
+ "skipping: " + event.toString());
}
Item item = (Item) dso;
DOIIdentifierProvider provider = new DSpace().getSingletonService(
DOIIdentifierProvider.class);
String doi = null;
try {
doi = provider.lookup(ctx, dso);
}
catch (IdentifierNotFoundException ex)
{
log.warn("DOIConsumer cannot handles items without DOIs, skipping: "
+ event.toString());
}
try
{
provider.updateMetadata(ctx, dso, doi);
}
catch (IllegalArgumentException ex)
{
// should not happen, as we got the DOI from the DOIProvider
log.warn("DOIConsumer caught an IdentifierException.", ex);
}
catch (IdentifierException ex)
{
log.warn("DOIConsumer cannot update metadata for Item with ID "
+ item.getID() + " and DOI " + doi + ".", ex);
}
}
@Override
public void end(Context ctx) throws Exception {
}
@Override
public void finish(Context ctx) throws Exception {
// nothing to do
}
}

View File

@@ -0,0 +1,175 @@
/**
* 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.identifier.doi;
import org.dspace.identifier.IdentifierException;
/**
*
* @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de)
*/
public class DOIIdentifierException extends IdentifierException {
/**
* Default.
*/
public static final int CODE_NOT_SET = 0;
/**
* A specified DOI does not exists.
*/
public static final int DOI_DOES_NOT_EXIST = 1;
/**
* A DOI cannot be created, registered, reserved, and so on because it is
* already used for another object.
*/
public static final int DOI_ALREADY_EXISTS = 2;
/**
* A DOI cannot be created, registered, reserved and so on because it uses a
* foreign prefix.
*/
public static final int FOREIGN_DOI = 3;
/**
* We got a answer from a registration agency that could not be parsed.
* Either they changed there API or the DOIConnector does not implement it
* properly.
*/
public static final int BAD_ANSWER = 4;
/**
* The registration agency was unable to parse our request. Either they
* changed there API or the DOIConnector does not implement it properly.
*/
public static final int BAD_REQUEST = 5;
/**
* Some registration agencies request that a DOI gets reserved before it can
* be registered. This error code signals that a unreserved DOI should be
* registered and that the registration agency denied it.
*/
public static final int REGISTER_FIRST = 6;
/**
* Error while authenticating against the registration agency.
*/
public static final int AUTHENTICATION_ERROR = 7;
/**
* A internal error occurred either in the registration agency or in the
* DOIConnector.
*/
public static final int INTERNAL_ERROR = 8;
/**
* An error arose while metadata conversion.
*/
public static final int CONVERSION_ERROR = 9;
/**
* A DOI and a provided object does not match. This error occurs if you try
* to connect an object with a DOI that is reserved or registered for
* another object.
*/
public static final int MISMATCH = 10;
/**
* An identifier supplied as DOI could not be recognized.
*/
public static final int UNRECOGNIZED = 11;
/**
* DSpace did not allowed to manipulate the metadata of an DSpaceObject.
*/
public static final int UNAUTHORIZED_METADATA_MANIPULATION = 12;
/**
* You tried to reserve or register a DOI that is marked as DELETED.
*/
public static final int DOI_IS_DELETED = 13;
private int code;
// FOR DEBUGGING
public static String codeToString(int code) {
switch (code) {
case CODE_NOT_SET:
return "CODE_NOT_SET";
case DOI_DOES_NOT_EXIST:
return "DOI_DOES_NOT_EXSIT";
case DOI_ALREADY_EXISTS:
return "DOI_ALREADY_EXISTS";
case FOREIGN_DOI:
return "FOREIGN_DOI";
case BAD_ANSWER:
return "BAD_ANSWER";
case REGISTER_FIRST:
return "REGISTER_FIRST";
case AUTHENTICATION_ERROR:
return "AUTHENTICATION_ERROR";
case INTERNAL_ERROR:
return "INTERNAL_ERROR";
case CONVERSION_ERROR:
return "CONVERSION_ERROR";
case MISMATCH:
return "MISMATCH";
case UNRECOGNIZED:
return "UNRECOGNIZED";
case UNAUTHORIZED_METADATA_MANIPULATION:
return "UNAUTHORIZED_METADATA_MANIPULATION";
case DOI_IS_DELETED:
return "DELETED";
default:
return "UNKOWN";
}
}
public DOIIdentifierException() {
super();
this.code = this.CODE_NOT_SET;
}
public DOIIdentifierException(int code) {
super();
this.code = code;
}
public DOIIdentifierException(String message) {
super(message);
this.code = this.CODE_NOT_SET;
}
public DOIIdentifierException(String message, int code) {
super(message);
this.code = code;
}
public DOIIdentifierException(String message, Throwable cause) {
super(message, cause);
this.code = this.CODE_NOT_SET;
}
public DOIIdentifierException(String message, Throwable cause, int code) {
super(message, cause);
this.code = code;
}
public DOIIdentifierException(Throwable cause) {
super(cause);
this.code = this.CODE_NOT_SET;
}
public DOIIdentifierException(Throwable cause, int code) {
super(cause);
this.code = code;
}
public int getCode()
{
return this.code;
}
public String getMessage()
{
String message = super.getMessage();
if ((message == null || message.isEmpty()) && code != CODE_NOT_SET)
{
return codeToString(code);
}
return message;
}
}

View File

@@ -0,0 +1,936 @@
/**
* 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.identifier.doi;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.SQLException;
import java.util.Date;
import java.util.Locale;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.handle.HandleManager;
import org.dspace.identifier.DOI;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.storage.rdbms.TableRowIterator;
import org.dspace.utils.DSpace;
/**
*
* @author Marsa Haoua
* @author Pascal-Nicolas Becker
*/
public class DOIOrganiser {
private static final Logger LOG = Logger.getLogger(DOIOrganiser.class);
private DOIIdentifierProvider provider;
private Context context;
private boolean quiet;
public DOIOrganiser(Context context, DOIIdentifierProvider provider)
{
this.context = context;
this.provider = provider;
this.quiet = false;
}
public static void main(String[] args)
{
LOG.debug("Starting DOI organiser ");
// setup Context
Context context = null;
try {
context = new Context();
}
catch (SQLException sqle)
{
System.err.println("Can't connect to database: " + sqle.getMessage());
System.exit(-1);
}
// Started from commandline, don't use the authentication system.
context.turnOffAuthorisationSystem();
DOIOrganiser organiser = new DOIOrganiser(context, new DSpace().getSingletonService(DOIIdentifierProvider.class));
// run command line interface
runCLI(context, organiser, args);
try
{
context.complete();
}
catch (SQLException sqle)
{
System.err.println("Cannot save changes to database: " + sqle.getMessage());
System.exit(-1);
}
}
public static void runCLI(Context context, DOIOrganiser organiser, String[] args)
{
// initlize options
Options options = new Options();
options.addOption("h", "help", false, "Help");
options.addOption("l", "list", false,
"List all objects to be reserved, registered, deleted of updated ");
options.addOption("r", "register-all", false,
"Perform online registration for all identifiers queued for registration.");
options.addOption("s", "reserve-all", false,
"Perform online reservation for all identifiers queued for reservation.");
options.addOption("u", "update-all", false,
"Perform online metadata update for all identifiers queued for metadata update.");
options.addOption("d", "delete-all", false,
"Perform online deletion for all identifiers queued for deletion.");
options.addOption("q", "quiet", false,
"Turn the command line output off.");
Option registerDoi = OptionBuilder.withArgName("DOI|ItemID|handle")
.withLongOpt("register-doi")
.hasArgs(1)
.withDescription("Register a specified identifier. "
+ "You can specify the identifier by ItemID, Handle or DOI.")
.create();
options.addOption(registerDoi);
Option reserveDoi = OptionBuilder.withArgName("DOI|ItemID|handle")
.withLongOpt("reserve-doi")
.hasArgs(1)
.withDescription("Reserve a specified identifier online. "
+ "You can specify the identifier by ItemID, Handle or DOI.")
.create();
options.addOption(reserveDoi);
Option update = OptionBuilder.withArgName("DOI|ItemID|handle")
.hasArgs(1)
.withDescription("Update online an object for a given DOI identifier"
+ " or ItemID or Handle. A DOI identifier or an ItemID or a Handle is needed.\n")
.withLongOpt("update-doi")
.create();
options.addOption(update);
Option delete = OptionBuilder.withArgName("DOI identifier")
.withLongOpt("delete-doi")
.hasArgs(1)
.withDescription("Delete a specified identifier.")
.create();
options.addOption(delete);
// initialize parser
CommandLineParser parser = new PosixParser();
CommandLine line = null;
HelpFormatter helpformater = new HelpFormatter();
try
{
line = parser.parse(options, args);
}
catch (ParseException ex)
{
LOG.fatal(ex);
System.exit(1);
}
// process options
// user asks for help
if (line.hasOption('h') || 0 == line.getOptions().length)
{
helpformater.printHelp("\nDOI organiser\n", options);
}
if (line.hasOption('q'))
{
organiser.setQuiet();
}
if (line.hasOption('l'))
{
organiser.list("reservation", null, null, DOIIdentifierProvider.TO_BE_RESERVERED);
organiser.list("registration", null, null, DOIIdentifierProvider.TO_BE_REGISTERED);
organiser.list("update", null, null,
DOIIdentifierProvider.UPDATE_BEFORE_REGISTERATION,
DOIIdentifierProvider.UPDATE_REGISTERED,
DOIIdentifierProvider.UPDATE_RESERVERED);
organiser.list("deletion", null, null, DOIIdentifierProvider.TO_BE_DELETED);
}
if (line.hasOption('s'))
{
TableRowIterator it = organiser
.getDOIsByStatus(DOIIdentifierProvider.TO_BE_RESERVERED);
try {
if (!it.hasNext())
{
System.err.println("There are no objects in the database "
+ "that could be reserved.");
}
while (it.hasNext())
{
TableRow doiRow = it.next();
DSpaceObject dso = DSpaceObject.find(
context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
organiser.reserve(doiRow, dso);
}
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
}
}
if (line.hasOption('r'))
{
TableRowIterator it = organiser
.getDOIsByStatus(DOIIdentifierProvider.TO_BE_REGISTERED);
try {
if (!it.hasNext())
{
System.err.println("There are no objects in the database "
+ "that could be registered.");
}
while (it.hasNext())
{
TableRow doiRow = it.next();
DSpaceObject dso = DSpaceObject.find(
context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
organiser.register(doiRow, dso);
}
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
}
}
if (line.hasOption('u'))
{
TableRowIterator it = organiser.getDOIsByStatus(
DOIIdentifierProvider.UPDATE_BEFORE_REGISTERATION,
DOIIdentifierProvider.UPDATE_RESERVERED,
DOIIdentifierProvider.UPDATE_REGISTERED);
try {
if (!it.hasNext())
{
System.err.println("There are no objects in the database "
+ "whose metadata needs an update.");
}
while (it.hasNext())
{
TableRow doiRow = it.next();
DSpaceObject dso = DSpaceObject.find(
context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
organiser.update(doiRow, dso);
}
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
}
}
if (line.hasOption('d'))
{
TableRowIterator it = organiser
.getDOIsByStatus(DOIIdentifierProvider.TO_BE_DELETED);
try {
if (!it.hasNext())
{
System.err.println("There are no objects in the database "
+ "that could be deleted.");
}
while (it.hasNext())
{
TableRow doiRow = it.next();
organiser.delete(doiRow.getStringColumn("doi"));
}
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
}
}
if(line.hasOption("reserve-doi"))
{
String identifier = line.getOptionValue("reserve-doi");
if(null == identifier)
{
helpformater.printHelp("\nDOI organiser\n", options);
}
else
{
try {
TableRow doiRow = organiser.findTableRow(identifier);
DSpaceObject dso = DSpaceObject.find(
context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
organiser.reserve(doiRow, dso);
}
catch (SQLException ex)
{
LOG.error(ex);
}
catch (IllegalArgumentException ex)
{
LOG.error(ex);
}
catch (IllegalStateException ex)
{
LOG.error(ex);
} catch (IdentifierException ex) {
LOG.error(ex);
}
}
}
if(line.hasOption("register-doi"))
{
String identifier = line.getOptionValue("register-doi");
if(null == identifier)
{
helpformater.printHelp("\nDOI organiser\n", options);
}
else
{
try {
TableRow doiRow = organiser.findTableRow(identifier);
DSpaceObject dso = DSpaceObject.find(
context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
organiser.register(doiRow, dso);
} catch (SQLException ex) {
LOG.error(ex);
} catch (IllegalArgumentException ex) {
LOG.error(ex);
} catch (IllegalStateException ex) {
LOG.error(ex);
} catch (IdentifierException ex) {
LOG.error(ex);
}
}
}
if(line.hasOption("update-doi"))
{
String identifier = line.getOptionValue('u');
if(null == identifier)
{
helpformater.printHelp("\nDOI organiser\n", options);
}
else
{
try {
TableRow doiRow = organiser.findTableRow(identifier);
DSpaceObject dso = DSpaceObject.find(
context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
organiser.update(doiRow, dso);
} catch (SQLException ex) {
LOG.error(ex);
} catch (IllegalArgumentException ex) {
LOG.error(ex);
} catch (IllegalStateException ex) {
LOG.error(ex);
} catch (IdentifierException ex) {
LOG.error(ex);
}
}
}
if(line.hasOption("delete-doi"))
{
String identifier = line.getOptionValue('d');
if (null == identifier)
{
helpformater.printHelp("\nDOI organiser\n", options);
}
else {
try {
organiser.delete(identifier);
} catch (SQLException ex) {
LOG.error(ex);
} catch (IllegalArgumentException ex) {
LOG.error(ex);
}
}
}
}
public TableRowIterator getDOIsByStatus(Integer ... status)
{
try
{
String sql = "SELECT * FROM Doi";
for (int i = 0; i < status.length ; i++)
{
if (0 == i)
{
sql += " WHERE ";
}
else
{
sql += " OR ";
}
sql += " status = ?";
}
if (status.length < 1)
{
return DatabaseManager.queryTable(context, "Doi", sql);
}
return DatabaseManager.queryTable(context, "Doi", sql, status);
}
catch (SQLException ex)
{
LOG.error("Error while trying to get data from database", ex);
throw new RuntimeException("Error while trying to get data from database", ex);
}
}
public void list(String processName, PrintStream out, PrintStream err, Integer ... status)
{
String indent = " ";
if (null == out)
{
out = System.out;
}
if (null == err)
{
err = System.err;
}
TableRowIterator it = this.getDOIsByStatus(status);
try
{
if (it.hasNext())
{
out.println("DOIs queued for " + processName + ": ");
}
else
{
out.println("There are no DOIs queued for " + processName + ".");
}
while(it.hasNext())
{
TableRow doiRow = it.next();
DSpaceObject dso = DSpaceObject.find(context,
doiRow.getIntColumn("resource_type_id"),
doiRow.getIntColumn("resource_id"));
out.print(indent + DOI.SCHEME + doiRow.getStringColumn("doi"));
if (null != dso)
{
out.println(" (belongs to item with handle " + dso.getHandle() + ")");
}
else
{
out.println(" (cannot determine handle of assigned object)");
}
}
out.println("");
}
catch (SQLException ex)
{
err.println("Error in database Connection: " + ex.getMessage());
ex.printStackTrace(err);
}
finally
{
it.close();
}
}
public void register(TableRow doiRow, DSpaceObject dso) throws SQLException
{
if (Constants.ITEM != dso.getType())
{
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
}
try {
provider.registerOnline(context, dso,
DOI.SCHEME + doiRow.getStringColumn("doi"));
if(!quiet)
{
System.out.println("This identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " is successfully registered.");
}
}
catch (IdentifierException ex)
{
if (!(ex instanceof DOIIdentifierException))
{
LOG.error("It wasn't possible to register this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " online. ", ex);
}
DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex;
try
{
sendAlertMail("Register", dso,
DOI.SCHEME + doiRow.getStringColumn("doi"),
doiIdentifierException.codeToString(doiIdentifierException
.getCode()));
}
catch (IOException ioe)
{
LOG.error("Couldn't send mail", ioe);
}
LOG.error("It wasn't possible to register this identifier : "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " online. Exceptions code: "
+ doiIdentifierException
.codeToString(doiIdentifierException.getCode()), ex);
if(!quiet)
{
System.err.println("It wasn't possible to register this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
}
catch (IllegalArgumentException ex)
{
LOG.error("Database table DOI contains a DOI that is not valid: "
+ DOI.SCHEME + doiRow.getStringColumn("doi") + "!", ex);
if(!quiet)
{
System.err.println("It wasn't possible to register this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
throw new IllegalStateException("Database table DOI contains a DOI "
+ " that is not valid: "
+ DOI.SCHEME + doiRow.getStringColumn("doi") + "!", ex);
}
catch (SQLException ex)
{
LOG.error("Error while trying to get data from database", ex);
if(!quiet)
{
System.err.println("It wasn't possible to register this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
throw new RuntimeException("Error while trying to get data from database", ex);
}
}
public void reserve(TableRow doiRow, DSpaceObject dso) throws SQLException
{
if (Constants.ITEM != dso.getType())
{
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
}
try
{
provider.reserveOnline(context, dso,
DOI.SCHEME + doiRow.getStringColumn("doi"));
if(!quiet)
{
System.out.println("This identifier : "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " is successfully reserved.");
}
}
catch (IdentifierException ex)
{
if (!(ex instanceof DOIIdentifierException))
{
LOG.error("It wasn't possible to register this identifier : "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " online. ",ex);
}
DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex;
try
{
sendAlertMail("Reserve", dso,
DOI.SCHEME + doiRow.getStringColumn("doi"),
DOIIdentifierException.codeToString(
doiIdentifierException.getCode()));
}
catch (IOException ioe)
{
LOG.error("Couldn't send mail", ioe);
}
LOG.error("It wasn't possible to reserve the identifier online. "
+ " Exceptions code: "
+ DOIIdentifierException
.codeToString(doiIdentifierException.getCode()), ex);
if(!quiet)
{
System.err.println("It wasn't possible to reserve this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
}
catch (IllegalArgumentException ex)
{
LOG.error("Database table DOI contains a DOI that is not valid: "
+ DOI.SCHEME + doiRow.getStringColumn("doi") + "!", ex);
if(!quiet)
{
System.err.println("It wasn't possible to reserve this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
throw new IllegalStateException("Database table DOI contains a DOI "
+ " that is not valid: "
+ DOI.SCHEME + doiRow.getStringColumn("doi") + "!", ex);
}
catch (SQLException ex)
{
LOG.error("Error while trying to get data from database", ex);
if(!quiet)
{
System.err.println("It wasn't possible to reserve this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
throw new RuntimeException("Error while trying to get data from database", ex);
}
}
public void update(TableRow doiRow, DSpaceObject dso)
{
if (Constants.ITEM != dso.getType())
{
throw new IllegalArgumentException("Currenty DSpace supports DOIs "
+ "for Items only.");
}
try
{
provider.updateMetadataOnline(context, dso,
DOI.SCHEME + doiRow.getStringColumn("doi"));
if(!quiet)
{
System.out.println("Successfully updated metadata of DOI " + DOI.SCHEME
+ doiRow.getStringColumn("doi") + ".");
}
}
catch (IdentifierException ex)
{
if (!(ex instanceof DOIIdentifierException))
{
LOG.error("It wasn't possible to register the identifier online. ",ex);
}
DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex;
try
{
sendAlertMail("Update", dso,
DOI.SCHEME + doiRow.getStringColumn("doi"),
doiIdentifierException.codeToString(doiIdentifierException
.getCode()));
}
catch (IOException ioe)
{
LOG.error("Couldn't send mail", ioe);
}
LOG.error("It wasn't possible to update this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " Exceptions code: "
+ doiIdentifierException
.codeToString(doiIdentifierException.getCode()), ex);
if(!quiet)
{
System.err.println("It wasn't possible to update this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
}
catch (IllegalArgumentException ex)
{
LOG.error("Database table DOI contains a DOI that is not valid: "
+ DOI.SCHEME + doiRow.getStringColumn("doi") + "!", ex);
if(!quiet)
{
System.err.println("It wasn't possible to update this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
throw new IllegalStateException("Database table DOI contains a DOI "
+ " that is not valid: "
+ DOI.SCHEME + doiRow.getStringColumn("doi") + "!", ex);
}
catch (SQLException ex)
{
LOG.error("It wasn't possible to connect to the Database!", ex);
}
}
public void delete(String identifier)
throws SQLException
{
String doi = null;
TableRow doiRow = null;
try
{
doi = DOI.formatIdentifier(identifier);
// If there's no exception: we found a valid DOI. :)
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
if (null == doiRow)
{
throw new IllegalStateException("You specified a valid DOI,"
+ " that is not stored in our database.");
}
provider.deleteOnline(context, doi);
if (!quiet)
{
System.err.println("It was possible to delete this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " online.");
}
}
catch (DOIIdentifierException ex)
{
// Identifier was not recognized as DOI.
LOG.error("It wasn't possible to detect this identifier: "
+ identifier
+ " Exceptions code: "
+ ex.codeToString(ex.getCode()), ex);
if (!quiet)
{
System.err.println("It wasn't possible to detect this identifier: "
+ identifier);
}
}
catch (IllegalArgumentException ex)
{
if (!quiet)
{
System.err.println("It wasn't possible to delete this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi")
+ " online. Take a look in log file.");
}
}
}
/**
* Finds the TableRow in the Doi table that belongs to the specified
* DspaceObject.
*
* @param identifier Either an ItemID, a DOI or a handle. If the identifier
* contains digits only we treat it as ItemID, if not we try to find a
* matching doi or a handle (in this order).
* @return The TableRow or null if the Object does not have a DOI.
* @throws SQLException
* @throws IllegalArgumentException If the identifier is null, an empty
* String or specifies an DSpaceObject that is not an item. We currently
* support DOIs for items only, but this may change once...
* @throws IllegalStateException If the identifier was a valid DOI that is
* not stored in our database or if it is a handle that is not bound to an
* DSpaceObject.
*/
public TableRow findTableRow(String identifier)
throws SQLException, IllegalArgumentException, IllegalStateException, IdentifierException
{
if (null == identifier || identifier.isEmpty())
{
throw new IllegalArgumentException("Identifier is null or empty.");
}
String sql = "SELECT * FROM Doi WHERE resource_type_id = ? AND resource_id = ? ";
TableRow doiRow = null;
String doi = null;
// detect it identifer is ItemID, handle or DOI.
// try to detect ItemID
if (identifier.matches("\\d*"))
{
Integer itemID = Integer.valueOf(identifier);
DSpaceObject dso = Item.find(context, itemID);
if (null != dso)
{
doiRow = DatabaseManager.querySingleTable(context, "Doi",
sql, Constants.ITEM, dso.getID());
//Check if this Item has an Identifier, mint one if it doesn't
if (null == doiRow)
{
doi = provider.mint(context, dso);
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
return doiRow;
}
return doiRow;
}
else
{
throw new IllegalStateException("You specified an ItemID, "
+ "that is not stored in our database.");
}
}
// detect handle
DSpaceObject dso = HandleManager.resolveToObject(context, identifier);
if (null != dso)
{
if (dso.getType() != Constants.ITEM)
{
throw new IllegalArgumentException(
"Currently DSpace supports DOIs for Items only. "
+ "Cannot process specified handle as it does not identify an Item.");
}
doiRow = DatabaseManager.querySingleTable(context, "Doi", sql,
Constants.ITEM, dso.getID());
if (null == doiRow)
{
doi = provider.mint(context, dso);
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
}
return doiRow;
}
// detect DOI
try {
doi = DOI.formatIdentifier(identifier);
// If there's no exception: we found a valid DOI. :)
doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
if (null == doiRow)
{
throw new IllegalStateException("You specified a valid DOI,"
+ " that is not stored in our database.");
}
}
catch (DOIIdentifierException ex)
{
// Identifier was not recognized as DOI.
LOG.error("It wasn't possible to detect this identifier: "
+ identifier
+ " Exceptions code: "
+ ex.codeToString(ex.getCode()), ex);
if(!quiet)
{
System.err.println("It wasn't possible to detect this identifier: "
+ DOI.SCHEME + doiRow.getStringColumn("doi"));
}
}
return doiRow;
}
private void sendAlertMail(String action, DSpaceObject dso, String doi, String reason)
throws IOException
{
String recipient = ConfigurationManager.getProperty("alert.recipient");
try
{
if (recipient != null)
{
Email email = Email.getEmail(
I18nUtil.getEmailFilename(Locale.getDefault(), "doi_maintenance_error"));
email.addRecipient(recipient);
email.addArgument(action);
email.addArgument(new Date());
email.addArgument(dso.getTypeText());
email.addArgument(new Integer(dso.getID()));
email.addArgument(doi);
email.addArgument(reason);
email.send();
if (!quiet)
{
System.err.println("Email alert is sent.");
}
}
}
catch (Exception e) {
LOG.warn("Unable to send email alert", e);
if (!quiet)
{
System.err.println("Unable to send email alert.");
}
}
}
private void setQuiet()
{
this.quiet = true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
/**
* 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/
*/
/**
* Make requests to the DOI registration angencies, f.e.to
* <a href='http://n2t.net/ezid/'>EZID</a> DOI service, and analyze the responses.
*
* <p>
* Use {@link org.dspace.identifier.doi.EZIDRequestFactory#getInstance} to configure an {@link org.dspace.identifier.doi.EZIDRequest}
* with your authority number and credentials. {@code EZIDRequest} encapsulates
* EZID's operations (lookup, create/mint, modify, delete...).
* An operation returns an {@link org.dspace.identifier.ezid.EZIDResponse} which gives easy access to
* EZID's status code and value, status of the underlying HTTP request, and
* key/value pairs found in the response body (if any).
* <p>
*/
package org.dspace.identifier.doi;

View File

@@ -0,0 +1,17 @@
/**
* 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/
*/
/**
* Providers of durable unique identifiers (Handles, DOIs, etc.).
* Generally, subclasses of {@link org.dspace.identifier.IdentifierProvider}
* offer methods to create, delete, and resolve subclasses of
* {@link org.dspace.identifier.Identifier}. Classes outside this package
* should rely on {@link org.dspace.identifier.IdentifierService} to perform
* these operations using the most appropriate provider.
*/
package org.dspace.identifier;

View File

@@ -0,0 +1,711 @@
/**
* 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.identifier;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import org.dspace.AbstractUnitTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.*;
import org.dspace.core.Context;
import org.dspace.kernel.ServiceManager;
import org.dspace.services.ConfigurationService;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.workflow.WorkflowItem;
import org.dspace.workflow.WorkflowManager;
import org.junit.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
/**
* Tests for {@link DataCiteIdentifierProvider}.
*
* @author Mark H. Wood
* @author Pascal-Nicolas Becker
*/
public class DOIIdentifierProviderTest
extends AbstractUnitTest
{
private static final String PREFIX = "10.5072";
private static final String NAMESPACE_SEPARATOR = "dspaceUnitTests-";
private static ServiceManager sm = null;
private static ConfigurationService config = null;
private static Community community;
private static Collection collection;
private static MockDOIConnector connector;
private DOIIdentifierProvider provider;
/** The most recently created test Item's ID */
private static int itemID;
public DOIIdentifierProviderTest()
{
}
private static void dumpMetadata(Item eyetem)
{
DCValue[] metadata = eyetem.getMetadata("dc", Item.ANY, Item.ANY, Item.ANY);
for (DCValue metadatum : metadata)
System.out.printf("Metadata: %s.%s.%s(%s) = %s\n",
metadatum.schema,
metadatum.element,
metadatum.qualifier,
metadatum.language,
metadatum.value);
}
/**
* Create a fresh Item, installed in the repository.
*
* @throws SQLException
* @throws AuthorizeException
* @throws IOException
*/
private Item newItem(Context ctx)
throws SQLException, AuthorizeException, IOException
{
ctx.turnOffAuthorisationSystem();
ctx.setCurrentUser(eperson);
WorkspaceItem wsItem = WorkspaceItem.create(ctx, collection, false);
WorkflowItem wfItem = WorkflowManager.start(ctx, wsItem);
WorkflowManager.advance(ctx, wfItem, ctx.getCurrentUser());
Item item = wfItem.getItem();
item.addMetadata("dc", "contributor", "author", null, "Author, A. N.");
item.addMetadata("dc", "title", null, null, "A Test Object");
item.addMetadata("dc", "publisher", null, null, "DSpace Test Harness");
// If DOIIdentifierProvider is configured
// (dspace/conf/spring/api/identifier-service.xml) the new created item
// gets automatically a DOI. We remove this DOI as it can make problems
// with the tests.
String sql = "DELETE FROM Doi WHERE resource_type_id = ? AND resource_id = ?";
DatabaseManager.updateQuery(context, sql, item.getType(), item.getID());
DCValue[] metadata = item.getMetadata(
DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
List<String> remainder = new ArrayList<String>();
for (DCValue id : metadata)
{
if (!id.value.startsWith(DOI.RESOLVER))
{
remainder.add(id.value);
}
}
item.clearMetadata(
DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
item.addMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null,
remainder.toArray(new String[remainder.size()]));
item.update();
ctx.commit();
ctx.restoreAuthSystemState();
return item;
}
public String createDOI(Item item, Integer status, boolean metadata)
throws SQLException, IdentifierException, AuthorizeException
{
return this.createDOI(item, status, metadata, null);
}
/**
* Create a DOI to an item.
* @param item Item the DOI should be created for.
* @param status The status of the DOI.
* @param metadata Whether the DOI should be included in the metadata of the item.
* @param doi The doi or null if we should generate one.
* @return the DOI
* @throws SQLException
*/
public String createDOI(Item item, Integer status, boolean metadata, String doi)
throws SQLException, IdentifierException, AuthorizeException
{
// we need some random data. UUIDs would be bloated here
Random random = new Random();
if (null == doi)
{
doi = DOI.SCHEME + PREFIX + "/" + NAMESPACE_SEPARATOR
+ Long.toHexString(new Date().getTime()) + "-"
+ random.nextInt(997);
}
TableRow doiRow = DatabaseManager.create(context, "Doi");
doiRow.setColumn("doi", doi.substring(DOI.SCHEME.length()));
doiRow.setColumn("resource_type_id", item.getType());
doiRow.setColumn("resource_id", item.getID());
if (status == null)
{
doiRow.setColumnNull("status");
}
else
{
doiRow.setColumn("status", status);
}
assumeTrue(1 == DatabaseManager.update(context, doiRow));
if (metadata)
{
item.addMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null,
DOI.DOIToExternalForm(doi));
item.update();
}
context.commit();
return doi;
}
@BeforeClass
public static void setUpClass()
throws Exception
{
// Find the usual kernel services
sm = kernelImpl.getServiceManager();
Context ctx = new Context();
ctx.turnOffAuthorisationSystem();
ctx.setCurrentUser(eperson);
// Create an environment for our test objects to live in.
community = Community.create(null, ctx);
community.setMetadata("name", "A Test Community");
community.update();
collection = community.createCollection();
collection.setMetadata("name", "A Test Collection");
collection.update();
ctx.complete();
config = kernelImpl.getConfigurationService();
// Configure the service under test.
config.setProperty(DOIIdentifierProvider.CFG_PREFIX, PREFIX);
config.setProperty(DOIIdentifierProvider.CFG_NAMESPACE_SEPARATOR,
NAMESPACE_SEPARATOR);
// Don't try to send mail.
config.setProperty("mail.server.disabled", "true");
connector = new MockDOIConnector();
}
@AfterClass
public static void tearDownClass()
throws Exception
{
/*
System.out.print("Tearing down\n\n");
Context ctx = new Context();
dumpMetadata(Item.find(ctx, itemID));
*/
}
@Before
public void setUp()
{
context.setCurrentUser(eperson);
context.turnOffAuthorisationSystem();
provider = new DOIIdentifierProvider();
provider.setConfigurationService(config);
provider.setDOIConnector(connector);
}
@After
public void tearDown()
{
context.restoreAuthSystemState();
connector.reset();
}
/**
* Test of supports method, of class DataCiteIdentifierProvider.
*/
@Test
public void testSupports_Class()
{
Class<? extends Identifier> identifier = DOI.class;
assertTrue("DOI should be supported", provider.supports(identifier));
}
@Test
public void testSupports_valid_String()
{
String[] validDOIs = new String[]
{
"10.5072/123abc-lkj/kljl",
PREFIX + "/" + NAMESPACE_SEPARATOR + "lkjljasd1234",
DOI.SCHEME + "10.5072/123abc-lkj/kljl",
"http://dx.doi.org/10.5072/123abc-lkj/kljl",
DOI.RESOLVER + "/10.5072/123abc-lkj/kljl"
};
for (String doi : validDOIs)
{
assertTrue("DOI should be supported", provider.supports(doi));
}
}
@Test
public void testDoes_not_support_invalid_String()
{
String[] invalidDOIs = new String[]
{
"11.5072/123abc-lkj/kljl",
"http://hdl.handle.net/handle/10.5072/123abc-lkj/kljl",
"",
null
};
for (String notADoi : invalidDOIs)
{
assertFalse("Invalid DOIs shouldn't be supported",
provider.supports(notADoi));
}
}
@Test
public void testStore_DOI_as_item_metadata()
throws SQLException, AuthorizeException, IOException, IdentifierException
{
Item item = newItem(context);
String doi = DOI.SCHEME + PREFIX + "/" + NAMESPACE_SEPARATOR
+ Long.toHexString(new Date().getTime());
provider.saveDOIToObject(context, item, doi);
DCValue[] metadata = item.getMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
boolean result = false;
for (DCValue id : metadata)
{
if (id.value.equals(DOI.DOIToExternalForm(doi)))
{
result = true;
}
}
assertTrue("Cannot store DOI as item metadata value.", result);
}
@Test
public void testGet_DOI_out_of_item_metadata()
throws SQLException, AuthorizeException, IOException, IdentifierException
{
Item item = newItem(context);
String doi = DOI.SCHEME + PREFIX + "/" + NAMESPACE_SEPARATOR
+ Long.toHexString(new Date().getTime());
item.addMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null,
DOI.DOIToExternalForm(doi));
item.update();
context.commit();
assertTrue("Failed to recognize DOI in item metadata.",
doi.equals(DOIIdentifierProvider.getDOIOutOfObject(item)));
}
@Test
public void testRemove_DOI_from_item_metadata()
throws SQLException, AuthorizeException, IOException, IdentifierException
{
Item item = newItem(context);
String doi = DOI.SCHEME + PREFIX + "/" + NAMESPACE_SEPARATOR
+ Long.toHexString(new Date().getTime());
item.addMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null,
DOI.DOIToExternalForm(doi));
item.update();
context.commit();
provider.removeDOIFromObject(context, item, doi);
DCValue[] metadata = item.getMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
boolean foundDOI = false;
for (DCValue id : metadata)
{
if (id.value.equals(DOI.DOIToExternalForm(doi)))
{
foundDOI = true;
}
}
assertFalse("Cannot remove DOI from item metadata.", foundDOI);
}
@Test
public void testGet_DOI_by_DSpaceObject()
throws SQLException, AuthorizeException, IOException,
IllegalArgumentException, IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, false);
String retrievedDOI = DOIIdentifierProvider.getDOIByObject(context, item);
assertNotNull("Failed to load DOI by DSpaceObject.", retrievedDOI);
assertTrue("Loaded wrong DOI by DSpaceObject.", doi.equals(retrievedDOI));
}
@Test
public void testGet_DOI_lookup()
throws SQLException, AuthorizeException, IOException,
IllegalArgumentException, IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, false);
String retrievedDOI = provider.lookup(context, (DSpaceObject) item);
assertNotNull("Failed to loookup doi.", retrievedDOI);
assertTrue("Loaded wrong DOI on lookup.", doi.equals(retrievedDOI));
}
@Test
public void testGet_DSpaceObject_by_DOI()
throws SQLException, AuthorizeException, IOException,
IllegalArgumentException, IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, false);
DSpaceObject dso = DOIIdentifierProvider.getObjectByDOI(context, doi);
assertNotNull("Failed to load DSpaceObject by DOI.", dso);
if (item.getType() != dso.getType() || item.getID() != dso.getID())
{
fail("Object loaded by DOI was another object then expected!");
}
}
@Test
public void testResolve_DOI()
throws SQLException, AuthorizeException, IOException,
IllegalArgumentException, IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, false);
DSpaceObject dso = provider.resolve(context, doi);
assertNotNull("Failed to resolve DOI.", dso);
if (item.getType() != dso.getType() || item.getID() != dso.getID())
{
fail("Object return by DOI lookup was another object then expected!");
}
}
/*
* The following test seems a bit silly, but it was helpful to debug some
* problems while deleting DOIs.
*/
@Test
public void testRemove_two_DOIs_from_item_metadata()
throws SQLException, AuthorizeException, IOException, IdentifierException
{
// add two DOIs.
Item item = newItem(context);
String doi1 = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, true);
String doi2 = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, true);
// remove one of it
provider.removeDOIFromObject(context, item, doi1);
// assure that the right one was removed
DCValue[] metadata = item.getMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
boolean foundDOI1 = false;
boolean foundDOI2 = false;
for (DCValue id : metadata)
{
if (id.value.equals(DOI.DOIToExternalForm(doi1)))
{
foundDOI1 = true;
}
if (id.value.equals(DOI.DOIToExternalForm(doi2)))
{
foundDOI2 = true;
}
}
assertFalse("Cannot remove DOI from item metadata.", foundDOI1);
assertTrue("Removed wrong DOI from item metadata.", foundDOI2);
// remove the otherone as well.
provider.removeDOIFromObject(context, item, doi2);
// check it
metadata = item.getMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
foundDOI1 = false;
foundDOI2 = false;
for (DCValue id : metadata)
{
if (id.value.equals(DOI.DOIToExternalForm(doi1)))
{
foundDOI1 = true;
}
if (id.value.equals(DOI.DOIToExternalForm(doi2)))
{
foundDOI2 = true;
}
}
assertFalse("Cannot remove DOI from item metadata.", foundDOI1);
assertFalse("Cannot remove DOI from item metadata.", foundDOI2);
}
@Test
public void testMintDOI() throws SQLException, AuthorizeException, IOException
{
Item item = newItem(context);
String doi = null;
try
{
// get a DOI:
doi = provider.mint(context, item);
}
catch(IdentifierException e)
{
e.printStackTrace();
fail("Got an IdentifierException: " + e.getMessage());
}
assertNotNull("Minted DOI is null!", doi);
assertFalse("Minted DOI is empty!", doi.isEmpty());
try
{
DOI.formatIdentifier(doi);
}
catch (Exception e)
{
e.printStackTrace();
fail("Minted an unrecognizable DOI: " + e.getMessage());
}
}
@Test
public void testMint_returns_existing_DOI()
throws SQLException, AuthorizeException, IOException, IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, null, true);
String retrievedDOI = provider.mint(context, item);
assertNotNull("Minted DOI is null?!", retrievedDOI);
assertTrue("Mint did not returned an existing DOI!", doi.equals(retrievedDOI));
}
@Test
public void testReserve_DOI()
throws SQLException, SQLException, AuthorizeException, IOException,
IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, null, true);
provider.reserve(context, item, doi);
TableRow doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
assumeNotNull(doiRow);
assertTrue("Reservation of DOI did not set the corret DOI status.",
DOIIdentifierProvider.TO_BE_RESERVERED.intValue() == doiRow.getIntColumn("status"));
}
@Test
public void testRegister_unreserved_DOI()
throws SQLException, SQLException, AuthorizeException, IOException,
IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, null, true);
provider.register(context, item, doi);
TableRow doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
assumeNotNull(doiRow);
assertTrue("Registration of DOI did not set the corret DOI status.",
DOIIdentifierProvider.TO_BE_REGISTERED.intValue() == doiRow.getIntColumn("status"));
}
@Test
public void testRegister_reserved_DOI()
throws SQLException, SQLException, AuthorizeException, IOException,
IdentifierException
{
Item item = newItem(context);
String doi = this.createDOI(item, DOIIdentifierProvider.IS_RESERVED, true);
provider.register(context, item, doi);
TableRow doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
assumeNotNull(doiRow);
assertTrue("Registration of DOI did not set the corret DOI status.",
DOIIdentifierProvider.TO_BE_REGISTERED.intValue() == doiRow.getIntColumn("status"));
}
@Test
public void testCreate_and_Register_DOI()
throws SQLException, SQLException, AuthorizeException, IOException,
IdentifierException
{
Item item = newItem(context);
String doi = provider.register(context, item);
// we want the created DOI to be returned in the following format:
// doi:10.<prefix>/<suffix>.
String formated_doi = DOI.formatIdentifier(doi);
assertTrue("DOI was not in the expected format!", doi.equals(formated_doi));
TableRow doiRow = DatabaseManager.findByUnique(context, "Doi", "doi",
doi.substring(DOI.SCHEME.length()));
assertNotNull("Created DOI was not stored in database.", doiRow);
assertTrue("Registration of DOI did not set the corret DOI status.",
DOIIdentifierProvider.TO_BE_REGISTERED.intValue() == doiRow.getIntColumn("status"));
}
@Test
public void testDelete_specified_DOI()
throws SQLException, AuthorizeException, IOException, IdentifierException
{
Item item = newItem(context);
String doi1 = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, true);
String doi2 = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, true);
// remove one of it
provider.delete(context, item, doi1);
// assure that the right one was removed
DCValue[] metadata = item.getMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
boolean foundDOI1 = false;
boolean foundDOI2 = false;
for (DCValue id : metadata)
{
if (id.value.equals(DOI.DOIToExternalForm(doi1)))
{
foundDOI1 = true;
}
if (id.value.equals(DOI.DOIToExternalForm(doi2)))
{
foundDOI2 = true;
}
}
assertFalse("Cannot remove DOI from item metadata.", foundDOI1);
assertTrue("Removed wrong DOI from item metadata.", foundDOI2);
TableRow doiRow1 = DatabaseManager.findByUnique(context, "Doi", "doi",
doi1.substring(DOI.SCHEME.length()));
assumeNotNull(doiRow1);
assertTrue("Status of deleted DOI was not set correctly.",
DOIIdentifierProvider.TO_BE_DELETED.intValue() == doiRow1.getIntColumn("status"));
TableRow doiRow2 = DatabaseManager.findByUnique(context, "Doi", "doi",
doi2.substring(DOI.SCHEME.length()));
assumeNotNull(doiRow2);
assertTrue("While deleting a DOI the status of another changed.",
DOIIdentifierProvider.IS_REGISTERED.intValue() == doiRow2.getIntColumn("status"));
}
@Test
public void testDelete_all_DOIs()
throws SQLException, AuthorizeException, IOException, IdentifierException
{
Item item = newItem(context);
String doi1 = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, true);
String doi2 = this.createDOI(item, DOIIdentifierProvider.IS_REGISTERED, true);
// remove one of it
provider.delete(context, item);
// assure that the right one was removed
DCValue[] metadata = item.getMetadata(DOIIdentifierProvider.MD_SCHEMA,
DOIIdentifierProvider.DOI_ELEMENT,
DOIIdentifierProvider.DOI_QUALIFIER,
null);
boolean foundDOI1 = false;
boolean foundDOI2 = false;
for (DCValue id : metadata)
{
if (id.value.equals(DOI.DOIToExternalForm(doi1)))
{
foundDOI1 = true;
}
if (id.value.equals(DOI.DOIToExternalForm(doi2)))
{
foundDOI2 = true;
}
}
assertFalse("Cannot remove DOI from item metadata.", foundDOI1);
assertFalse("Did not removed all DOIs from item metadata.", foundDOI2);
TableRow doiRow1 = DatabaseManager.findByUnique(context, "Doi", "doi",
doi1.substring(DOI.SCHEME.length()));
assumeNotNull(doiRow1);
assertTrue("Status of deleted DOI was not set correctly.",
DOIIdentifierProvider.TO_BE_DELETED.intValue() == doiRow1.getIntColumn("status"));
TableRow doiRow2 = DatabaseManager.findByUnique(context, "Doi", "doi",
doi1.substring(DOI.SCHEME.length()));
assumeNotNull(doiRow2);
assertTrue("Did not set the status of all deleted DOIs as expected.",
DOIIdentifierProvider.TO_BE_DELETED.intValue() == doiRow2.getIntColumn("status"));
}
// test the following methods using the MockDOIConnector.
// updateMetadataOnline
// registerOnline
// reserveOnline
}

View File

@@ -0,0 +1,171 @@
/**
* 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.identifier;
import java.util.HashMap;
import java.util.Map;
import mockit.Mock;
import mockit.MockUp;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
/**
*
* @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de)
*/
public class MockDOIConnector
extends MockUp<DOIConnector>
implements org.dspace.identifier.doi.DOIConnector
{
public Map<String, Integer> reserved;
public Map<String, Integer> registered;
public MockDOIConnector()
{
reserved = new HashMap<String, Integer>();
registered = new HashMap<String, Integer>();
}
public void reset()
{
reserved.clear();
registered.clear();
}
@Override
@Mock
public boolean isDOIReserved(Context context, String doi)
throws DOIIdentifierException
{
return reserved.containsKey(doi);
}
@Override
@Mock
public boolean isDOIReserved(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException
{
if (null == doi)
{
throw new NullPointerException();
}
Integer itemId = reserved.get(doi);
return (itemId != null && itemId.intValue() == dso.getID()) ? true : false;
}
@Override
@Mock
public boolean isDOIRegistered(Context context, String doi)
throws DOIIdentifierException
{
return registered.containsKey(doi);
}
@Override
@Mock
public boolean isDOIRegistered(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException
{
if (null == doi)
{
throw new NullPointerException();
}
Integer itemId = registered.get(doi);
return (itemId != null && itemId.intValue() == dso.getID()) ? true : false;
}
@Override
@Mock
public void deleteDOI(Context context, String doi)
throws DOIIdentifierException
{
if (reserved.remove(doi) == null)
{
throw new DOIIdentifierException("Trying to delete a DOI that was "
+ "never reserved!", DOIIdentifierException.DOI_DOES_NOT_EXIST);
}
registered.remove(doi);
}
@Override
@Mock
public void reserveDOI(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException
{
Integer itemId = reserved.get(doi);
if (null != itemId)
{
if (dso.getID() == itemId.intValue())
{
return;
}
else
{
throw new DOIIdentifierException("Trying to reserve a DOI that "
+ "is reserved for another object.",
DOIIdentifierException.MISMATCH);
}
}
reserved.put(doi, new Integer(dso.getID()));
}
@Override
@Mock
public void registerDOI(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException
{
if (!reserved.containsKey(doi))
{
throw new DOIIdentifierException("Trying to register an unreserverd "
+ "DOI.", DOIIdentifierException.REGISTER_FIRST);
}
if (reserved.get(doi).intValue() != dso.getID())
{
throw new DOIIdentifierException("Trying to register a DOI that is"
+ " reserved for another item.", DOIIdentifierException.MISMATCH);
}
if (registered.containsKey(doi))
{
if (registered.get(doi).intValue() == dso.getID())
{
return;
}
else
{
throw new DOIIdentifierException("Trying to register a DOI that "
+ "is registered for another item.",
DOIIdentifierException.MISMATCH);
}
}
registered.put(doi, new Integer(dso.getID()));
}
@Override
@Mock
public void updateMetadata(Context context, DSpaceObject dso, String doi)
throws DOIIdentifierException
{
if (!reserved.containsKey(doi))
{
throw new DOIIdentifierException("Trying to update a DOI that is not "
+ "registered!", DOIIdentifierException.DOI_DOES_NOT_EXIST);
}
if (reserved.get(doi).intValue() != dso.getID())
{
throw new DOIIdentifierException("Trying to update metadata of an "
+ "unreserved DOI.", DOIIdentifierException.DOI_DOES_NOT_EXIST);
}
}
}

View File

@@ -0,0 +1,454 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Document : DIM2DataCite.xsl
Created on : January 23, 2013, 1:26 PM
Author : pbecker, ffuerste
Description: Converts metadata from DSpace Intermediat Format (DIM) into
metadata following the DataCite Schema for the Publication and
Citation of Research Data, Version 2.2
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dspace="http://www.dspace.org/xmlns/dspace/dim"
xmlns="http://datacite.org/schema/kernel-2.2"
version="1.0">
<!-- CONFIGURATION -->
<!-- The content of the following variable will be used as element publisher. -->
<xsl:variable name="publisher">My University</xsl:variable>
<!-- The content of the following variable will be used as element contributor with contributorType datamanager. -->
<xsl:variable name="datamanager"><xsl:value-of select="$publisher" /></xsl:variable>
<!-- The content of the following variable will be used as element contributor with contributorType hostingInstitution. -->
<xsl:variable name="hostinginstitution"><xsl:value-of select="$publisher" /></xsl:variable>
<!-- Please take a look into the DataCite schema documentation if you want to know how to use these elements.
http://schema.datacite.org -->
<!-- DO NOT CHANGE ANYTHING BELOW THIS LINE EXCEPT YOU REALLY KNOW WHAT YOU ARE DOING! -->
<xsl:output method="xml" indent="yes" encoding="utf-8" />
<!-- Don't copy everything by default! -->
<xsl:template match="@* | text()" />
<xsl:template match="/dspace:dim[@dspaceType='ITEM']">
<!--
org.dspace.identifier.doi.DataCiteConnector uses this XSLT to
transform metadata for the DataCite metadata store. This crosswalk
should only be used, when it is ensured that all mandatory
properties are in the metadata of the item to export.
The classe named above respects this.
-->
<resource xmlns="http://datacite.org/schema/kernel-2.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://datacite.org/schema/kernel-2.2 http://schema.datacite.org/meta/kernel-2.2/metadata.xsd">
<!--
MANDATORY PROPERTIES
-->
<!--
DataCite (1)
Template Call for DOI identifier.
-->
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='identifier' and starts-with(., 'http://dx.doi.org/')]" />
<!--
DataCite (2)
Add creator information.
-->
<creators>
<xsl:choose>
<xsl:when test="//dspace:field[@mdschema='dc' and @element='contributor' and @qualifier='author']">
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='contributor' and @qualifier='author']" />
</xsl:when>
<xsl:otherwise>
<creator>
<creatorName>(:unkn) unknown</creatorName>
</creator>
</xsl:otherwise>
</xsl:choose>
</creators>
<!--
DataCite (3)
Add Title information.
-->
<titles>
<xsl:choose>
<xsl:when test="//dspace:field[@mdschema='dc' and @element='title']">
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='title']" />
</xsl:when>
<xsl:otherwise>
<title>(:unas) unassigned</title>
</xsl:otherwise>
</xsl:choose>
</titles>
<!--
DataCite (4)
Add Publisher information from configuration above
-->
<publisher>
<xsl:value-of select="$publisher" />
</publisher>
<!--
DataCite (5)
Add PublicationYear information
-->
<publicationYear>
<xsl:choose>
<xsl:when test="//dspace:field[@mdschema='dc' and @element='date' and @qualifier='issued']">
<xsl:value-of select="substring(//dspace:field[@mdschema='dc' and @element='date' and @qualifier='issued'], 1, 4)" />
</xsl:when>
<xsl:when test="//dspace:field[@mdschema='dc' and @element='date' and @qualifier='available']">
<xsl:value-of select="substring(//dspace:field[@mdschema='dc' and @element='date' and @qualifier='issued'], 1, 4)" />
</xsl:when>
<xsl:when test="//dspace:field[@mdschema='dc' and @element='date']">
<xsl:value-of select="substring(//dspace:field[@mdschema='dc' and @element='date'], 1, 4)" />
</xsl:when>
<xsl:otherwise>0000</xsl:otherwise>
</xsl:choose>
</publicationYear>
<!--
OPTIONAL PROPERTIES
-->
<!--
DataCite (6)
Template Call for subjects.
-->
<xsl:if test="//dspace:field[@mdschema='dc' and @element='subject']">
<subjects>
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='subject']" />
</subjects>
</xsl:if>
<!--
DataCite (7)
Add contributorType from configuration above.
Template Call for Contributors
-->
<contributors>
<xsl:element name="contributor">
<xsl:attribute name="contributorType">DataManager</xsl:attribute>
<xsl:element name="contributorName">
<xsl:value-of select="$datamanager"/>
</xsl:element>
</xsl:element>
<xsl:element name="contributor">
<xsl:attribute name="contributorType">HostingInstitution</xsl:attribute>
<contributorName>
<xsl:value-of select="$hostinginstitution" />
</contributorName>
</xsl:element>
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='contributor'][not(@qualifier='author')]" />
</contributors>
<!--
DataCite (8)
Template Call for Dates
-->
<xsl:if test="//dspace:field[@mdschema='dc' and @element='date']" >
<dates>
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='date']" />
</dates>
</xsl:if>
<!-- Add language(s). -->
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='language' and (@qualifier='iso' or @qualifier='rfc3066')]" />
<!-- Add resource type. -->
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='type']" />
<!--
Add alternativeIdentifiers.
This element is important as it is used to recognize for which
DSpace object a DOI is reserved for. See below for further
information.
-->
<xsl:if test="//dspace:field[@mdschema='dc' and @element='identifier' and not(starts-with(., 'http://dx.doi.org/'))]">
<xsl:element name="alternateIdentifiers">
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='identifier' and not(starts-with(., 'http://dx.doi.org/'))]" />
</xsl:element>
</xsl:if>
<!-- Add sizes. -->
<!--
<xsl:if test="//dspace:field[@mdschema='dc' and @element='format' and @qualifier='extent']">
<sizes>
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='format' and @qualifier='extent']" />
</sizes>
</xsl:if>
-->
<!-- Add formats. -->
<!--
<xsl:if test="//dspace:field[@mdschema='dc' and @element='format']">
<formats>
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='format']" />
</formats>
</xsl:if>
-->
<!-- Add version. -->
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='description' and @qualifier='provenance']" />
<!-- Add rights. -->
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='rights']" />
<!-- Add descriptions. -->
<xsl:if test="//dspace:field[@mdschema='dc' and @element='description'][not(@qualifier='provenance')]">
<xsl:element name="descriptions">
<xsl:apply-templates select="//dspace:field[@mdschema='dc' and @element='description'][not(@qualifier='provenance')]" />
</xsl:element>
</xsl:if>
</resource>
</xsl:template>
<!-- Add doi identifier information. -->
<xsl:template match="dspace:field[@mdschema='dc' and @element='identifier' and starts-with(., 'http://dx.doi.org/')]">
<identifier identifierType="DOI">
<xsl:value-of select="substring(., 19)"/>
</identifier>
</xsl:template>
<!-- DataCite (2) :: Creator -->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='contributor' and @qualifier='author']">
<creator>
<creatorName>
<xsl:value-of select="." />
</creatorName>
</creator>
</xsl:template>
<!-- DataCite (3) :: Title -->
<xsl:template match="dspace:field[@mdschema='dc' and @element='title']">
<xsl:element name="title">
<xsl:if test="@qualifier='alternative'">
<xsl:attribute name="titleType">AlternativeTitle</xsl:attribute>
</xsl:if>
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<!--
DataCite (6), DataCite (6.1)
Adds subject and subjectScheme information
"This term is intended to be used with non-literal values as defined in the
DCMI Abstract Model (http://dublincore.org/documents/abstract-model/).
As of December 2007, the DCMI Usage Board is seeking a way to express
this intention with a formal range declaration."
(http://dublincore.org/documents/dcmi-terms/#terms-subject)
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='subject']">
<xsl:element name="subject">
<xsl:if test="@qualifier">
<xsl:attribute name="subjectScheme"><xsl:value-of select="@qualifier" /></xsl:attribute>
</xsl:if>
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<!--
DataCite (7), DataCite (7.1)
Adds contributor and contributorType information
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='contributor'][not(@qualifier='author')]">
<xsl:if test="@qualifier='editor'">
<xsl:element name="contributor">
<xsl:attribute name="contributorType">Editor</xsl:attribute>
<contributorName>
<xsl:value-of select="." />
</contributorName>
</xsl:element>
</xsl:if>
</xsl:template>
<!--
DataCite (8), DataCite (8.1)
Adds Date and dateType information
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='date']">
<xsl:element name="date">
<xsl:if test="@qualifier='accessioned'">
<xsl:attribute name="dateType">Issued</xsl:attribute>
</xsl:if>
<xsl:if test="@qualifier='submitted'">
<xsl:attribute name="dateType">Issued</xsl:attribute>
</xsl:if>
<!-- part of DublinCore DSpace to mapping but not part of DSpace default fields
<xsl:if test="@qualifier='dateAccepted'">
<xsl:attribute name="dateType">Issued</xsl:attribute>
</xsl:if>
-->
<xsl:if test="@qualifier='issued'">
<xsl:attribute name="dateType">Issued</xsl:attribute>
</xsl:if>
<xsl:if test="@qualifier='available'">
<xsl:attribute name="dateType">Available</xsl:attribute>
</xsl:if>
<xsl:if test="@qualifier='copyright'">
<xsl:attribute name="dateType">Copyrighted</xsl:attribute>
</xsl:if>
<xsl:if test="@qualifier='created'">
<xsl:attribute name="dateType">Created</xsl:attribute>
</xsl:if>
<xsl:if test="@qualifier='updated'">
<xsl:attribute name="dateType">Updated</xsl:attribute>
</xsl:if>
<xsl:value-of select="substring(., 1, 10)" />
</xsl:element>
</xsl:template>
<!--
DataCite (9)
Adds Language information
Transforming the language flags according to ISO 639-2/B & ISO 639-3
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='language' and (@qualifier='iso' or @qualifier='rfc3066')]">
<xsl:for-each select=".">
<xsl:element name="language">
<xsl:choose>
<xsl:when test="contains(string(text()), '_')">
<xsl:value-of select="translate(string(text()), '_', '-')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="string(text())"/>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</xsl:template>
<!--
DataCite (10), DataCite (10.1)
Adds resourceType and resourceTypeGeneral information
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='type']">
<xsl:for-each select=".">
<!-- Transforming the language flags according to ISO 639-2/B & ISO 639-3 -->
<xsl:element name="resourceType">
<xsl:attribute name="resourceTypeGeneral">
<xsl:choose>
<xsl:when test="string(text())='Animation'">Image</xsl:when>
<xsl:when test="string(text())='Article'">Text</xsl:when>
<xsl:when test="string(text())='Book'">Text</xsl:when>
<xsl:when test="string(text())='Book chapter'">Text</xsl:when>
<xsl:when test="string(text())='Dataset'">Dataset</xsl:when>
<xsl:when test="string(text())='Learning Object'">InteractiveResource</xsl:when>
<xsl:when test="string(text())='Image'">Image</xsl:when>
<xsl:when test="string(text())='Image, 3-D'">Image</xsl:when>
<xsl:when test="string(text())='Map'">Image</xsl:when>
<xsl:when test="string(text())='Musical Score'">Sound</xsl:when>
<xsl:when test="string(text())='Plan or blueprint'">Image</xsl:when>
<xsl:when test="string(text())='Preprint'">Text</xsl:when>
<xsl:when test="string(text())='Presentation'">Image</xsl:when>
<xsl:when test="string(text())='Recording, acoustical'">Sound</xsl:when>
<xsl:when test="string(text())='Recording, musical'">Sound</xsl:when>
<xsl:when test="string(text())='Recording, oral'">Sound</xsl:when>
<xsl:when test="string(text())='Software'">Software</xsl:when>
<xsl:when test="string(text())='Technical Report'">Text</xsl:when>
<xsl:when test="string(text())='Thesis'">Text</xsl:when>
<xsl:when test="string(text())='Video'">Film</xsl:when>
<xsl:when test="string(text())='Working Paper'">Text</xsl:when>
<!-- FIXME -->
<xsl:when test="string(text())='Other'">Collection</xsl:when>
<!-- FIXME -->
<xsl:otherwise>Collection</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</xsl:template>
<!--
DataCite (11), DataCite (11.1)
Adds AlternativeIdentifier and alternativeIdentifierType information
Adds all identifiers except the doi.
This element is important as it is used to recognize for which DSpace
objet a DOI is reserved for. The DataCiteConnector will test all
AlternativeIdentifiers by using HandleManager.
resolveUrlToHandle(context, altId) until one is recognized or all have
been tested.
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='identifier' and not(starts-with(., 'http://dx.doi.org/'))]">
<xsl:element name="alternateIdentifier">
<xsl:if test="@qualifier">
<xsl:attribute name="alternateIdentifierType"><xsl:value-of select="@qualifier" /></xsl:attribute>
</xsl:if>
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<!--
DataCite (12), DataCite (12.1)
Adds RelatedIdentifier and relatedIdentifierType information
-->
<!--
DataCite (13)
Adds Size information
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='format' and @qualifier='extent']">
<xsl:element name="format">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<!--
DataCite (14)
Adds Format information
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='format']">
<xsl:element name="format">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<!--
DataCite (15)
Adds Version information
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='description' and @qualifier='provenance']">
<xsl:if test="contains(text(),'Made available')">
<xsl:element name="version">
<xsl:value-of select="." />
</xsl:element>
</xsl:if>
</xsl:template>
<!--
DataCite (16)
Adds Rights information
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='rights']">
<xsl:element name="rights">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<!--
DataCite (17)
Description
-->
<xsl:template match="//dspace:field[@mdschema='dc' and @element='description'][not(@qualifier='provenance')]">
<xsl:element name="description">
<xsl:attribute name="descriptionType">
<xsl:choose>
<xsl:when test="@qualifier='abstract'">Abstract</xsl:when>
<xsl:otherwise>Other</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
</xsl:stylesheet>

View File

@@ -233,6 +233,25 @@ log.dir = ${dspace.dir}/log
# an X-Forward header. If it finds it, it will use this for the user IP address
#useProxies = true
##### DOI registration agency credentials ######
# To mint DOIs you have to use a DOI registration agency like DataCite. Several
# DataCite members offers services as DOI registration agency, so f.e. EZID or
# TIB Hannover. To mint DOIs with DSpace you have to get an agreement with an
# DOI registration agency. You have to edit
# [dspace]/config/spring/api/identifier-service.xml and to configure the following
# properties.
# Credentials used to authenticate against the registration agency:
identifier.doi.user = username
identifier.doi.password = password
# DOI prefix used to mint DOIs. All DOIs minted by DSpace will use this prefix.
# The Prefix will be assinged by the registration agency.
identifier.doi.prefix = 10.5072
# If you want to, you can further separate your namespace. Should all the suffix
# of all DOIs minted by DSpace start with a special string to separate it from
# other services also minting DOIs under your prefix?
identifier.doi.namespaceseparator = dspace/
##### Search settings #####
# Where to put search index files
@@ -477,6 +496,15 @@ crosswalk.dissemination.marc.schemaLocation = \
http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
crosswalk.dissemination.marc.preferList = true
##
## Configure XSLT-driven submission crosswalk for DataCite
##
crosswalk.dissemination.DataCite.stylesheet = crosswalks/DIM2DataCite.xsl
crosswalk.dissemination.DataCite.schemaLocation = \
http://datacite.org/schema/kernel-2.2 \
http://schema.datacite.org/meta/kernel-2.2/metadata.xsd
crosswalk.dissemination.DataCite.preferList = false
# Crosswalk Plugin Configuration:
# The purpose of Crosswalks is to translate an external metadata format to/from
# the DSpace Internal Metadata format (DIM) or the DSpace Database.
@@ -648,9 +676,15 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher
# as the SOLR implementation rely on the discovery consumer
#
# event.dispatcher.default.consumers = versioning, browse, discovery, eperson, harvester
#
# uncomment event.consumer.doi.class and event.consumer.doi.filters below and add doi here
# if you want to send metadata updates to your doi registration agency.
event.dispatcher.default.consumers = versioning, discovery, eperson, harvester
# event.dispatcher.default.consumers = versioning, discovery, eperson, harvester, doi
# The noindex dispatcher will not create search or browse indexes (useful for batch item imports)
event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher
event.dispatcher.noindex.consumers = eperson
@@ -675,6 +709,10 @@ event.consumer.eperson.filters = EPerson+Create
event.consumer.harvester.class = org.dspace.harvest.HarvestConsumer
event.consumer.harvester.filters = Item+Delete
# consumer to update metadata of DOIs
#event.consumer.doi.class = org.dspace.identifier.doi.DOIConsumer
#event.consumer.doi.filters = Item+Modify_Metadata
# test consumer for debugging and monitoring
#event.consumer.test.class = org.dspace.event.TestConsumer
#event.consumer.test.filters = All+All

View File

@@ -0,0 +1,18 @@
# E-mail sent to designated address when a metadata update, registration
# or reserveration of a doi fails
#
# Parameters: {0} action (updating metadata of, registering or reserving)
# {1} Date & Time
# {2} resource type text
# {3} resource id
# {4} doi
# {5} reason
#
# See org.dspace.core.Email for information on the format of this file.
#
Subject: DSpace: Error {0} DOI {3}
Date: {1}
{0} DOI {4} for {2} with ID {3} failed:
{5}

View File

@@ -199,6 +199,14 @@
</step>
</command>
<command>
<name>doi-organiser</name>
<description>Run the DOI organiser</description>
<step>
<class>org.dspace.identifier.doi.DOIOrganiser</class>
</step>
</command>
<command>
<name>packager</name>
<description>Execute a packager</description>

View File

@@ -21,11 +21,48 @@
autowire="byType"
scope="singleton"/>
<!-- provider for using the versioned handle identifier instead of the default one. -->
<!--<bean id="org.dspace.identifier.HandleIdentifierProvider" class="org.dspace.identifier.VersionedHandleIdentifierProvider"-->
<!--scope="singleton">-->
<!--<property name="configurationService" ref="org.dspace.services.ConfigurationService"/>-->
<!--</bean>-->
<!-- provider to mint and register DOIs with DSpace.
To mint DOIs you need a registration agency. The DOIIdentifierProvider
maintains the doi database table and handling of DSpaceObject. It uses
a DOIConnector that handle all API calls to your DOI registration
agency. Please configure a DOIConnector as well!-->
<!-- To mint DOIs with DSpace get an agreement with a DOI registration
agency, take a look into dspace.cfg, and remove this comment
<bean id="org.dspace.identifier.DOIIdentifierProvider"
class="org.dspace.identifier.DOIIdentifierProvider"
scope="singleton">
<property name="configurationService"
ref="org.dspace.services.ConfigurationService" />
<property name="DOIConnector"
ref="org.dspace.identifier.doi.DOIConnector" />
</bean>
-->
<!-- The DOIConnector will handle the API calls to your DOI registration
agency for the DOIIdentifierProvider. If your registration agency
tells you to use DataCites API directly you can use the
DataCiteConnector. If your registration agency is not part of DataCite
or provides their own API you have to implement a DOIConnector.
EZID f.e. is part of DataCite but provides their own APIs. The following
DataCiteConnector won't work if EZID is your registration agency.
-->
<!-- Remove this comment to use DataCite API directly as DOIConnector.
<bean id="org.dspace.identifier.doi.DOIConnector"
class="org.dspace.identifier.doi.DataCiteConnector"
scope="singleton">
<property name='DATACITE_SCHEME' value='https'/>
<property name='DATACITE_HOST' value='test.datacite.org'/>
<property name='DATACITE_DOI_PATH' value='/mds/doi/' />
<property name='DATACITE_METADATA_PATH' value='/mds/metadata/' />
<property name='disseminationCrosswalkName' value="DataCite" />
</bean>
-->
</beans>

View File

@@ -64,6 +64,7 @@ DROP TABLE TasklistItem;
DROP TABLE WorkflowItem;
DROP TABLE WorkspaceItem;
DROP TABLE Handle;
DROP TABLE Doi;
DROP TABLE EPersonGroup2EPerson;
DROP TABLE ResourcePolicy;
DROP TABLE Collection2Item;
@@ -114,6 +115,7 @@ DROP SEQUENCE collection2item_seq;
DROP SEQUENCE resourcepolicy_seq;
DROP SEQUENCE epersongroup2eperson_seq;
DROP SEQUENCE handle_seq;
DROP SEQUENCE doi_seq;
DROP SEQUENCE workspaceitem_seq;
DROP SEQUENCE workflowitem_seq;
DROP SEQUENCE tasklistitem_seq;

View File

@@ -102,6 +102,7 @@ CREATE SEQUENCE collection2item_seq;
CREATE SEQUENCE resourcepolicy_seq;
CREATE SEQUENCE epersongroup2eperson_seq;
CREATE SEQUENCE handle_seq;
CREATE SEQUENCE doi_seq;
CREATE SEQUENCE workspaceitem_seq;
CREATE SEQUENCE workflowitem_seq;
CREATE SEQUENCE tasklistitem_seq;
@@ -492,6 +493,23 @@ CREATE INDEX handle_handle_idx ON Handle(handle);
-- index by resource id and resource type id
CREATE INDEX handle_resource_id_and_type_idx ON handle(resource_id, resource_type_id);
-------------------------------------------------------
-- Doi table
-------------------------------------------------------
CREATE TABLE Doi
(
doi_id INTEGER PRIMARY KEY,
doi VARCHAR(256),
resource_type_id INTEGER,
resource_id INTEGER,
status INTEGER
);
-- index by handle, commonly looked up
CREATE INDEX doi_doi_idx ON Doi(doi);
-- index by resource id and resource type id
CREATE INDEX doi_resource_id_and_type_idx ON Doi(resource_id, resource_type_id);
-------------------------------------------------------
-- WorkspaceItem table
-------------------------------------------------------

View File

@@ -55,6 +55,7 @@ CREATE SEQUENCE collection2item_seq;
CREATE SEQUENCE resourcepolicy_seq;
CREATE SEQUENCE epersongroup2eperson_seq;
CREATE SEQUENCE handle_seq;
CREATE SEQUENCE doi_seq;
CREATE SEQUENCE workspaceitem_seq;
CREATE SEQUENCE workflowitem_seq;
CREATE SEQUENCE tasklistitem_seq;
@@ -444,6 +445,21 @@ CREATE TABLE Handle
-- index by resource id and resource type id
CREATE INDEX handle_resource_id_type_idx ON handle(resource_id, resource_type_id);
-------------------------------------------------------
-- Doi table
-------------------------------------------------------
CREATE TABLE Doi
(
doi_id INTEGER PRIMARY KEY,
doi VARCHAR2(256) UNIQUE,
resource_type_id INTEGER,
resource_id INTEGER,
status INTEGER
);
-- index by resource id and resource type id
CREATE INDEX doi_resource_id_type_idx ON doi(resource_id, resource_type_id);
-------------------------------------------------------
-- WorkspaceItem table
-------------------------------------------------------

View File

@@ -21,6 +21,23 @@
-- DUMP YOUR DATABASE FIRST. DUMP YOUR DATABASE FIRST. DUMP YOUR DATABASE FIRST. DUMP YOUR DATABASE FIRST.
--
-------------------------------------------
-- Add support for DOIs (table and seq.) --
-------------------------------------------
CREATE SEQUENCE doi_seq;
CREATE TABLE Doi
(
doi_id INTEGER PRIMARY KEY,
doi VARCHAR2(256) UNIQUE,
resource_type_id INTEGER,
resource_id INTEGER,
status INTEGER
);
-- index by resource id and resource type id
CREATE INDEX doi_resource_id_type_idx ON doi(resource_id, resource_type_id);
-------------------------------------------
-- Table of running web applications for 'dspace version' --
-------------------------------------------

View File

@@ -93,6 +93,7 @@ CREATE SEQUENCE collection2item_seq;
CREATE SEQUENCE resourcepolicy_seq;
CREATE SEQUENCE epersongroup2eperson_seq;
CREATE SEQUENCE handle_seq;
CREATE SEQUENCE doi_seq;
CREATE SEQUENCE workspaceitem_seq;
CREATE SEQUENCE workflowitem_seq;
CREATE SEQUENCE tasklistitem_seq;
@@ -485,6 +486,23 @@ CREATE INDEX handle_handle_idx ON Handle(handle);
-- index by resource id and resource type id
CREATE INDEX handle_resource_id_and_type_idx ON handle(resource_id, resource_type_id);
-------------------------------------------------------
-- Doi table
-------------------------------------------------------
CREATE TABLE Doi
(
doi_id INTEGER PRIMARY KEY,
doi VARCHAR(256) UNIQUE,
resource_type_id INTEGER,
resource_id INTEGER,
status INTEGER
);
-- index by handle, commonly looked up
CREATE INDEX doi_doi_idx ON Doi(doi);
-- index by resource id and resource type id
CREATE INDEX doi_resource_id_and_type_idx ON Doi(resource_id, resource_type_id);
-------------------------------------------------------
-- WorkspaceItem table
-------------------------------------------------------

View File

@@ -21,6 +21,26 @@
-- DUMP YOUR DATABASE FIRST. DUMP YOUR DATABASE FIRST. DUMP YOUR DATABASE FIRST. DUMP YOUR DATABASE FIRST.
--
-------------------------------------------
-- Add support for DOIs (table and seq.) --
-------------------------------------------
CREATE SEQUENCE doi_seq;
CREATE TABLE Doi
(
doi_id INTEGER PRIMARY KEY,
doi VARCHAR(256) UNIQUE,
resource_type_id INTEGER,
resource_id INTEGER,
status INTEGER
);
-- index by handle, commonly looked up
CREATE INDEX doi_doi_idx ON Doi(doi);
-- index by resource id and resource type id
CREATE INDEX doi_resource_id_and_type_idx ON Doi(resource_id, resource_type_id);
-------------------------------------------
-- New columns and longer hash for salted password hashing DS-861 --
-------------------------------------------