Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Curation tasks using the scripts and processes functionality #2820

Merged
477 changes: 282 additions & 195 deletions dspace-api/src/main/java/org/dspace/curate/CurationCli.java

Large diffs are not rendered by default.

@@ -0,0 +1,85 @@
/**
* 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.util.ArrayList;
import java.util.List;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.lang3.StringUtils;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;

/**
* This Enum holds all the possible options and combinations for the Curation script
*
* @author Maria Verdonck (Atmire) on 23/06/2020
*/
public enum CurationClientOptions {
TASK,
QUEUE,
HELP;

private static List<String> taskOptions;

/**
* This method resolves the CommandLine parameters to figure out which action the curation script should perform
*
* @param commandLine The relevant CommandLine for the curation script
* @return The curation option to be ran, parsed from the CommandLine
*/
protected static CurationClientOptions getClientOption(CommandLine commandLine) {
if (commandLine.hasOption("h")) {
return CurationClientOptions.HELP;
} else if (commandLine.hasOption("t") || commandLine.hasOption("T")) {
return CurationClientOptions.TASK;
} else if (commandLine.hasOption("q")) {
return CurationClientOptions.QUEUE;
}
return null;
}

protected static Options constructOptions() {
Options options = new Options();

options.addOption("t", "task", true, "curation task name; options: " + getTaskOptions());
options.addOption("T", "taskfile", true, "file containing curation task names");
options.addOption("i", "id", true,
"Id (handle) of object to perform task on, or 'all' to perform on whole repository");
options.addOption("p", "parameter", true, "a task parameter 'NAME=VALUE'");
options.addOption("q", "queue", true, "name of task queue to process");
options.addOption("e", "eperson", true, "email address of curating eperson");
options.addOption("r", "reporter", true,
"relative or absolute path to the desired report file. Use '-' to report to console. If absent, no " +
"reporting");
options.addOption("s", "scope", true,
"transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' applies");
options.addOption("v", "verbose", false, "report activity to stdout");
options.addOption("h", "help", false, "help");

return options;
}

/**
* Creates list of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask
*
* @return List of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask
*/
public static List<String> getTaskOptions() {
if (taskOptions == null) {
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
String[] taskConfigs = configurationService.getArrayProperty("plugin.named.org.dspace.curate.CurationTask");
taskOptions = new ArrayList<>();
for (String taskConfig : taskConfigs) {
taskOptions.add(StringUtils.substringAfterLast(taskConfig, "=").trim());
}
}
return taskOptions;
}
}
@@ -0,0 +1,61 @@
/**
* 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.sql.SQLException;

import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;

/**
* The {@link ScriptConfiguration} for the {@link CurationCli} script
*
* @author Maria Verdonck (Atmire) on 23/06/2020
*/
public class CurationScriptConfiguration<T extends CurationCli> extends ScriptConfiguration<T> {

@Autowired
private AuthorizeService authorizeService;

private Class<T> dspaceRunnableClass;

@Override
public Class<T> getDspaceRunnableClass() {
return this.dspaceRunnableClass;
}

@Override
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}

/**
* Only admin can run Curation script via the scripts and processes endpoints.
* @param context The relevant DSpace context
* @return True if currentUser is admin, otherwise false
*/
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}

@Override
public Options getOptions() {
if (options == null) {
super.options = CurationClientOptions.constructOptions();
}
return options;
}
}
20 changes: 7 additions & 13 deletions dspace-api/src/main/java/org/dspace/curate/Curator.java
Expand Up @@ -98,6 +98,7 @@ public Curator() {
communityService = ContentServiceFactory.getInstance().getCommunityService();
itemService = ContentServiceFactory.getInstance().getItemService();
handleService = HandleServiceFactory.getInstance().getHandleService();
resolver = new TaskResolver();
}

/**
Expand Down Expand Up @@ -142,10 +143,10 @@ public Curator addTask(String taskName) {
// performance order currently FIFO - to be revisited
perfList.add(taskName);
} catch (IOException ioE) {
log.error("Task: '" + taskName + "' initialization failure: " + ioE.getMessage());
System.out.println("Task: '" + taskName + "' initialization failure: " + ioE.getMessage());
}
} else {
log.error("Task: '" + taskName + "' does not resolve");
System.out.println("Task: '" + taskName + "' does not resolve");
}
return this;
}
Expand Down Expand Up @@ -259,13 +260,6 @@ public void curate(Context c, String id) throws IOException {
/**
* Performs all configured tasks upon DSpace object
* (Community, Collection or Item).
* <P>
* Note: Site-wide tasks will default to running as
* an Anonymous User unless you call the Site-wide task
* via the {@link curate(Context,String)} or
* {@link #curate(Context, DSpaceObject)} method with an
* authenticated Context object.
*
* @param dso the DSpace object
* @throws IOException if IO error
*/
Expand Down Expand Up @@ -325,7 +319,7 @@ public void queue(Context c, String id, String queueId) throws IOException {
taskQ.enqueue(queueId, new TaskQueueEntry(c.getCurrentUser().getName(),
System.currentTimeMillis(), perfList, id));
} else {
log.error("curate - no TaskQueue implemented");
System.out.println("curate - no TaskQueue implemented");
}
}

Expand All @@ -346,7 +340,7 @@ public void report(String message) {
try {
reporter.append(message);
} catch (IOException ex) {
log.error("Task reporting failure", ex);
System.out.println("Task reporting failure: " + ex);
}
}

Expand Down Expand Up @@ -552,7 +546,7 @@ public boolean run(DSpaceObject dso) throws IOException {
return !suspend(statusCode);
} catch (IOException ioe) {
//log error & pass exception upwards
log.error("Error executing curation task '" + task.getName() + "'", ioe);
System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe);
throw ioe;
}
}
Expand All @@ -568,7 +562,7 @@ public boolean run(Context c, String id) throws IOException {
return !suspend(statusCode);
} catch (IOException ioe) {
//log error & pass exception upwards
log.error("Error executing curation task '" + task.getName() + "'", ioe);
System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe);
throw ioe;
}
}
Expand Down
2 changes: 2 additions & 0 deletions dspace-api/src/test/data/dspaceFolder/assetstore/curate.txt
@@ -0,0 +1,2 @@
checklinks
requiredmetadata
Expand Up @@ -19,6 +19,12 @@
<property name="dspaceRunnableClass" value="org.dspace.app.bulkedit.MetadataExport"/>
</bean>

<bean id="curate" class="org.dspace.curate.CurationScriptConfiguration">
<property name="description" value="Curation tasks"/>
<property name="dspaceRunnableClass" value="org.dspace.curate.CurationCli"/>
</bean>

<!-- Keep as last script; for test ScriptRestRepository#findOneScriptByNameTest -->
<bean id="mock-script" class="org.dspace.scripts.MockDSpaceRunnableScriptConfiguration" scope="prototype">
<property name="description" value="Mocking a script for testing purposes" />
<property name="dspaceRunnableClass" value="org.dspace.scripts.impl.MockDSpaceRunnableScript"/>
Expand Down
58 changes: 47 additions & 11 deletions dspace-api/src/test/java/org/dspace/curate/CuratorTest.java
Expand Up @@ -8,52 +8,60 @@
package org.dspace.curate;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.HashMap;
import java.util.Map;

import org.dspace.AbstractUnitTest;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.SiteService;
import org.dspace.core.factory.CoreServiceFactory;
import org.dspace.ctask.general.NoOpCurationTask;
import org.dspace.services.ConfigurationService;
import org.junit.Test;

/**
*
* @author mhwood
*/
public class CuratorTest
extends AbstractUnitTest {
public class CuratorTest extends AbstractUnitTest {

private static final SiteService SITE_SERVICE = ContentServiceFactory.getInstance().getSiteService();

static final String RUN_PARAMETER_NAME = "runParameter";
static final String RUN_PARAMETER_VALUE = "a parameter";
static final String TASK_PROPERTY_NAME = "taskProperty";
static final String TASK_PROPERTY_VALUE = "a property";

/** Value of a known runtime parameter, if any. */
/**
* Value of a known runtime parameter, if any.
*/
static String runParameter;

/** Value of a known task property, if any. */
/**
* Value of a known task property, if any.
*/
static String taskProperty;

/**
* Test of curate method, of class Curator.
* Currently this just tests task properties and run parameters.
*
* @throws java.lang.Exception passed through.
*/
@Test
public void testCurate_DSpaceObject()
throws Exception {
public void testCurate_DSpaceObject() throws Exception {
System.out.println("curate");

final String TASK_NAME = "dummyTask";

// Configure the task to be run.
ConfigurationService cfg = kernelImpl.getConfigurationService();
cfg.setProperty("plugin.named.org.dspace.curate.CurationTask",
DummyTask.class.getName() + " = " + TASK_NAME);
DummyTask.class.getName() + " = " + TASK_NAME);
cfg.setProperty(TASK_NAME + '.' + TASK_PROPERTY_NAME, TASK_PROPERTY_VALUE);

// Get and configure a Curator.
Expand All @@ -72,12 +80,40 @@ public void testCurate_DSpaceObject()

// Check the result.
System.out.format("Task %s result was '%s'%n",
TASK_NAME, instance.getResult(TASK_NAME));
TASK_NAME, instance.getResult(TASK_NAME));
System.out.format("Task %s status was %d%n",
TASK_NAME, instance.getStatus(TASK_NAME));
TASK_NAME, instance.getStatus(TASK_NAME));
assertEquals("Unexpected task status",
Curator.CURATE_SUCCESS, instance.getStatus(TASK_NAME));
Curator.CURATE_SUCCESS, instance.getStatus(TASK_NAME));
assertEquals("Wrong run parameter", RUN_PARAMETER_VALUE, runParameter);
assertEquals("Wrong task property", TASK_PROPERTY_VALUE, taskProperty);
}

@Test
public void testCurate_NoOpTask() throws Exception {

CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses();

final String TASK_NAME = "noop";

// Configure the noop task to be run.
ConfigurationService cfg = kernelImpl.getConfigurationService();
cfg.setProperty("plugin.named.org.dspace.curate.CurationTask",
NoOpCurationTask.class.getName() + " = " + TASK_NAME);

// Get and configure a Curator.
Curator curator = new Curator();

StringBuilder reporterOutput = new StringBuilder();
curator.setReporter(reporterOutput); // Send any report to our StringBuilder.

curator.addTask(TASK_NAME);
Item item = mock(Item.class);
when(item.getType()).thenReturn(2);
when(item.getHandle()).thenReturn("testHandle");
curator.curate(context, item);

assertEquals(Curator.CURATE_SUCCESS, curator.getStatus(TASK_NAME));
assertEquals(reporterOutput.toString(), "No operation performed on testHandle");
}
}
Expand Up @@ -82,7 +82,9 @@ public void findAllScriptsTest() throws Exception {
ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(),
scriptConfigurations.get(2).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(3).getName(),
scriptConfigurations.get(3).getDescription())
scriptConfigurations.get(3).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(4).getName(),
scriptConfigurations.get(4).getDescription())
)));

}
Expand Down Expand Up @@ -139,7 +141,7 @@ public void findOneScriptByNameTest() throws Exception {
getClient(token).perform(get("/api/system/scripts/mock-script"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", ScriptMatcher
.matchMockScript(scriptConfigurations.get(3).getOptions())));
.matchMockScript(scriptConfigurations.get(scriptConfigurations.size() - 1).getOptions())));
}

@Test
Expand Down