diff --git a/Kitodo/pom.xml b/Kitodo/pom.xml
index 553293812aa..20aed63d2b0 100644
--- a/Kitodo/pom.xml
+++ b/Kitodo/pom.xml
@@ -226,6 +226,12 @@
Saxon-HE
compile
+
+ com.github.spotbugs
+ spotbugs-annotations
+ ${spotbugs-maven-plugin.version}
+ provided
+
se.jiderhamn.classloader-leak-prevention
classloader-leak-prevention-servlet3
diff --git a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java
index 29ea0699e9e..c0801b94a0a 100644
--- a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java
+++ b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java
@@ -619,6 +619,8 @@ public enum ParameterCore implements ParameterInterface {
ACTIVE_MQ_AUTH_PASSWORD(new Parameter<>("activeMQ.authPassword", "")),
+ ACTIVE_MQ_CREATE_NEW_PROCESSES_QUEUE(new Parameter("activeMQ.createNewProcesses.queue")),
+
ACTIVE_MQ_FINALIZE_STEP_QUEUE(new Parameter("activeMQ.finalizeStep.queue")),
ACTIVE_MQ_KITODO_SCRIPT_ALLOW(new Parameter("activeMQ.kitodoScript.allow")),
diff --git a/Kitodo/src/main/java/org/kitodo/production/helper/TempProcess.java b/Kitodo/src/main/java/org/kitodo/production/helper/TempProcess.java
index 6acd5283cf3..bba10c12da1 100644
--- a/Kitodo/src/main/java/org/kitodo/production/helper/TempProcess.java
+++ b/Kitodo/src/main/java/org/kitodo/production/helper/TempProcess.java
@@ -215,7 +215,7 @@ public void verifyDocType() throws IOException, ProcessGenerationException {
if (docTypeMetadata.isPresent() && docTypeMetadata.get() instanceof MetadataEntry) {
String docType = ((MetadataEntry)docTypeMetadata.get()).getValue();
if (StringUtils.isNotBlank(docType)
- && !this.getWorkpiece().getLogicalStructure().getType().equals(docType)) {
+ && !Objects.equals(this.getWorkpiece().getLogicalStructure().getType(), docType)) {
this.getWorkpiece().getLogicalStructure().setType(docType);
}
}
diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java
index 0bcd1ee4fdf..002dcca9513 100644
--- a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java
+++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/ActiveMQDirector.java
@@ -54,10 +54,11 @@ public class ActiveMQDirector implements Runnable, ServletContextListener {
private static final Logger logger = LogManager.getLogger(ActiveMQDirector.class);
// When implementing new services, add them to this list
- private static Collection extends ActiveMQProcessor> services;
+ private static Collection services;
static {
- services = Arrays.asList(new FinalizeStepProcessor(), new TaskActionProcessor(), new KitodoScriptProcessor());
+ services = Arrays.asList(new FinalizeStepProcessor(), new TaskActionProcessor(),
+ new CreateNewProcessesProcessor(), new KitodoScriptProcessor());
}
private static Connection connection = null;
diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/CreateNewProcessOrder.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/CreateNewProcessOrder.java
new file mode 100644
index 00000000000..73eace87ad3
--- /dev/null
+++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/CreateNewProcessOrder.java
@@ -0,0 +1,310 @@
+/*
+ * (c) Kitodo. Key to digital objects e. V.
+ *
+ * This file is part of the Kitodo project.
+ *
+ * It is licensed under GNU General Public License version 3 or later.
+ *
+ * For the full copyright and license information, please read the
+ * GPL3-License.txt file that was distributed with this source code.
+ */
+
+package org.kitodo.production.interfaces.activemq;
+
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.jms.JMSException;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.kitodo.api.MdSec;
+import org.kitodo.api.Metadata;
+import org.kitodo.api.MetadataEntry;
+import org.kitodo.api.MetadataGroup;
+import org.kitodo.data.database.beans.ImportConfiguration;
+import org.kitodo.data.database.beans.Process;
+import org.kitodo.data.database.beans.Template;
+import org.kitodo.data.database.exceptions.DAOException;
+import org.kitodo.data.exceptions.DataException;
+import org.kitodo.exceptions.ProcessorException;
+import org.kitodo.production.dto.ProcessDTO;
+import org.kitodo.production.services.ServiceManager;
+import org.kitodo.production.services.data.ImportConfigurationService;
+
+/**
+ * Order to create a new process. This contains all the necessary data.
+ */
+public class CreateNewProcessOrder {
+
+ /* Catalog imports can be specified (none, one or more). An
+ * "importconfiguration" and a search "value" must be specified. The search
+ * is carried out in the default search field. If no hit is found, or more
+ * than one, the search aborts with an error message. In the case of
+ * multiple imports, a repeated import is carried out according to the
+ * procedure specified in the rule set. */
+ private static final String FIELD_IMPORT = "import";
+ private static final String FIELD_IMPORT_CONFIG = "importconfiguration";
+ private static final String FIELD_IMPORT_VALUE = "value";
+
+ /* Additionally metadata can be passed. Passing multiple metadata or passing
+ * grouped metadata is also possible. */
+ private static final String FIELD_METADATA = "metadata";
+
+ /* A parent process can optionally be specified. The process ID or the
+ * process title can be specified. (If the value is all digits, it is
+ * considered the process ID, else it is considered the process title.) The
+ * process must be found in the client’s processes. If no parent process is
+ * specified, but a metadata entry with a use="higherLevelIdentifier" is
+ * included in the data from the catalog, the parent process is searched for
+ * using the metadata entry with use="recordIdentifier". It must already
+ * exist for the client. No parent process is implicitly created. The child
+ * process is added at the last position in the parent process. */
+ private static final String FIELD_PARENT = "parent";
+
+ // Mandatory information is the project ID.
+ private static final String FIELD_PROJECT = "project";
+
+ // Mandatory information is the process template.
+ private static final String FIELD_TEMPLATE = "template";
+
+ /* A process title can optionally be specified. If it is specified
+ * explicitly, exactly this process title is used, otherwise the system
+ * creates the process title according to the configured rule. The process
+ * title must still be unused for the client who owns the project. */
+ private static final String FIELD_TITLE = "title";
+
+ private final Integer projectId;
+ private final Integer templateId;
+ private final List> imports;
+ private final Optional title;
+ private final Optional parentId;
+ private final Collection metadata;
+
+ /**
+ * Creates a new CreateNewProcessOrder from an Active MQ message.
+ *
+ * @param ticket
+ * Active MQ message with (hopefully) all the data
+ * @throws DAOException
+ * if the ImportConfiguartionDAO is unable to find an import
+ * configuration with the given ID
+ * @throws DataException
+ * if there is an error accessing the search service
+ * @throws IllegalArgumentException
+ * If a required field is missing in the Active MQ message
+ * message, or contains inappropriate values.
+ * @throws JMSException
+ * Defined by the JMS API. I have not seen any cases where this
+ * would actually be thrown in the calls used here.
+ * @throws ProcessorException
+ * if the process count for the title is not exactly one
+ */
+ CreateNewProcessOrder(MapMessageObjectReader ticket) throws DAOException, DataException, JMSException,
+ ProcessorException {
+ this.projectId = ticket.getMandatoryInteger(FIELD_PROJECT);
+ this.templateId = ticket.getMandatoryInteger(FIELD_TEMPLATE);
+ this.imports = convertImports(ticket.getList(FIELD_IMPORT));
+ this.title = Optional.ofNullable(ticket.getString(FIELD_TITLE));
+ this.parentId = Optional.ofNullable(convertProcessId(ticket.getString(FIELD_PARENT)));
+ this.metadata = convertMetadata(ticket.getMapOfString(FIELD_METADATA), MdSec.DMD_SEC);
+ }
+
+ /**
+ * Converts import details into safe data objects. For {@code null}, it will
+ * return an empty list, never {@code null}.
+ *
+ * @throws IllegalArgumentException
+ * if a list member is not a map, or one of the mandatory map
+ * entries is missing or of a wrong type
+ * @throws DAOException
+ * if the ImportConfiguartionDAO is unable to find an import
+ * configuration with that ID
+ */
+ private static final List> convertImports(@Nullable List> imports)
+ throws DAOException {
+
+ if (Objects.isNull(imports) || imports.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final ImportConfigurationService importConfigurationService = ServiceManager.getImportConfigurationService();
+ List> result = new ArrayList<>();
+ for (Object dubious : imports) {
+ if (!(dubious instanceof Map)) {
+ throw new IllegalArgumentException("Entry of \"imports\" is not a map");
+ }
+ Map, ?> map = (Map, ?>) dubious;
+ ImportConfiguration importconfiguration = importConfigurationService.getById(MapMessageObjectReader
+ .getMandatoryInteger(map, FIELD_IMPORT_CONFIG));
+ String value = MapMessageObjectReader.getMandatoryString(map, FIELD_IMPORT_VALUE);
+ result.add(Pair.of(importconfiguration, value));
+ }
+ return result;
+ }
+
+ /**
+ * Gets the process ID. If the string is an integer, it is used as the
+ * process ID. Otherwise it is considered a title and searched for. If it is
+ * a title, there must be exactly one process for it to be converted to an
+ * ID.
+ *
+ * @param processId
+ * parent process reference
+ * @return ID of the parent process
+ * @throws DataException
+ * if there is an error accessing the search service
+ * @throws ProcessorException
+ * if the process count for the title is not exactly one
+ */
+ @CheckForNull
+ private static final Integer convertProcessId(String processId) throws DataException, ProcessorException {
+ if (Objects.isNull(processId)) {
+ return null;
+ }
+ if (processId.matches("\\d+")) {
+ return Integer.valueOf(processId);
+ } else {
+ List parents = ServiceManager.getProcessService().findByTitle(processId);
+ if (parents.size() == 0) {
+ throw new ProcessorException("Parent process not found");
+ } else if (parents.size() > 1) {
+ throw new ProcessorException("Parent process exists more than one");
+ } else {
+ return parents.get(0).getId();
+ }
+ }
+ }
+
+ /**
+ * Converts metadata details into safe data objects. For {@code null}, it
+ * will return an empty collection, never {@code null}.
+ */
+ private static final HashSet convertMetadata(@Nullable Map, ?> metadata, @Nullable MdSec domain) {
+
+ HashSet result = new HashSet<>();
+ if (Objects.isNull(metadata)) {
+ return result;
+ }
+
+ for (Entry, ?> entry : metadata.entrySet()) {
+ Object dubiousKey = entry.getKey();
+ if (!(dubiousKey instanceof String) || ((String) dubiousKey).isEmpty()) {
+ throw new IllegalArgumentException("Invalid metadata key");
+ }
+ String key = (String) dubiousKey;
+
+ Object dubiousValuesList = entry.getValue();
+ if (!(dubiousValuesList instanceof List)) {
+ dubiousValuesList = Collections.singletonList(dubiousValuesList);
+ }
+ for (Object dubiousValue : (List>) dubiousValuesList) {
+ if (dubiousValue instanceof Map) {
+ MetadataGroup metadataGroup = new MetadataGroup();
+ metadataGroup.setKey(key);
+ metadataGroup.setMetadata(convertMetadata((Map, ?>) dubiousValue, null));
+ metadataGroup.setDomain(domain);
+ result.add(metadataGroup);
+ } else {
+ MetadataEntry metadataEntry = new MetadataEntry();
+ metadataEntry.setKey(key);
+ metadataEntry.setValue(dubiousValue.toString());
+ metadataEntry.setDomain(domain);
+ result.add(metadataEntry);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the project ID. This is a mandatory field and can never be
+ * {@code null}.
+ *
+ * @return the project ID
+ */
+ @NonNull
+ Integer getProjectId() {
+ return projectId;
+ }
+
+ /**
+ * Returns the production template.
+ *
+ * @return the template
+ * @throws DAOException
+ * if the template cannot be loaded
+ */
+ Template getTemplate() throws DAOException {
+ return ServiceManager.getTemplateService().getById(templateId);
+ }
+
+ /**
+ * Returns the production template ID. This is a mandatory field and can
+ * never be {@code null}.
+ *
+ * @return the template ID
+ */
+ @NonNull
+ Integer getTemplateId() {
+ return templateId;
+ }
+
+ /**
+ * Returns import instructions. Each instruction consists of an import
+ * configuration and a search value to be searched for in the default search
+ * field. Subsequent search statements must be executed as additive imports.
+ * Can be empty, but never {@code null}.
+ *
+ * @return import instructions
+ */
+ @NonNull
+ List> getImports() {
+ return imports;
+ }
+
+ /**
+ * Returns an (optional) predefined title. If specified, this title must be
+ * used. Otherwise, the title must be formed using the formation rule. Can
+ * be {@code Optional.empty()}, but never {@code null}.
+ *
+ * @return the title, if any
+ */
+ @NonNull
+ Optional getTitle() {
+ return title;
+ }
+
+ /**
+ * Returns the parent process, if any.
+ *
+ * @return the parent process, or {@code null}
+ * @throws DAOException
+ * if the process cannot be loaded
+ */
+ @CheckForNull
+ Process getParent() throws DAOException {
+ return parentId.isPresent() ? ServiceManager.getProcessService().getById(parentId.get()) : null;
+ }
+
+ /**
+ * Specifies the metadata for the logical structure root of the process to
+ * be created. Can be empty, but never {@code null}.
+ *
+ * @return the metadata
+ */
+ @NonNull
+ Collection getMetadata() {
+ return metadata;
+ }
+}
diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/CreateNewProcessesProcessor.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/CreateNewProcessesProcessor.java
new file mode 100644
index 00000000000..87a7d2b10cf
--- /dev/null
+++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/CreateNewProcessesProcessor.java
@@ -0,0 +1,220 @@
+/*
+ * (c) Kitodo. Key to digital objects e. V.
+ *
+ * This file is part of the Kitodo project.
+ *
+ * It is licensed under GNU General Public License version 3 or later.
+ *
+ * For the full copyright and license information, please read the
+ * GPL3-License.txt file that was distributed with this source code.
+ */
+
+package org.kitodo.production.interfaces.activemq;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Locale.LanguageRange;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.jms.JMSException;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.xpath.XPathExpressionException;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.kitodo.api.Metadata;
+import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalMetadata;
+import org.kitodo.api.dataeditor.rulesetmanagement.RulesetManagementInterface;
+import org.kitodo.api.dataformat.Workpiece;
+import org.kitodo.config.ConfigCore;
+import org.kitodo.config.enums.ParameterCore;
+import org.kitodo.data.database.beans.Process;
+import org.kitodo.data.database.beans.Task;
+import org.kitodo.data.database.exceptions.DAOException;
+import org.kitodo.data.elasticsearch.exceptions.CustomResponseException;
+import org.kitodo.data.exceptions.DataException;
+import org.kitodo.exceptions.CommandException;
+import org.kitodo.exceptions.InvalidMetadataValueException;
+import org.kitodo.exceptions.NoRecordFoundException;
+import org.kitodo.exceptions.NoSuchMetadataFieldException;
+import org.kitodo.exceptions.ProcessGenerationException;
+import org.kitodo.exceptions.ProcessorException;
+import org.kitodo.exceptions.UnsupportedFormatException;
+import org.kitodo.production.forms.createprocess.ProcessFieldedMetadata;
+import org.kitodo.production.helper.Helper;
+import org.kitodo.production.helper.ProcessHelper;
+import org.kitodo.production.helper.TempProcess;
+import org.kitodo.production.metadata.MetadataEditor;
+import org.kitodo.production.process.ProcessGenerator;
+import org.kitodo.production.process.ProcessValidator;
+import org.kitodo.production.services.ServiceManager;
+import org.kitodo.production.services.data.ImportService;
+import org.kitodo.production.services.data.ProcessService;
+import org.kitodo.production.services.data.RulesetService;
+import org.kitodo.production.services.data.TaskService;
+import org.kitodo.production.services.dataformat.MetsService;
+import org.kitodo.production.services.file.FileService;
+import org.xml.sax.SAXException;
+
+/**
+ * An Active MQ service interface to create new processes.
+ */
+public class CreateNewProcessesProcessor extends ActiveMQProcessor {
+ private static final Logger logger = LogManager.getLogger(CreateNewProcessesProcessor.class);
+
+ private static final String ACQUISITION_STAGE_PROCESS_CREATION = "create";
+ private static final int IMPORT_WITHOUT_ANY_HIERARCHY = 1;
+ private static final String LAST_CHILD = Integer.toString(-1);
+ private static final List METADATA_LANGUAGE = Locale.LanguageRange.parse("en");
+
+ private final FileService fileService = ServiceManager.getFileService();
+ private final ImportService importService = ServiceManager.getImportService();
+ private final MetsService metsService = ServiceManager.getMetsService();
+ private final ProcessService processService = ServiceManager.getProcessService();
+ private final RulesetService rulesetService = ServiceManager.getRulesetService();
+ private final TaskService taskService = ServiceManager.getTaskService();
+
+ private RulesetManagementInterface rulesetManagement;
+
+ /**
+ * The default constructor looks up the queue name to use in
+ * kitodo_config.properties. If that is not configured and “null” is passed
+ * to the super constructor, this will prevent
+ * ActiveMQDirector.registerListeners() from starting this service.
+ */
+ public CreateNewProcessesProcessor() {
+ super(ConfigCore.getOptionalString(ParameterCore.ACTIVE_MQ_CREATE_NEW_PROCESSES_QUEUE).orElse(null));
+ }
+
+ /* The main routine processing incoming tickets. The function has been
+ * divided into three parts so that it is not too long. */
+ @Override
+ protected void process(MapMessageObjectReader ticket) throws ProcessorException, JMSException {
+ try {
+ CreateNewProcessOrder order = new CreateNewProcessOrder(ticket);
+ rulesetManagement = rulesetService.openRuleset(order.getTemplate().getRuleset());
+ TempProcess tempProcess = obtainTempProcess(order);
+ Process process = tempProcess.getProcess();
+ Process parentProcess = formProcessTitle(order, tempProcess, process);
+ createProcess(tempProcess, process, parentProcess);
+
+ } catch (CommandException | CustomResponseException | DataException | DAOException
+ | InvalidMetadataValueException | IOException | NoRecordFoundException | NoSuchMetadataFieldException
+ | ParserConfigurationException | ProcessGenerationException | SAXException | TransformerException
+ | UnsupportedFormatException | URISyntaxException | XPathExpressionException e) {
+ throw new ProcessorException(e.getMessage());
+ }
+ }
+
+ /* In the first part of the processing routine, the process is either
+ * created without import, or it is imported. The existing data is then
+ * added. */
+ private TempProcess obtainTempProcess(CreateNewProcessOrder order) throws DAOException,
+ InvalidMetadataValueException,
+ IOException, NoRecordFoundException, NoSuchMetadataFieldException, ParserConfigurationException,
+ ProcessGenerationException, ProcessorException, SAXException, TransformerException,
+ UnsupportedFormatException, URISyntaxException, XPathExpressionException {
+
+ if (order.getImports().isEmpty()) {
+ ProcessGenerator processGenerator = new ProcessGenerator();
+ processGenerator.generateProcess(order.getTemplateId(), order.getProjectId());
+ TempProcess tempProcess = new TempProcess(processGenerator.getGeneratedProcess(), new Workpiece());
+ tempProcess.getWorkpiece().getLogicalStructure().getMetadata().addAll(order.getMetadata());
+ tempProcess.verifyDocType();
+ return tempProcess;
+ } else {
+ TempProcess tempProcess = importProcess(order, 0);
+ tempProcess.getWorkpiece().getLogicalStructure().getMetadata().addAll(order.getMetadata());
+ tempProcess.verifyDocType();
+ for (int which = 1; which < order.getImports().size(); which++) {
+ TempProcess repeatedImport = importProcess(order, which);
+ Set metadata = repeatedImport.getWorkpiece().getLogicalStructure().getMetadata();
+ rulesetManagement.updateMetadata(tempProcess.getWorkpiece().getLogicalStructure().getType(),
+ tempProcess.getWorkpiece().getLogicalStructure().getMetadata(),
+ ACQUISITION_STAGE_PROCESS_CREATION, metadata);
+ }
+ return tempProcess;
+ }
+ }
+
+ /**
+ * Imports a dataset with an import configuration.
+ *
+ * @param order
+ * order for creating the process
+ * @param which
+ * which dataset should be imported
+ * @return the imported dataset
+ */
+ private TempProcess importProcess(CreateNewProcessOrder order, int which) throws DAOException,
+ InvalidMetadataValueException, IOException, NoRecordFoundException, NoSuchMetadataFieldException,
+ ParserConfigurationException, ProcessGenerationException, ProcessorException, SAXException,
+ TransformerException, UnsupportedFormatException, URISyntaxException, XPathExpressionException {
+
+ List processHierarchy = importService.importProcessHierarchy(
+ order.getImports().get(which).getValue(), order.getImports().get(which).getKey(),
+ order.getProjectId(), order.getTemplateId(), IMPORT_WITHOUT_ANY_HIERARCHY,
+ rulesetManagement.getFunctionalKeys(FunctionalMetadata.HIGHERLEVEL_IDENTIFIER));
+ if (processHierarchy.size() == 0) {
+ throw new ProcessorException("Process was not imported");
+ } else if (processHierarchy.size() > 1) {
+ throw new ProcessorException(processHierarchy.size() + " processes were imported");
+ }
+ return processHierarchy.get(0);
+ }
+
+ /* In the second and middle part of the processing routine, the process
+ * title is generated. */
+ private Process formProcessTitle(CreateNewProcessOrder order, TempProcess tempProcess, Process process)
+ throws DAOException, ProcessGenerationException, ProcessorException {
+
+ ProcessFieldedMetadata processDetails = ProcessHelper.initializeProcessDetails(tempProcess.getWorkpiece()
+ .getLogicalStructure(), rulesetManagement, ACQUISITION_STAGE_PROCESS_CREATION, METADATA_LANGUAGE);
+ Process parentProcess = order.getParent();
+ ProcessHelper.generateAtstslFields(tempProcess, processDetails.getRows(), Collections.emptyList(), tempProcess
+ .getWorkpiece().getLogicalStructure().getType(), rulesetManagement, ACQUISITION_STAGE_PROCESS_CREATION,
+ METADATA_LANGUAGE, parentProcess, true);
+ if (order.getTitle().isPresent()) {
+ process.setTitle(order.getTitle().get());
+ }
+ if (!ProcessValidator.isProcessTitleCorrect(process.getTitle())) {
+ throw new ProcessorException(Helper.getTranslation("processTitleAlreadyInUse", process.getTitle()));
+ }
+ return parentProcess;
+ }
+
+ /* In the third and final part of the processing routine, the process is
+ * created and saved. */
+ private void createProcess(TempProcess tempProcess, Process process, Process parentProcess) throws DataException,
+ CustomResponseException, IOException, CommandException {
+
+ saveProcess(process);
+ fileService.createProcessLocation(process);
+ metsService.saveWorkpiece(tempProcess.getWorkpiece(), processService.getMetadataFileUri(process));
+ if (Objects.nonNull(parentProcess)) {
+ MetadataEditor.addLink(parentProcess, LAST_CHILD, process.getId());
+ process.setParent(parentProcess);
+ parentProcess.getChildren().add(process);
+ saveProcess(process);
+ saveProcess(parentProcess);
+ }
+ }
+
+ /**
+ * When the process is saved, the tasks are also indexed.
+ *
+ * @param process
+ * process to be saved
+ */
+ private void saveProcess(Process process) throws DataException, CustomResponseException, IOException {
+ processService.save(process, true);
+ for (Task task : process.getTasks()) {
+ taskService.saveToIndex(task, true);
+ }
+ }
+}
diff --git a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/MapMessageObjectReader.java b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/MapMessageObjectReader.java
index cc9839613d9..097ee761d01 100644
--- a/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/MapMessageObjectReader.java
+++ b/Kitodo/src/main/java/org/kitodo/production/interfaces/activemq/MapMessageObjectReader.java
@@ -11,12 +11,16 @@
package org.kitodo.production.interfaces.activemq;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -33,6 +37,7 @@ public class MapMessageObjectReader {
private MapMessage ticket;
private static final Logger logger = LogManager.getLogger(MapMessageObjectReader.class);
+ private static final String MANDATORY_ARGUMENT = "Mandatory argument ";
private static final String MISSING_ARGUMENT = "Missing mandatory argument: \"";
private static final String WRONG_TYPE = "\" was not found to be of type ";
@@ -102,7 +107,8 @@ public Set getMandatorySetOfString(String key) throws JMSException {
* in case that getObject returns null or the returned string is
* of length “0”.
* @throws JMSException
- * can be thrown by MapMessage.getString(String)
+ * thrown by MapMessage.getString(String) if the string is a
+ * byte[]
*/
public String getMandatoryString(String key) throws JMSException {
String mandatoryString = ticket.getString(key);
@@ -112,6 +118,32 @@ public String getMandatoryString(String key) throws JMSException {
return mandatoryString;
}
+ /**
+ * Fetches a String from a map. This is a strict implementation that
+ * requires the string not to be null and not to be empty.
+ *
+ * @param key
+ * the name of the string to return
+ * @return the string requested
+ * @throws IllegalArgumentException
+ * in case that get returns null, an inappropriate object, or
+ * the returned string is of length “0”.
+ */
+ public static String getMandatoryString(Map, ?> data, String key) {
+ Object value = data.get(key);
+ if (Objects.isNull(value)) {
+ throw new IllegalArgumentException(MISSING_ARGUMENT + key + "\"");
+ }
+ if (!(value instanceof String)) {
+ throw new IllegalArgumentException(MANDATORY_ARGUMENT + key + " is not a string");
+ }
+ String mandatoryString = (String) value;
+ if (mandatoryString.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_ARGUMENT + key + "\"");
+ }
+ return mandatoryString;
+ }
+
/**
* Fetches a {@code Collection} from a MapMessage. This is a loose
* implementation for an optional object with optional content. The
@@ -169,6 +201,7 @@ public Collection getCollectionOfString(String key) throws JMSException
* can be thrown by MapMessage.getString(String)
*/
+ @CheckForNull
public String getString(String key) throws JMSException {
return ticket.getString(key);
}
@@ -178,12 +211,12 @@ public String getString(String key) throws JMSException {
* implementation that requires the Integer not to be null.
*
* @param key
- * the name of the string to return
+ * the name of the integer to return
* @return the string requested
* @throws IllegalArgumentException
* in case that getObject returns null
* @throws JMSException
- * can be thrown by MapMessage.getString(String)
+ * in case that getObject returns an unmatching object type
*/
public Integer getMandatoryInteger(String key) throws JMSException {
if (!ticket.itemExists(key)) {
@@ -192,6 +225,33 @@ public Integer getMandatoryInteger(String key) throws JMSException {
return ticket.getInt(key);
}
+ /**
+ * Fetches an Integer object from a map. This is a strict implementation
+ * that requires the Integer not to be null.
+ *
+ * @param data
+ * the data map
+ * @param key
+ * the name of the integer to return
+ * @return the string requested
+ * @throws IllegalArgumentException
+ * in case that there is no such key, or get returns an
+ * unmatching object type
+ */
+ public static Integer getMandatoryInteger(Map, ?> data, String key) {
+ if (!data.containsKey(key)) {
+ throw new IllegalArgumentException(MISSING_ARGUMENT + key + "\"");
+ }
+ Object value = data.get(key);
+ if (value instanceof Integer) {
+ return (Integer) value;
+ }
+ if (value instanceof String) {
+ return Integer.valueOf((String) value);
+ }
+ throw new IllegalArgumentException(MANDATORY_ARGUMENT + key + " is not an integer");
+ }
+
/**
* Fetches a {@code Map} from a MapMessage. This is a partly
* strict implementation that allows no null element neither as key, nor as
@@ -240,6 +300,91 @@ public Map getMapOfStringToString(String key) {
return mapOfStringToString;
}
+ /**
+ * Fetches a {@code List>} from a MapMessage. May return {@code null} if
+ * there is no such object.
+ *
+ * @param key
+ * key for which the list to return
+ * @return the map, or {@code null}
+ * @throws IllegalArgumentException
+ * if the object isn’t a {@code List}
+ * @throws JMSException
+ * if an I/O exception occurs during read, i.e. if the map
+ * message sent is larger than the allowed size
+ */
+ @CheckForNull
+ public List> getList(String key) throws JMSException {
+ Object valueObject = ticket.getObject(key);
+ if (Objects.isNull(valueObject)) {
+ return null;
+ }
+ if (!(valueObject instanceof List)) {
+ throw new IllegalArgumentException(key + " is not a List");
+ }
+ return (List>) valueObject;
+ }
+
+ /**
+ * Fetches an {@code Integer} from a MapMessage. May return {@code null} if
+ * there is no such object.
+ *
+ * @param key
+ * key for which the integer to return
+ * @return the integer, or {@code null} if there isn’t one
+ * @throws IllegalArgumentException
+ * if the object is not an Integer
+ * @throws JMSException
+ * if an I/O exception occurs during read, i.e. if the map
+ * message sent is larger than the allowed size
+ */
+ @CheckForNull
+ public Integer getInteger(String key) throws JMSException {
+ Object valueObject = ticket.getObject(key);
+ if (Objects.isNull(valueObject)) {
+ return null;
+ }
+ if (!(valueObject instanceof Integer)) {
+ throw new IllegalArgumentException(key + " is not an Integer");
+ }
+ return (Integer) valueObject;
+ }
+
+ /**
+ * Fetches a {@code Map} from a MapMessage. May return
+ * {@code null} if there is no such object.
+ *
+ * @param key
+ * key for which the map to return
+ * @return the map, or {@code null}
+ * @throws IllegalArgumentException
+ * if the object isn’t a {@code Map} or one of its keys isn’t a
+ * {@code String}
+ * @throws JMSException
+ * if an I/O exception occurs during read, i.e. if the map
+ * message sent is larger than the allowed size
+ */
+ @CheckForNull
+ public Map getMapOfString(String key) throws JMSException {
+ HashMap mapOfString = new HashMap<>();
+ Object mapObject = ticket.getObject(key);
+ if (Objects.isNull(mapObject)) {
+ return null;
+ }
+ if (!(mapObject instanceof Map)) {
+ throw new IllegalArgumentException("Incompatible types: \"" + key + WRONG_TYPE + "Map.");
+ }
+ for (Entry, ?> entry : ((Map, ?>) mapObject).entrySet()) {
+ Object entryKey = entry.getKey();
+ if (!(entryKey instanceof String)) {
+ throw new IllegalArgumentException("Incompatible types: A key element of \"" + key + WRONG_TYPE
+ + "String.");
+ }
+ mapOfString.put(((String) entryKey), entry.getValue());
+ }
+ return mapOfString;
+ }
+
/**
* Tests whether a field can be obtained from a MapMessage.
*
diff --git a/Kitodo/src/main/resources/kitodo_config.properties b/Kitodo/src/main/resources/kitodo_config.properties
index 22b3a1f0b0a..6c1e4860252 100644
--- a/Kitodo/src/main/resources/kitodo_config.properties
+++ b/Kitodo/src/main/resources/kitodo_config.properties
@@ -645,6 +645,9 @@ activeMQ.user=testAdmin
# The Kitodo Script commands authorized to be executed must be named here:
activeMQ.kitodoScript.allow=createFolders&export&searchForMedia
+# You can provide a queue from which messages are read to create new processes
+#activeMQ.createNewProcesses.queue=KitodoProduction.CreateNewProcesses.Queue
+
# -----------------------------------
# Elasticsearch properties