Skip to content

Commit

Permalink
Merge pull request #2820 from atmire/w2p-71513_Curation-tasks-scripts…
Browse files Browse the repository at this point in the history
…-and-processes

Curation tasks using the scripts and processes functionality
  • Loading branch information
tdonohue committed Jul 31, 2020
2 parents c44613a + 7a269f3 commit 1301e43
Show file tree
Hide file tree
Showing 13 changed files with 947 additions and 256 deletions.
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

0 comments on commit 1301e43

Please sign in to comment.