mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 10:04:21 +00:00
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:
@@ -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>
|
||||||
* <foo><mimetype>application/pdf</mimetype></foo>
|
* <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>
|
||||||
* <foo>
|
* <foo>
|
||||||
* <bar>val1</bar>
|
* <bar>val1</bar>
|
||||||
* <bar>val2</bar>
|
* <bar>val2</bar>
|
||||||
* </foo>
|
* </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>.
|
||||||
@@ -295,4 +351,4 @@ public class RegistryLoader {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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,
|
||||||
|
@@ -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) {
|
||||||
|
Reference in New Issue
Block a user