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.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.dspace.authorize.AuthorizeException;
import org.dspace.content.BitstreamFormat;
@@ -41,7 +48,7 @@ import org.xml.sax.SAXException;
* <P>
* <code>RegistryLoader -bitstream bitstream-formats.xml</code>
* <P>
* <code>RegistryLoader -dc dc-types.xml</code>
* <code>RegistryLoader -metadata dc-types.xml</code>
*
* @author Robert Tansley
* @version $Revision$
@@ -50,7 +57,7 @@ public class RegistryLoader {
/**
* 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()
.getBitstreamFormatService();
@@ -67,40 +74,55 @@ public class RegistryLoader {
* @throws Exception if error
*/
public static void main(String[] argv) throws Exception {
String usage = "Usage: " + RegistryLoader.class.getName()
+ " (-bitstream | -metadata) registry-file.xml";
Context context = null;
// Set up command-line options and parse arguments
CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions();
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
// authorisation
context.turnOffAuthorisationSystem();
try {
// Work out what we're loading
if (argv[0].equalsIgnoreCase("-bitstream")) {
RegistryLoader.loadBitstreamFormats(context, argv[1]);
} else if (argv[0].equalsIgnoreCase("-metadata")) {
if (line.hasOption('b')) {
String filename = line.getOptionValue('b');
if (StringUtils.isEmpty(filename)) {
System.err.println("No file path provided for bitstream format registry");
printHelp(options);
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(argv[1], true);
MetadataImporter.loadRegistry(filename, true);
} else {
System.err.println(usage);
System.err.println("No registry type specified");
printHelp(options);
System.exit(1);
}
// Commit changes and close Context
context.complete();
System.exit(0);
} catch (ArrayIndexOutOfBoundsException ae) {
System.err.println(usage);
System.exit(1);
} catch (Exception e) {
log.fatal(LogHelper.getHeader(context, "error_loading_registries",
""), e);
log.fatal(LogHelper.getHeader(context, "error_loading_registries", ""), e);
System.err.println("Error: \n - " + e.getMessage());
System.exit(1);
} finally {
@@ -109,6 +131,40 @@ public class RegistryLoader {
context.abort();
}
}
} catch (ParseException e) {
System.err.println("Error parsing command-line arguments: " + e.getMessage());
printHelp(options);
System.exit(1);
}
}
/**
* 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);
}
/**
@@ -221,7 +277,7 @@ public class RegistryLoader {
* contains:
* <P>
* <code>
* &lt;foo&gt;&lt;mimetype&gt;application/pdf&lt;/mimetype&gt;&lt;/foo&gt;
* <foo><mimetype>application/pdf</mimetype></foo>
* </code>
* passing this the <code>foo</code> node and <code>mimetype</code> will
* return <code>application/pdf</code>.
@@ -262,10 +318,10 @@ public class RegistryLoader {
* document contains:
* <P>
* <code>
* &lt;foo&gt;
* &lt;bar&gt;val1&lt;/bar&gt;
* &lt;bar&gt;val2&lt;/bar&gt;
* &lt;/foo&gt;
* <foo>
* <bar>val1</bar>
* <bar>val2</bar>
* </foo>
* </code>
* passing this the <code>foo</code> node and <code>bar</code> will
* 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 {
InitializeEntities initializeEntities = new InitializeEntities();
// Set up command-line options and parse arguments
CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions();
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);
// Get the file location from the command line
String fileLocation = getFileLocationFromCommandLine(line);
// Run the script
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) {
if (line.hasOption("h")) {
if (line.hasOption("h") || !line.hasOption("f")) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("Intialize Entities", options);
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) {
String query = line.getOptionValue("f");
if (StringUtils.isEmpty(query)) {
@@ -88,13 +104,25 @@ public class InitializeEntities {
return query;
}
/**
* Create the command-line options
* @return the command-line options
*/
protected static Options createCommandLineOptions() {
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;
}
/**
* 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 {
Context context = new Context();
context.turnOffAuthorisationSystem();
@@ -102,6 +130,12 @@ public class InitializeEntities {
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 {
try {
File fXmlFile = new File(fileLocation);
@@ -158,15 +192,15 @@ public class InitializeEntities {
for (int j = 0; j < leftCardinalityList.getLength(); j++) {
Node node = leftCardinalityList.item(j);
leftCardinalityMin = getString(leftCardinalityMin,(Element) node, "min");
leftCardinalityMax = getString(leftCardinalityMax,(Element) node, "max");
leftCardinalityMin = getCardinalityMinString(leftCardinalityMin,(Element) node, "min");
leftCardinalityMax = getCardinalityMinString(leftCardinalityMax,(Element) node, "max");
}
for (int j = 0; j < rightCardinalityList.getLength(); j++) {
Node node = rightCardinalityList.item(j);
rightCardinalityMin = getString(rightCardinalityMin,(Element) node, "min");
rightCardinalityMax = getString(rightCardinalityMax,(Element) node, "max");
rightCardinalityMin = getCardinalityMinString(rightCardinalityMin,(Element) node, "min");
rightCardinalityMax = getCardinalityMinString(rightCardinalityMax,(Element) node, "max");
}
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) {
leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent();
}
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,
String rightwardType, String leftCardinalityMin, String leftCardinalityMax,
String rightCardinalityMin, String rightCardinalityMax,

View File

@@ -24,6 +24,8 @@ import java.util.UUID;
import org.apache.commons.cli.ParseException;
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.content.DSpaceObject;
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.service.HandleService;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
/**
@@ -45,7 +48,9 @@ import org.dspace.utils.DSpace;
public class Curation extends DSpaceRunnable<CurationScriptConfiguration> {
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;
private CurationClientOptions curationClientOptions;
@@ -345,9 +350,29 @@ public class Curation extends DSpaceRunnable<CurationScriptConfiguration> {
if (this.commandLine.hasOption('i')) {
this.id = this.commandLine.getOptionValue('i').toLowerCase();
if (!this.id.equalsIgnoreCase("all")) {
HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
DSpaceObject dso;
if (!this.id.equalsIgnoreCase("all")) {
// First, try to parse the id as a UUID. If that fails, treat it as a handle.
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 {
dso = handleService.resolveToObject(this.context, id);
} catch (SQLException e) {