/** * 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; } }