/**
* 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.*;
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.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
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.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
import org.dspace.identifier.DOI;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.factory.IdentifierServiceFactory;
import org.dspace.identifier.service.DOIService;
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;
protected HandleService handleService;
protected ItemService itemService;
protected DOIService doiService;
public DOIOrganiser(Context context, DOIIdentifierProvider provider)
{
this.context = context;
this.provider = provider;
this.quiet = false;
this.handleService = HandleServiceFactory.getInstance().getHandleService();
this.itemService = ContentServiceFactory.getInstance().getItemService();
this.doiService = IdentifierServiceFactory.getInstance().getDOIService();
}
public static void main(String[] args)
{
LOG.debug("Starting DOI organiser ");
// setup Context
Context context = new Context();
// 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_RESERVED);
organiser.list("registration", null, null, DOIIdentifierProvider.TO_BE_REGISTERED);
organiser.list("update", null, null,
DOIIdentifierProvider.UPDATE_BEFORE_REGISTRATION,
DOIIdentifierProvider.UPDATE_REGISTERED,
DOIIdentifierProvider.UPDATE_RESERVED);
organiser.list("deletion", null, null, DOIIdentifierProvider.TO_BE_DELETED);
}
DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService();
if (line.hasOption('s'))
{
try {
List<DOI> dois = doiService.getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_RESERVED));
if (0 == dois.size())
{
System.err.println("There are no objects in the database "
+ "that could be reserved.");
}
for (DOI doi : dois) {
organiser.reserve(doi);
}
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
}
}
if (line.hasOption('r'))
{
try {
List<DOI> dois = doiService.getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_REGISTERED));
if (0 == dois.size())
{
System.err.println("There are no objects in the database "
+ "that could be registered.");
}
for (DOI doi : dois)
{
organiser.register(doi);
}
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
}
}
if (line.hasOption('u'))
{
try {
List<DOI> dois = doiService.getDOIsByStatus(context, Arrays.asList(
DOIIdentifierProvider.UPDATE_BEFORE_REGISTRATION,
DOIIdentifierProvider.UPDATE_RESERVED,
DOIIdentifierProvider.UPDATE_REGISTERED));
if (0 == dois.size())
{
System.err.println("There are no objects in the database "
+ "whose metadata needs an update.");
}
for (DOI doi : dois)
{
organiser.update(doi);
}
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
}
}
if (line.hasOption('d'))
{
try {
List<DOI> dois = doiService.getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_DELETED));
if (0 == dois.size())
{
System.err.println("There are no objects in the database "
+ "that could be deleted.");
}
Iterator<DOI> iterator = dois.iterator();
while (iterator.hasNext()) {
DOI doi = iterator.next();
iterator.remove();
organiser.delete(doi.getDoi());
}
} 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 {
DOI doiRow = organiser.resolveToDOI(identifier);
organiser.reserve(doiRow);
}
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 {
DOI doiRow = organiser.resolveToDOI(identifier);
organiser.register(doiRow);
} 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("update-doi");
if(null == identifier)
{
helpformater.printHelp("\nDOI organiser\n", options);
}
else
{
try {
DOI doiRow = organiser.resolveToDOI(identifier);
organiser.update(doiRow);
} 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("delete-doi");
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 void list(String processName, PrintStream out, PrintStream err, Integer ... status)
{
String indent = " ";
if (null == out)
{
out = System.out;
}
if (null == err)
{
err = System.err;
}
try
{
List<DOI> doiList = doiService.getDOIsByStatus(context, Arrays.asList(status));
if (0 < doiList.size())
{
out.println("DOIs queued for " + processName + ": ");
}
else
{
out.println("There are no DOIs queued for " + processName + ".");
}
for (DOI doiRow : doiList)
{
out.print(indent + DOI.SCHEME + doiRow.getDoi());
DSpaceObject dso = doiRow.getDSpaceObject();
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);
}
}
public void register(DOI doiRow) throws SQLException
{
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType())
{
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
}
try {
provider.registerOnline(context, dso,
DOI.SCHEME + doiRow.getDoi());
if(!quiet)
{
System.out.println("This identifier: "
+ DOI.SCHEME + doiRow.getDoi()
+ " is successfully registered.");
}
}
catch (IdentifierException ex)
{
if (!(ex instanceof DOIIdentifierException))
{
LOG.error("It wasn't possible to register this identifier: "
+ DOI.SCHEME + doiRow.getDoi()
+ " online. ", ex);
}
DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex;
try
{
sendAlertMail("Register", dso,
DOI.SCHEME + doiRow.getDoi(),
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.getDoi()
+ " online. Exceptions code: "
+ doiIdentifierException
.codeToString(doiIdentifierException.getCode()), ex);
if(!quiet)
{
System.err.println("It wasn't possible to register this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
}
}
catch (IllegalArgumentException ex)
{
LOG.error("Database table DOI contains a DOI that is not valid: "
+ DOI.SCHEME + doiRow.getDoi() + "!", ex);
if(!quiet)
{
System.err.println("It wasn't possible to register this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
}
throw new IllegalStateException("Database table DOI contains a DOI "
+ " that is not valid: "
+ DOI.SCHEME + doiRow.getDoi() + "!", 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.getDoi());
}
throw new RuntimeException("Error while trying to get data from database", ex);
}
}
public void reserve(DOI doiRow) throws SQLException
{
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType())
{
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
}
try
{
provider.reserveOnline(context, dso,
DOI.SCHEME + doiRow.getDoi());
if(!quiet)
{
System.out.println("This identifier : "
+ DOI.SCHEME + doiRow.getDoi()
+ " is successfully reserved.");
}
}
catch (IdentifierException ex)
{
if (!(ex instanceof DOIIdentifierException))
{
LOG.error("It wasn't possible to register this identifier : "
+ DOI.SCHEME + doiRow.getDoi()
+ " online. ",ex);
}
DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex;
try
{
sendAlertMail("Reserve", dso,
DOI.SCHEME + doiRow.getDoi(),
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.getDoi());
}
}
catch (IllegalArgumentException ex)
{
LOG.error("Database table DOI contains a DOI that is not valid: "
+ DOI.SCHEME + doiRow.getDoi() + "!", ex);
if(!quiet)
{
System.err.println("It wasn't possible to reserve this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
}
throw new IllegalStateException("Database table DOI contains a DOI "
+ " that is not valid: "
+ DOI.SCHEME + doiRow.getDoi() + "!", 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.getDoi());
}
throw new RuntimeException("Error while trying to get data from database", ex);
}
}
public void update(DOI doiRow)
{
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType())
{
throw new IllegalArgumentException("Currenty DSpace supports DOIs "
+ "for Items only.");
}
try
{
provider.updateMetadataOnline(context, dso,
DOI.SCHEME + doiRow.getDoi());
if(!quiet)
{
System.out.println("Successfully updated metadata of DOI " + DOI.SCHEME
+ doiRow.getDoi() + ".");
}
}
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.getDoi(),
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.getDoi()
+ " Exceptions code: "
+ doiIdentifierException
.codeToString(doiIdentifierException.getCode()), ex);
if(!quiet)
{
System.err.println("It wasn't possible to update this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
}
}
catch (IllegalArgumentException ex)
{
LOG.error("Database table DOI contains a DOI that is not valid: "
+ DOI.SCHEME + doiRow.getDoi() + "!", ex);
if(!quiet)
{
System.err.println("It wasn't possible to update this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
}
throw new IllegalStateException("Database table DOI contains a DOI "
+ " that is not valid: "
+ DOI.SCHEME + doiRow.getDoi() + "!", 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;
DOI doiRow = null;
try
{
doi = doiService.formatIdentifier(identifier);
// If there's no exception: we found a valid DOI. :)
doiRow = doiService.findByDoi(context,
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.getDoi()
+ " 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.getDoi()
+ " 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 if database error
* @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.
* @throws IdentifierException if identifier error
*/
public DOI resolveToDOI(String identifier)
throws SQLException, IllegalArgumentException, IllegalStateException, IdentifierException
{
if (null == identifier || identifier.isEmpty())
{
throw new IllegalArgumentException("Identifier is null or empty.");
}
DOI doiRow = null;
String doi = null;
// detect it identifer is ItemID, handle or DOI.
// try to detect ItemID
if (identifier.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"))
{
DSpaceObject dso = itemService.find(context, UUID.fromString(identifier));
if (null != dso)
{
doiRow = doiService.findDOIByDSpaceObject(context, dso);
//Check if this Item has an Identifier, mint one if it doesn't
if (null == doiRow)
{
doi = provider.mint(context, dso);
doiRow = doiService.findByDoi(context,
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 = handleService.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 = doiService.findDOIByDSpaceObject(context, dso);
if (null == doiRow)
{
doi = provider.mint(context, dso);
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
}
return doiRow;
}
// detect DOI
try {
doi = doiService.formatIdentifier(identifier);
// If there's no exception: we found a valid DOI. :)
doiRow = doiService.findByDoi(context,
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.getDoi());
}
}
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(ContentServiceFactory.getInstance().getDSpaceObjectService(dso).getTypeText(dso));
email.addArgument(dso.getID().toString());
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;
}
}