/**
* 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 org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.core.Context;
import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
import org.dspace.services.ConfigurationService;
import org.dspace.versioning.Version;
import org.dspace.versioning.VersionHistory;
import org.dspace.versioning.service.VersionHistoryService;
import org.dspace.versioning.service.VersioningService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Marsa Haoua
* @author Pascal-Nicolas Becker (dspace at pascal dash becker dot de)
*/
public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider
{
/** log4j category */
private static Logger log = Logger.getLogger(VersionedDOIIdentifierProvider.class);
protected DOIConnector connector;
static final char DOT = '.';
protected static final String pattern = "\\d+\\" + String.valueOf(DOT) +"\\d+";
@Autowired(required = true)
protected VersioningService versioningService;
@Autowired(required = true)
protected VersionHistoryService versionHistoryService;
@Override
public String mint(Context context, DSpaceObject dso)
throws IdentifierException
{
if (!(dso instanceof Item))
{
throw new IdentifierException("Currently only Items are supported for DOIs.");
}
Item item = (Item) dso;
VersionHistory history = null;
try {
history = versionHistoryService.findByItem(context, item);
} catch (SQLException ex) {
throw new RuntimeException("A problem occured while accessing the database.", ex);
}
String doi = null;
try
{
doi = getDOIByObject(context, dso);
if (doi != null)
{
return doi;
}
}
catch (SQLException ex)
{
log.error("Error while attemping to retrieve information about a DOI for "
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso)
+ " with ID " + dso.getID() + ".", ex);
throw new RuntimeException("Error while attempting to retrieve "
+ "information about a DOI for "
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso)
+ " with ID " + dso.getID() + ".", ex);
}
// check whether we have a DOI in the metadata and if we have to remove it
String metadataDOI = getDOIOutOfObject(dso);
if (metadataDOI != null)
{
// check whether doi and version number matches
String bareDOI = getBareDOI(metadataDOI);
int versionNumber;
try {
versionNumber = versionHistoryService.getVersion(context, history, item).getVersionNumber();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
String versionedDOI = bareDOI;
if (versionNumber > 1)
{
versionedDOI = bareDOI
.concat(String.valueOf(DOT))
.concat(String.valueOf(versionNumber));
}
if (!metadataDOI.equalsIgnoreCase(versionedDOI))
{
log.debug("Will remove DOI " + metadataDOI
+ " from item metadata, as it should become " + versionedDOI + ".");
// remove old versioned DOIs
try {
removePreviousVersionDOIsOutOfObject(context, item, metadataDOI);
} catch (AuthorizeException ex) {
throw new RuntimeException("Trying to remove an old DOI from a versioned item, but wasn't authorized to.", ex);
}
} else {
log.debug("DOI " + doi + " matches version number " + versionNumber + ".");
// ensure DOI exists in our database as well and return.
// this also checks that the doi is not assigned to another dso already.
try {
loadOrCreateDOI(context, dso, versionedDOI);
} catch (SQLException ex) {
log.error("A problem with the database connection occurd while processing DOI " + versionedDOI + ".", ex);
throw new RuntimeException("A problem with the database connection occured.", ex);
}
return versionedDOI;
}
}
try{
if(history != null)
{
// versioning is currently supported for items only
// if we have a history, we have a item
doi = makeIdentifierBasedOnHistory(context, dso, history);
} else {
doi = loadOrCreateDOI(context, dso, null).getDoi();
}
} catch(SQLException ex) {
log.error("SQLException while creating a new DOI: ", ex);
throw new IdentifierException(ex);
} catch (AuthorizeException ex) {
log.error("AuthorizationException while creating a new DOI: ", ex);
throw new IdentifierException(ex);
}
return doi;
}
@Override
public void register(Context context, DSpaceObject dso, String identifier)
throws IdentifierException
{
if (!(dso instanceof Item))
{
throw new IdentifierException("Currently only Items are supported for DOIs.");
}
Item item = (Item) dso;
if (StringUtils.isEmpty(identifier))
{
identifier = mint(context, dso);
}
String doiIdentifier = doiService.formatIdentifier(identifier);
DOI doi = null;
// search DOI in our db
try
{
doi = loadOrCreateDOI(context, dso, doiIdentifier);
} catch (SQLException ex) {
log.error("Error in databse connection: " + ex.getMessage(), ex);
throw new RuntimeException("Error in database conncetion.", ex);
}
if (DELETED.equals(doi.getStatus()) ||
TO_BE_DELETED.equals(doi.getStatus()))
{
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.equals(doi.getStatus()))
{
return;
}
String metadataDOI = getDOIOutOfObject(dso);
if (!StringUtils.isEmpty(metadataDOI)
&& !metadataDOI.equalsIgnoreCase(doiIdentifier))
{
// remove doi of older version from the metadata
try {
removePreviousVersionDOIsOutOfObject(context, item, metadataDOI);
} catch (AuthorizeException ex) {
throw new RuntimeException("Trying to remove an old DOI from a versioned item, but wasn't authorized to.", ex);
}
}
// change status of DOI
doi.setStatus(TO_BE_REGISTERED);
try {
doiService.update(context, doi);
}
catch (SQLException ex)
{
log.warn("SQLException while changing status of DOI {} to be registered.", ex);
throw new RuntimeException(ex);
}
}
protected String getBareDOI(String identifier)
throws DOIIdentifierException
{
doiService.formatIdentifier(identifier);
String doiPrefix = DOI.SCHEME.concat(getPrefix())
.concat(String.valueOf(SLASH))
.concat(getNamespaceSeparator());
String doiPostfix = identifier.substring(doiPrefix.length());
if (doiPostfix.matches(pattern) && doiPostfix.lastIndexOf(DOT) != -1)
{
return doiPrefix.concat(doiPostfix.substring(0, doiPostfix.lastIndexOf(DOT)));
}
// if the pattern does not match, we are already working on a bare handle.
return identifier;
}
protected String getDOIPostfix(String identifier)
throws DOIIdentifierException{
String doiPrefix = DOI.SCHEME.concat(getPrefix()).concat(String.valueOf(SLASH)).concat(getNamespaceSeparator());
String doiPostfix = null;
if(null != identifier){
doiPostfix = identifier.substring(doiPrefix.length());
}
return doiPostfix;
}
// Should never return null!
protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history)
throws AuthorizeException, SQLException, DOIIdentifierException
{
// Mint foreach new version an identifier like: 12345/100.versionNumber
// use the bare handle (g.e. 12345/100) for the first version.
// currently versioning is supported for items only
if (!(dso instanceof Item))
{
throw new IllegalArgumentException("Cannot create versioned handle for objects other then item: Currently versioning supports items only.");
}
Item item = (Item)dso;
Version version = versionHistoryService.getVersion(context, history, item);
String previousVersionDOI = null;
for (Version v : versioningService.getVersionsByHistory(context, history))
{
previousVersionDOI = getDOIByObject(context, v.getItem());
if (null != previousVersionDOI)
{
break;
}
}
if (previousVersionDOI == null)
{
// We need to generate a new DOI.
DOI doi = doiService.create(context);
// as we reuse the DOI ID, we do not have to check whether the DOI exists already.
String identifier = this.getPrefix() + "/" + this.getNamespaceSeparator() +
doi.getID().toString();
if (version.getVersionNumber() > 1)
{
identifier.concat(String.valueOf(DOT).concat(String.valueOf(version.getVersionNumber())));
}
doi.setDoi(identifier);
doi.setDSpaceObject(dso);
doi.setStatus(null);
doiService.update(context, doi);
return doi.getDoi();
}
assert(previousVersionDOI != null);
String identifier = getBareDOI(previousVersionDOI);
if (version.getVersionNumber() > 1)
{
identifier = identifier.concat(String.valueOf(DOT)).concat(String.valueOf(versionHistoryService.getVersion(context, history, item).getVersionNumber()));
}
loadOrCreateDOI(context, dso, identifier);
return identifier;
}
void removePreviousVersionDOIsOutOfObject(Context c, Item item, String oldDoi)
throws IdentifierException, AuthorizeException
{
if (StringUtils.isEmpty(oldDoi))
{
throw new IllegalArgumentException("Old DOI must be neither empty nor null!");
}
String bareDoi = getBareDOI(doiService.formatIdentifier(oldDoi));
String bareDoiRef = doiService.DOIToExternalForm(bareDoi);
List<MetadataValue> identifiers = itemService.getMetadata(item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, Item.ANY);
// We have to remove all DOIs referencing previous versions. To do that,
// we store all identifiers we do not know in an array list, clear
// dc.identifier.uri and add the safed identifiers.
// The list of identifiers to safe won't get larger then the number of
// existing identifiers.
ArrayList<String> newIdentifiers = new ArrayList<String>(identifiers.size());
boolean changed = false;
for (MetadataValue identifier : identifiers)
{
if (!StringUtils.startsWithIgnoreCase(identifier.getValue(), bareDoiRef))
{
newIdentifiers.add(identifier.getValue());
} else {
changed = true;
}
}
// reset the metadata if neccessary.
if (changed)
{
try
{
itemService.clearMetadata(c, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, Item.ANY);
itemService.addMetadata(c, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, newIdentifiers);
itemService.update(c, item);
} catch (SQLException ex) {
throw new RuntimeException("A problem with the database connection occured.", ex);
}
}
}
@Required
public void setDOIConnector(DOIConnector connector)
{
super.setDOIConnector(connector);
this.connector = connector;
}
@Required
public void setConfigurationService(ConfigurationService configurationService) {
super.setConfigurationService(configurationService);
this.configurationService = configurationService;
}
}