Merge pull request #10673 from DSpace/backport-10640-to-dspace-7_x

[Port dspace-7_x] Improve CLI RegistryLoader, InitializeEntities, Curation commands
This commit is contained in:
Tim Donohue
2025-04-29 15:37:28 -05:00
committed by GitHub
3 changed files with 192 additions and 51 deletions

View File

@@ -21,6 +21,13 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.BitstreamFormat; import org.dspace.content.BitstreamFormat;
@@ -41,7 +48,7 @@ import org.xml.sax.SAXException;
* <P> * <P>
* <code>RegistryLoader -bitstream bitstream-formats.xml</code> * <code>RegistryLoader -bitstream bitstream-formats.xml</code>
* <P> * <P>
* <code>RegistryLoader -dc dc-types.xml</code> * <code>RegistryLoader -metadata dc-types.xml</code>
* *
* @author Robert Tansley * @author Robert Tansley
* @version $Revision$ * @version $Revision$
@@ -50,7 +57,7 @@ public class RegistryLoader {
/** /**
* log4j category * log4j category
*/ */
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistryLoader.class); private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistryLoader.class);
protected static BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance() protected static BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance()
.getBitstreamFormatService(); .getBitstreamFormatService();
@@ -67,50 +74,99 @@ public class RegistryLoader {
* @throws Exception if error * @throws Exception if error
*/ */
public static void main(String[] argv) throws Exception { public static void main(String[] argv) throws Exception {
String usage = "Usage: " + RegistryLoader.class.getName() // Set up command-line options and parse arguments
+ " (-bitstream | -metadata) registry-file.xml"; CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions();
Context context = null;
try { try {
context = new Context(); CommandLine line = parser.parse(options, argv);
// Check if help option was entered or no options provided
if (line.hasOption('h') || line.getOptions().length == 0) {
printHelp(options);
System.exit(0);
}
Context context = new Context();
// Can't update registries anonymously, so we need to turn off // Can't update registries anonymously, so we need to turn off
// authorisation // authorisation
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
// Work out what we're loading try {
if (argv[0].equalsIgnoreCase("-bitstream")) { // Work out what we're loading
RegistryLoader.loadBitstreamFormats(context, argv[1]); if (line.hasOption('b')) {
} else if (argv[0].equalsIgnoreCase("-metadata")) { String filename = line.getOptionValue('b');
// Call MetadataImporter, as it handles Metadata schema updates if (StringUtils.isEmpty(filename)) {
MetadataImporter.loadRegistry(argv[1], true); System.err.println("No file path provided for bitstream format registry");
} else { printHelp(options);
System.err.println(usage); System.exit(1);
}
RegistryLoader.loadBitstreamFormats(context, filename);
} else if (line.hasOption('m')) {
String filename = line.getOptionValue('m');
if (StringUtils.isEmpty(filename)) {
System.err.println("No file path provided for metadata registry");
printHelp(options);
System.exit(1);
}
// Call MetadataImporter, as it handles Metadata schema updates
MetadataImporter.loadRegistry(filename, true);
} else {
System.err.println("No registry type specified");
printHelp(options);
System.exit(1);
}
// Commit changes and close Context
context.complete();
System.exit(0);
} catch (Exception e) {
log.fatal(LogHelper.getHeader(context, "error_loading_registries", ""), e);
System.err.println("Error: \n - " + e.getMessage());
System.exit(1);
} finally {
// Clean up our context, if it still exists & it was never completed
if (context != null && context.isValid()) {
context.abort();
}
} }
} catch (ParseException e) {
// Commit changes and close Context System.err.println("Error parsing command-line arguments: " + e.getMessage());
context.complete(); printHelp(options);
System.exit(0);
} catch (ArrayIndexOutOfBoundsException ae) {
System.err.println(usage);
System.exit(1); System.exit(1);
} catch (Exception e) {
log.fatal(LogHelper.getHeader(context, "error_loading_registries",
""), e);
System.err.println("Error: \n - " + e.getMessage());
System.exit(1);
} finally {
// Clean up our context, if it still exists & it was never completed
if (context != null && context.isValid()) {
context.abort();
}
} }
} }
/**
* Create the command-line options
* @return the command-line options
*/
private static Options createCommandLineOptions() {
Options options = new Options();
options.addOption("b", "bitstream", true, "load bitstream format registry from specified file");
options.addOption("m", "metadata", true, "load metadata registry from specified file");
options.addOption("h", "help", false, "print this help message");
return options;
}
/**
* Print the help message
* @param options the command-line options
*/
private static void printHelp(Options options) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("RegistryLoader",
"Load bitstream format or metadata registries into the database\n",
options,
"\nExamples:\n" +
" RegistryLoader -b bitstream-formats.xml\n" +
" RegistryLoader -m dc-types.xml",
true);
}
/** /**
* Load Bitstream Format metadata * Load Bitstream Format metadata
* *
@@ -221,7 +277,7 @@ public class RegistryLoader {
* contains: * contains:
* <P> * <P>
* <code> * <code>
* &lt;foo&gt;&lt;mimetype&gt;application/pdf&lt;/mimetype&gt;&lt;/foo&gt; * <foo><mimetype>application/pdf</mimetype></foo>
* </code> * </code>
* passing this the <code>foo</code> node and <code>mimetype</code> will * passing this the <code>foo</code> node and <code>mimetype</code> will
* return <code>application/pdf</code>. * return <code>application/pdf</code>.
@@ -262,10 +318,10 @@ public class RegistryLoader {
* document contains: * document contains:
* <P> * <P>
* <code> * <code>
* &lt;foo&gt; * <foo>
* &lt;bar&gt;val1&lt;/bar&gt; * <bar>val1</bar>
* &lt;bar&gt;val2&lt;/bar&gt; * <bar>val2</bar>
* &lt;/foo&gt; * </foo>
* </code> * </code>
* passing this the <code>foo</code> node and <code>bar</code> will * passing this the <code>foo</code> node and <code>bar</code> will
* return <code>val1</code> and <code>val2</code>. * return <code>val1</code> and <code>val2</code>.

View File

@@ -64,20 +64,36 @@ public class InitializeEntities {
*/ */
public static void main(String[] argv) throws SQLException, AuthorizeException, ParseException { public static void main(String[] argv) throws SQLException, AuthorizeException, ParseException {
InitializeEntities initializeEntities = new InitializeEntities(); InitializeEntities initializeEntities = new InitializeEntities();
// Set up command-line options and parse arguments
CommandLineParser parser = new DefaultParser(); CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions(); Options options = createCommandLineOptions();
CommandLine line = parser.parse(options,argv); CommandLine line = parser.parse(options,argv);
String fileLocation = getFileLocationFromCommandLine(line); // First of all, check if the help option was entered or a required argument is missing
checkHelpEntered(options, line); checkHelpEntered(options, line);
// Get the file location from the command line
String fileLocation = getFileLocationFromCommandLine(line);
// Run the script
initializeEntities.run(fileLocation); initializeEntities.run(fileLocation);
} }
/**
* Check if the help option was entered or a required argument is missing. If so, print help and exit.
* @param options the defined command-line options
* @param line the parsed command-line arguments
*/
private static void checkHelpEntered(Options options, CommandLine line) { private static void checkHelpEntered(Options options, CommandLine line) {
if (line.hasOption("h")) { if (line.hasOption("h") || !line.hasOption("f")) {
HelpFormatter formatter = new HelpFormatter(); HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("Intialize Entities", options); formatter.printHelp("Intialize Entities", options);
System.exit(0); System.exit(0);
} }
} }
/**
* Get the file path from the command-line argument. Exits with exit code 1 if no file argument was entered.
* @param line the parsed command-line arguments
* @return the file path
*/
private static String getFileLocationFromCommandLine(CommandLine line) { private static String getFileLocationFromCommandLine(CommandLine line) {
String query = line.getOptionValue("f"); String query = line.getOptionValue("f");
if (StringUtils.isEmpty(query)) { if (StringUtils.isEmpty(query)) {
@@ -88,13 +104,25 @@ public class InitializeEntities {
return query; return query;
} }
/**
* Create the command-line options
* @return the command-line options
*/
protected static Options createCommandLineOptions() { protected static Options createCommandLineOptions() {
Options options = new Options(); Options options = new Options();
options.addOption("f", "file", true, "the location for the file containing the xml data"); options.addOption("f", "file", true, "the path to the file containing the " +
"relationship definitions (e.g. ${dspace.dir}/config/entities/relationship-types.xml)");
options.addOption("h", "help", false, "print this message");
return options; return options;
} }
/**
* Run the script for the given file location
* @param fileLocation the file location
* @throws SQLException If something goes wrong initializing context or inserting relationship types
* @throws AuthorizeException If the script user fails to authorize while inserting relationship types
*/
private void run(String fileLocation) throws SQLException, AuthorizeException { private void run(String fileLocation) throws SQLException, AuthorizeException {
Context context = new Context(); Context context = new Context();
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
@@ -102,6 +130,12 @@ public class InitializeEntities {
context.complete(); context.complete();
} }
/**
* Parse the XML file at fileLocation to create relationship types in the database
* @param context DSpace context
* @param fileLocation the full or relative file path to the relationship types XML
* @throws AuthorizeException If the script user fails to authorize while inserting relationship types
*/
private void parseXMLToRelations(Context context, String fileLocation) throws AuthorizeException { private void parseXMLToRelations(Context context, String fileLocation) throws AuthorizeException {
try { try {
File fXmlFile = new File(fileLocation); File fXmlFile = new File(fileLocation);
@@ -158,15 +192,15 @@ public class InitializeEntities {
for (int j = 0; j < leftCardinalityList.getLength(); j++) { for (int j = 0; j < leftCardinalityList.getLength(); j++) {
Node node = leftCardinalityList.item(j); Node node = leftCardinalityList.item(j);
leftCardinalityMin = getString(leftCardinalityMin,(Element) node, "min"); leftCardinalityMin = getCardinalityMinString(leftCardinalityMin,(Element) node, "min");
leftCardinalityMax = getString(leftCardinalityMax,(Element) node, "max"); leftCardinalityMax = getCardinalityMinString(leftCardinalityMax,(Element) node, "max");
} }
for (int j = 0; j < rightCardinalityList.getLength(); j++) { for (int j = 0; j < rightCardinalityList.getLength(); j++) {
Node node = rightCardinalityList.item(j); Node node = rightCardinalityList.item(j);
rightCardinalityMin = getString(rightCardinalityMin,(Element) node, "min"); rightCardinalityMin = getCardinalityMinString(rightCardinalityMin,(Element) node, "min");
rightCardinalityMax = getString(rightCardinalityMax,(Element) node, "max"); rightCardinalityMax = getCardinalityMinString(rightCardinalityMax,(Element) node, "max");
} }
populateRelationshipType(context, leftType, rightType, leftwardType, rightwardType, populateRelationshipType(context, leftType, rightType, leftwardType, rightwardType,
@@ -182,13 +216,39 @@ public class InitializeEntities {
} }
} }
private String getString(String leftCardinalityMin,Element node, String minOrMax) { /**
* Extract the min or max value for the left or right cardinality from the node text content
* @param leftCardinalityMin current left cardinality min
* @param node node to extract the min or max value from
* @param minOrMax element tag name to parse
* @return final left cardinality min
*/
private String getCardinalityMinString(String leftCardinalityMin, Element node, String minOrMax) {
if (node.getElementsByTagName(minOrMax).getLength() > 0) { if (node.getElementsByTagName(minOrMax).getLength() > 0) {
leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent(); leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent();
} }
return leftCardinalityMin; return leftCardinalityMin;
} }
/**
* Populate the relationship type based on values parsed from the XML relationship types configuration
*
* @param context DSpace context
* @param leftType left relationship type (e.g. "Publication").
* @param rightType right relationship type (e.g. "Journal").
* @param leftwardType leftward relationship type (e.g. "isAuthorOfPublication").
* @param rightwardType rightward relationship type (e.g. "isPublicationOfAuthor").
* @param leftCardinalityMin left cardinality min
* @param leftCardinalityMax left cardinality max
* @param rightCardinalityMin right cardinality min
* @param rightCardinalityMax right cardinality max
* @param copyToLeft copy metadata values to left if right side is deleted
* @param copyToRight copy metadata values to right if left side is deleted
* @param tilted set a tilted relationship side (left or right) if there are many relationships going one way
* to help performance (e.g. authors with 1000s of publications)
* @throws SQLException if database error occurs while saving the relationship type
* @throws AuthorizeException if authorization error occurs while saving the relationship type
*/
private void populateRelationshipType(Context context, String leftType, String rightType, String leftwardType, private void populateRelationshipType(Context context, String leftType, String rightType, String leftwardType,
String rightwardType, String leftCardinalityMin, String leftCardinalityMax, String rightwardType, String leftCardinalityMin, String leftCardinalityMax,
String rightCardinalityMin, String rightCardinalityMax, String rightCardinalityMin, String rightCardinalityMax,

View File

@@ -24,6 +24,8 @@ import java.util.UUID;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.io.output.NullOutputStream;
import org.dspace.app.util.DSpaceObjectUtilsImpl;
import org.dspace.app.util.service.DSpaceObjectUtils;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.factory.ContentServiceFactory;
@@ -35,6 +37,7 @@ import org.dspace.eperson.service.EPersonService;
import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService; import org.dspace.handle.service.HandleService;
import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.DSpaceRunnable;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace; import org.dspace.utils.DSpace;
/** /**
@@ -45,7 +48,9 @@ import org.dspace.utils.DSpace;
public class Curation extends DSpaceRunnable<CurationScriptConfiguration> { public class Curation extends DSpaceRunnable<CurationScriptConfiguration> {
protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
protected DSpaceObjectUtils dspaceObjectUtils = DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName(DSpaceObjectUtilsImpl.class.getName(), DSpaceObjectUtilsImpl.class);
HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
protected Context context; protected Context context;
private CurationClientOptions curationClientOptions; private CurationClientOptions curationClientOptions;
@@ -345,9 +350,29 @@ public class Curation extends DSpaceRunnable<CurationScriptConfiguration> {
if (this.commandLine.hasOption('i')) { if (this.commandLine.hasOption('i')) {
this.id = this.commandLine.getOptionValue('i').toLowerCase(); this.id = this.commandLine.getOptionValue('i').toLowerCase();
DSpaceObject dso;
if (!this.id.equalsIgnoreCase("all")) { if (!this.id.equalsIgnoreCase("all")) {
HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); // First, try to parse the id as a UUID. If that fails, treat it as a handle.
DSpaceObject dso; UUID uuid = null;
try {
uuid = UUID.fromString(id);
} catch (Exception e) {
// It's not a UUID, proceed to treat it as a handle.
}
if (uuid != null) {
try {
dso = dspaceObjectUtils.findDSpaceObject(context, uuid);
if (dso != null) {
// We already resolved an object, return early
return;
}
} catch (SQLException e) {
String error = "SQLException trying to find dso with uuid " + uuid;
super.handler.logError(error);
throw new RuntimeException(error, e);
}
}
// If we get here, the id is not a UUID, so we assume it's a handle.
try { try {
dso = handleService.resolveToObject(this.context, id); dso = handleService.resolveToObject(this.context, id);
} catch (SQLException e) { } catch (SQLException e) {