diff --git a/dspace-api/src/test/data/dspaceFolder/config/modules/curate.cfg b/dspace-api/src/test/data/dspaceFolder/config/modules/curate.cfg new file mode 100644 index 0000000000..92ba93ca38 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/modules/curate.cfg @@ -0,0 +1,29 @@ +#---------------------------------------------------------------# +#--------------CURATION SYSTEM CONFIGURATIONS-------------------# +#---------------------------------------------------------------# +# Configuration properties used solely by the Curation system # +#---------------------------------------------------------------# + +### Task Class implementations + +# NOTE: Other configurations can append to this list of default tasks by simply +# adding their own additional values of "plugin.named.org.dspace.curate.CurationTask" +plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.NoOpCurationTask = noop +plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ProfileFormats = profileformats +plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RequiredMetadata = requiredmetadata +#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ClamScan = vscan +#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MicrosoftTranslator = translate +plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MetadataValueLinkChecker = checklinks +# add new tasks here (or in additional config files) + +# Testing tasks +plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.testing.MarkerTask = marker + +## task queue implementation +plugin.single.org.dspace.curate.TaskQueue = org.dspace.curate.FileTaskQueue + +# directory location of curation task queues +curate.taskqueue.dir = ${dspace.dir}/ctqueues + +# (optional) directory location of scripted (non-java) tasks +# curate.script.dir = ${dspace.dir}/ctscripts diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java new file mode 100644 index 0000000000..70d8f12990 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java @@ -0,0 +1,57 @@ +/** + * 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.util; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.spring.ConfigurationPropertySource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Utility class that will initialize the DSpace Configuration on Spring startup. + * Adapted from the class of the same name in {@code dspace-server-webapp}. + *

+ * NOTE: MUST be loaded after DSpaceKernelInitializer, as it requires the kernel + * is already initialized. + *

+ * This initializer ensures that our DSpace Configuration is loaded into Spring's + * list of {@code PropertySource}s very early in the Spring startup process. + * That is important as it allows us to use DSpace configurations within + * {@code @ConditionalOnProperty} annotations on beans, as well as {@code @Value} + * annotations and XML bean definitions. + */ +public class DSpaceConfigurationInitializer + implements ApplicationContextInitializer { + + private static final Logger log = LogManager.getLogger(); + + @Override + public void initialize(final ConfigurableApplicationContext applicationContext) { + // Load DSpace Configuration service (requires kernel already initialized) + ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + Configuration configuration = configurationService.getConfiguration(); + + // Create an Apache Commons Configuration Property Source from our configuration + ConfigurationPropertySource apacheCommonsConfigPropertySource = + new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + + // Prepend it to the Environment's list of PropertySources + // NOTE: This is added *first* in the list so that settings in DSpace's + // ConfigurationService *override* any default values in Spring's + // application.properties (or similar). + applicationContext.getEnvironment() + .getPropertySources() + .addFirst(apacheCommonsConfigPropertySource); + } +} + diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java new file mode 100644 index 0000000000..93fd308185 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java @@ -0,0 +1,135 @@ +/** + * 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.util; + +import java.io.File; +import javax.naming.Context; +import javax.naming.InitialContext; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.kernel.DSpaceKernel; +import org.dspace.kernel.DSpaceKernelManager; +import org.dspace.servicemanager.DSpaceKernelImpl; +import org.dspace.servicemanager.DSpaceKernelInit; +import org.dspace.servicemanager.config.DSpaceConfigurationService; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * Utility class that will initialize the DSpace Kernel on Spring startup. + * Adapted from the class of the same name in {@code dspace-server-webapp}. + */ +public class DSpaceKernelInitializer + implements ApplicationContextInitializer { + + private static final Logger log = LogManager.getLogger(); + + private transient DSpaceKernel dspaceKernel; + + @Override + public void initialize(final ConfigurableApplicationContext applicationContext) { + // Check if the kernel is already started + this.dspaceKernel = DSpaceKernelManager.getDefaultKernel(); + if (this.dspaceKernel == null) { + DSpaceKernelImpl kernelImpl = null; + try { + // Load the kernel with default settings + kernelImpl = DSpaceKernelInit.getKernel(null); + if (!kernelImpl.isRunning()) { + // Determine configured DSpace home & init the Kernel + kernelImpl.start(getDSpaceHome(applicationContext.getEnvironment())); + } + this.dspaceKernel = kernelImpl; + + } catch (Exception e) { + // failed to start so destroy it and log and throw an exception + try { + if (kernelImpl != null) { + kernelImpl.destroy(); + } + this.dspaceKernel = null; + } catch (Exception e1) { + // nothing + } + String message = "Failure during ServletContext initialisation: " + e.getMessage(); + log.error("Failure during ServletContext initialisation: ", e); + throw new RuntimeException(message, e); + } + } + + if (applicationContext.getParent() == null) { + // Set the DSpace Kernel Application context as a parent of the + // Spring context so that we can auto-wire all DSpace Kernel services. + applicationContext.setParent(dspaceKernel.getServiceManager().getApplicationContext()); + + // Add a listener for Spring application shutdown so that we can + // nicely cleanup the DSpace kernel. + applicationContext.addApplicationListener(new DSpaceKernelDestroyer(dspaceKernel)); + } + } + + /** + * Find DSpace's "home" directory (from current environment) + * Initially look for JNDI Resource called "java:/comp/env/dspace.dir". + * If not found, use value provided in "dspace.dir" in Spring Environment + */ + private String getDSpaceHome(ConfigurableEnvironment environment) { + // Load the "dspace.dir" property from Spring's configuration. + // This gives us the location of our DSpace configuration, which is + // necessary to start the kernel. + String providedHome = environment.getProperty(DSpaceConfigurationService.DSPACE_HOME); + + String dspaceHome = null; + try { + // Allow ability to override home directory via JNDI + Context ctx = new InitialContext(); + dspaceHome = (String) ctx.lookup("java:/comp/env/" + DSpaceConfigurationService.DSPACE_HOME); + } catch (Exception e) { + // do nothing + } + + // Otherwise, verify the 'providedHome' value is non-empty, exists and includes DSpace configs + if (dspaceHome == null) { + if (StringUtils.isNotBlank(providedHome) && + !providedHome.equals("${" + DSpaceConfigurationService.DSPACE_HOME + "}")) { + File test = new File(providedHome); + if (test.exists() && new File(test, DSpaceConfigurationService.DSPACE_CONFIG_PATH).exists()) { + dspaceHome = providedHome; + } + } + } + return dspaceHome; + } + + + /** + * Utility class that will destroy the DSpace Kernel on Spring shutdown. + */ + private class DSpaceKernelDestroyer + implements ApplicationListener { + private DSpaceKernel kernel; + + public DSpaceKernelDestroyer(DSpaceKernel kernel) { + this.kernel = kernel; + } + + @Override + public void onApplicationEvent(final ContextClosedEvent event) { + if (this.kernel != null) { + this.kernel.destroy(); + this.kernel = null; + } + } + } +} + diff --git a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java new file mode 100644 index 0000000000..d5109a2def --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java @@ -0,0 +1,115 @@ +/** + * 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.workflow; + +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.List; +import java.util.regex.Pattern; +import javax.inject.Inject; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.ctask.testing.MarkerTask; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.util.DSpaceConfigurationInitializer; +import org.dspace.util.DSpaceKernelInitializer; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Test the attachment of curation tasks to workflows. + * + * @author mwood + */ +@RunWith(SpringRunner.class) +@ContextConfiguration( + initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }, + locations = { "classpath:spring/*.xml" } +) +public class WorkflowCurationIT + extends AbstractIntegrationTestWithDatabase { + @Inject + private ConfigurationService configurationService; + + @Inject + private ItemService itemService; + + /** + * Basic smoke test of a curation task attached to a workflow step. + * See {@link MarkerTask}. + * @throws java.lang.Exception passed through. + */ + @Test + public void curationTest() + throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + + // A submitter; + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .withLanguage("en") + .build(); + + // A containment hierarchy; + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + final String CURATION_COLLECTION_HANDLE = "123456789/curation-test-1"; + Collection collection = CollectionBuilder + .createCollection(context, community, CURATION_COLLECTION_HANDLE) + .withName("Collection") + .build(); + + // A workflow configuration for the test Collection; + // See test/dspaceFolder/config/spring/api/workflow.xml + + // A curation task attached to the workflow; + // See test/dspaceFolder/config/workflow-curation.xml for the attachment. + // This should include MarkerTask. + + // A workflow item; + context.setCurrentUser(submitter); + XmlWorkflowItem wfi = WorkflowItemBuilder.createWorkflowItem(context, collection) + .withTitle("Test of workflow curation") + .withIssueDate("2021-05-14") + .withSubject("Testing") + .build(); + + context.restoreAuthSystemState(); + + //** THEN ** + + // Search the Item's provenance for MarkerTask's name. + List provenance = itemService.getMetadata(wfi.getItem(), + MarkerTask.SCHEMA, MarkerTask.ELEMENT, MarkerTask.QUALIFIER, MarkerTask.LANGUAGE); + Pattern markerPattern = Pattern.compile(MarkerTask.class.getCanonicalName()); + boolean found = false; + for (MetadataValue record : provenance) { + if (markerPattern.matcher(record.getValue()).find()) { + found = true; + break; + } + } + assertThat("Item should have been curated", found); + } +}