mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 18:14:26 +00:00
401 lines
16 KiB
Java
401 lines
16 KiB
Java
/**
|
|
* 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.curate;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.PrintStream;
|
|
import java.io.Writer;
|
|
import java.sql.SQLException;
|
|
import java.time.Instant;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
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;
|
|
import org.dspace.core.Context;
|
|
import org.dspace.core.factory.CoreServiceFactory;
|
|
import org.dspace.eperson.EPerson;
|
|
import org.dspace.eperson.factory.EPersonServiceFactory;
|
|
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;
|
|
|
|
/**
|
|
* CurationCli provides command-line access to Curation tools and processes.
|
|
*
|
|
* @author richardrodgers
|
|
*/
|
|
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;
|
|
|
|
private String task;
|
|
private String taskFile;
|
|
private String id;
|
|
private String queue;
|
|
private String scope;
|
|
private String reporter;
|
|
private Map<String, String> parameters;
|
|
private boolean verbose;
|
|
|
|
@Override
|
|
public void internalRun() throws Exception {
|
|
if (curationClientOptions == CurationClientOptions.HELP) {
|
|
printHelp();
|
|
return;
|
|
}
|
|
|
|
Curator curator = initCurator();
|
|
|
|
// load curation tasks
|
|
if (curationClientOptions == CurationClientOptions.TASK) {
|
|
long start = Instant.now().toEpochMilli();
|
|
handleCurationTask(curator);
|
|
this.endScript(start);
|
|
}
|
|
|
|
// process task queue
|
|
if (curationClientOptions == CurationClientOptions.QUEUE) {
|
|
// process the task queue
|
|
TaskQueue taskQueue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService()
|
|
.getSinglePlugin(TaskQueue.class);
|
|
if (taskQueue == null) {
|
|
super.handler.logError("No implementation configured for queue");
|
|
throw new UnsupportedOperationException("No queue service available");
|
|
}
|
|
long timeRun = this.runQueue(taskQueue, curator);
|
|
this.endScript(timeRun);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Does the curation task (-t) or the task in the given file (-T).
|
|
* Checks:
|
|
* - if required option -i is missing.
|
|
* - if option -t has a valid task option
|
|
*/
|
|
private void handleCurationTask(Curator curator) throws IOException, SQLException {
|
|
String taskName;
|
|
if (commandLine.hasOption('t')) {
|
|
if (verbose) {
|
|
handler.logInfo("Adding task: " + this.task);
|
|
}
|
|
curator.addTask(this.task);
|
|
if (verbose && !curator.hasTask(this.task)) {
|
|
handler.logInfo("Task: " + this.task + " not resolved");
|
|
}
|
|
} else if (commandLine.hasOption('T')) {
|
|
// load taskFile
|
|
BufferedReader reader = null;
|
|
try {
|
|
reader = new BufferedReader(new FileReader(this.taskFile));
|
|
while ((taskName = reader.readLine()) != null) {
|
|
if (verbose) {
|
|
super.handler.logInfo("Adding task: " + taskName);
|
|
}
|
|
curator.addTask(taskName);
|
|
}
|
|
} finally {
|
|
if (reader != null) {
|
|
reader.close();
|
|
}
|
|
}
|
|
}
|
|
// run tasks against object
|
|
if (verbose) {
|
|
super.handler.logInfo("Starting curation");
|
|
super.handler.logInfo("Curating id: " + this.id);
|
|
}
|
|
if ("all".equals(this.id)) {
|
|
// run on whole Site
|
|
curator.curate(context,
|
|
ContentServiceFactory.getInstance().getSiteService().findSite(context).getHandle());
|
|
} else {
|
|
curator.curate(context, this.id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs task queue (-q set)
|
|
*
|
|
* @param queue The task queue
|
|
* @param curator The curator
|
|
* @return Time when queue started
|
|
*/
|
|
private long runQueue(TaskQueue queue, Curator curator) throws SQLException, AuthorizeException, IOException {
|
|
// use current time as our reader 'ticket'
|
|
long ticket = Instant.now().toEpochMilli();
|
|
Iterator<TaskQueueEntry> entryIter = queue.dequeue(this.queue, ticket).iterator();
|
|
while (entryIter.hasNext()) {
|
|
TaskQueueEntry entry = entryIter.next();
|
|
if (verbose) {
|
|
super.handler.logInfo("Curating id: " + entry.getObjectId());
|
|
}
|
|
curator.clear();
|
|
for (String taskName : entry.getTaskNames()) {
|
|
curator.addTask(taskName);
|
|
}
|
|
curator.curate(context, entry.getObjectId());
|
|
}
|
|
queue.release(this.queue, ticket, true);
|
|
return ticket;
|
|
}
|
|
|
|
/**
|
|
* End of curation script; logs script time if -v verbose is set
|
|
*
|
|
* @param timeRun Time script was started
|
|
* @throws SQLException If DSpace context can't complete
|
|
*/
|
|
private void endScript(long timeRun) throws SQLException {
|
|
context.complete();
|
|
if (verbose) {
|
|
long elapsed = Instant.now().toEpochMilli() - timeRun;
|
|
this.handler.logInfo("Ending curation. Elapsed time: " + elapsed);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the curator with command line variables
|
|
*
|
|
* @return Initialised curator
|
|
* @throws FileNotFoundException If file of command line variable -r reporter is not found
|
|
*/
|
|
private Curator initCurator() throws FileNotFoundException {
|
|
Curator curator = new Curator(handler);
|
|
OutputStream reporterStream;
|
|
if (null == this.reporter) {
|
|
reporterStream = NullOutputStream.NULL_OUTPUT_STREAM;
|
|
} else if ("-".equals(this.reporter)) {
|
|
reporterStream = System.out;
|
|
} else {
|
|
reporterStream = new PrintStream(this.reporter);
|
|
}
|
|
Writer reportWriter = new OutputStreamWriter(reporterStream);
|
|
curator.setReporter(reportWriter);
|
|
|
|
if (this.scope != null) {
|
|
Curator.TxScope txScope = Curator.TxScope.valueOf(this.scope.toUpperCase());
|
|
curator.setTransactionScope(txScope);
|
|
}
|
|
|
|
curator.addParameters(parameters);
|
|
// we are operating in batch mode, if anyone cares.
|
|
curator.setInvoked(Curator.Invoked.BATCH);
|
|
return curator;
|
|
}
|
|
|
|
@Override
|
|
public void printHelp() {
|
|
super.printHelp();
|
|
super.handler.logInfo("\nwhole repo: CurationCli -t estimate -i all");
|
|
super.handler.logInfo("single item: CurationCli -t generate -i itemId");
|
|
super.handler.logInfo("task queue: CurationCli -q monthly");
|
|
}
|
|
|
|
@Override
|
|
public CurationScriptConfiguration getScriptConfiguration() {
|
|
return new DSpace().getServiceManager().getServiceByName("curate", CurationScriptConfiguration.class);
|
|
}
|
|
|
|
@Override
|
|
public void setup() throws ParseException {
|
|
assignCurrentUserInContext();
|
|
this.curationClientOptions = CurationClientOptions.getClientOption(commandLine);
|
|
|
|
if (this.curationClientOptions != null) {
|
|
this.initGeneralLineOptionsAndCheckIfValid();
|
|
if (curationClientOptions == CurationClientOptions.TASK) {
|
|
this.initTaskLineOptionsAndCheckIfValid();
|
|
} else if (curationClientOptions == CurationClientOptions.QUEUE) {
|
|
this.queue = this.commandLine.getOptionValue('q');
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("[--help || --task|--taskfile <> -identifier <> || -queue <> ] must be" +
|
|
" specified");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method will assign the currentUser to the {@link Context} variable which is also created in this method.
|
|
* The instance of the method in this class will fetch the EPersonIdentifier from this class, this identifier
|
|
* was given to this class upon instantiation, it'll then be used to find the {@link EPerson} associated with it
|
|
* and this {@link EPerson} will be set as the currentUser of the created {@link Context}
|
|
* @throws ParseException If something went wrong with the retrieval of the EPerson Identifier
|
|
*/
|
|
protected void assignCurrentUserInContext() throws ParseException {
|
|
UUID currentUserUuid = this.getEpersonIdentifier();
|
|
try {
|
|
this.context = new Context(Context.Mode.BATCH_EDIT);
|
|
EPerson eperson = ePersonService.find(context, currentUserUuid);
|
|
if (eperson == null) {
|
|
super.handler.logError("EPerson not found: " + currentUserUuid);
|
|
throw new IllegalArgumentException("Unable to find a user with uuid: " + currentUserUuid);
|
|
}
|
|
assignSpecialGroupsInContext();
|
|
this.context.setCurrentUser(eperson);
|
|
} catch (SQLException e) {
|
|
handler.handleException("Something went wrong trying to fetch eperson for uuid: " + currentUserUuid, e);
|
|
}
|
|
}
|
|
|
|
protected void assignSpecialGroupsInContext() throws SQLException {
|
|
for (UUID uuid : handler.getSpecialGroups()) {
|
|
context.setSpecialGroup(uuid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fills in some optional command line options.
|
|
* Checks if there are missing required options or invalid values for options.
|
|
*/
|
|
private void initGeneralLineOptionsAndCheckIfValid() {
|
|
// report file
|
|
if (this.commandLine.hasOption('r')) {
|
|
this.reporter = this.commandLine.getOptionValue('r');
|
|
}
|
|
|
|
// parameters
|
|
this.parameters = new HashMap<>();
|
|
if (this.commandLine.hasOption('p')) {
|
|
for (String parameter : this.commandLine.getOptionValues('p')) {
|
|
String[] parts = parameter.split("=", 2);
|
|
String name = parts[0].trim();
|
|
String value;
|
|
if (parts.length > 1) {
|
|
value = parts[1].trim();
|
|
} else {
|
|
value = "true";
|
|
}
|
|
this.parameters.put(name, value);
|
|
}
|
|
}
|
|
|
|
// verbose
|
|
verbose = false;
|
|
if (commandLine.hasOption('v')) {
|
|
verbose = true;
|
|
}
|
|
|
|
// scope
|
|
if (this.commandLine.getOptionValue('s') != null) {
|
|
this.scope = this.commandLine.getOptionValue('s');
|
|
boolean knownScope;
|
|
try {
|
|
Curator.TxScope.valueOf(this.scope.toUpperCase());
|
|
knownScope = true;
|
|
} catch (IllegalArgumentException | NullPointerException e) {
|
|
knownScope = false;
|
|
}
|
|
if (!knownScope) {
|
|
this.handler.logError("Bad transaction scope '"
|
|
+ this.scope
|
|
+ "': only 'object', 'curation' or 'open' recognized");
|
|
throw new IllegalArgumentException(
|
|
"Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " +
|
|
"'open' recognized");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fills in required command line options for the task or taskFile option.
|
|
* Checks if there are is a missing required -i option and if -i is either 'all' or a valid dso handle.
|
|
* Checks if -t task has a valid task option.
|
|
* Checks if -T taskfile is a valid file.
|
|
*/
|
|
private void initTaskLineOptionsAndCheckIfValid() {
|
|
// task or taskFile
|
|
if (this.commandLine.hasOption('t')) {
|
|
this.task = this.commandLine.getOptionValue('t');
|
|
if (!CurationClientOptions.getTaskOptions().contains(this.task)) {
|
|
super.handler
|
|
.logError("-t task must be one of: " + CurationClientOptions.getTaskOptions());
|
|
throw new IllegalArgumentException(
|
|
"-t task must be one of: " + CurationClientOptions.getTaskOptions());
|
|
}
|
|
} else if (this.commandLine.hasOption('T')) {
|
|
this.taskFile = this.commandLine.getOptionValue('T');
|
|
if (!(new File(this.taskFile).isFile())) {
|
|
super.handler
|
|
.logError("-T taskFile must be valid file: " + this.taskFile);
|
|
throw new IllegalArgumentException("-T taskFile must be valid file: " + this.taskFile);
|
|
}
|
|
}
|
|
|
|
if (this.commandLine.hasOption('i')) {
|
|
this.id = this.commandLine.getOptionValue('i').toLowerCase();
|
|
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) {
|
|
super.handler.logError("SQLException trying to resolve handle " + id + " to a valid dso");
|
|
throw new IllegalArgumentException(
|
|
"SQLException trying to resolve handle " + id + " to a valid dso");
|
|
}
|
|
if (dso == null) {
|
|
super.handler.logError("Id must be specified: a valid dso handle or 'all'; " + this.id + " could " +
|
|
"not be resolved to valid dso handle");
|
|
throw new IllegalArgumentException(
|
|
"Id must be specified: a valid dso handle or 'all'; " + this.id + " could " +
|
|
"not be resolved to valid dso handle");
|
|
}
|
|
}
|
|
} else {
|
|
super.handler.logError("Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " +
|
|
"help)");
|
|
throw new IllegalArgumentException(
|
|
"Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " +
|
|
"help)");
|
|
}
|
|
}
|
|
}
|