- Merged with KevinVdV/DSpace/DS-4239-migrate-workflow-xml-to-spring

- Changed all implementations of XmlWorkflowFactoryImpl to work with new mapping
- Moved their java doc to XmlWorkflowFactory
- Removed/Changed exceptions
- Altered tests in WorkflowDefinitionRestRepositoryIT to work with changes

Merge remote-tracking branch 'github-kevin/DS-4239-migrate-workflow-xml-to-spring' into workflow-step-definitions

# Conflicts:
#	dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowFactoryImpl.java
#	dspace-api/src/main/java/org/dspace/xmlworkflow/factory/XmlWorkflowFactory.java
This commit is contained in:
Marie Verdonck
2019-12-20 11:21:17 +01:00
16 changed files with 618 additions and 664 deletions

View File

@@ -48,6 +48,7 @@ import org.dspace.harvest.HarvestedCollection;
import org.dspace.harvest.service.HarvestedCollectionService; import org.dspace.harvest.service.HarvestedCollectionService;
import org.dspace.workflow.factory.WorkflowServiceFactory; import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.dspace.xmlworkflow.WorkflowConfigurationException; import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.XmlWorkflowFactoryImpl;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.state.Workflow; import org.dspace.xmlworkflow.state.Workflow;
import org.dspace.xmlworkflow.storedcomponents.CollectionRole; import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
@@ -383,7 +384,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
log.error(LogManager.getHeader(context, "setWorkflowGroup", log.error(LogManager.getHeader(context, "setWorkflowGroup",
"collection_id=" + collection.getID() + " " + e.getMessage()), e); "collection_id=" + collection.getID() + " " + e.getMessage()), e);
} }
if (!StringUtils.equals(XmlWorkflowFactory.LEGACY_WORKFLOW_NAME, workflow.getID())) { if (!StringUtils.equals(XmlWorkflowFactoryImpl.LEGACY_WORKFLOW_NAME, workflow.getID())) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"setWorkflowGroup can be used only on collection with the default basic dspace workflow. " "setWorkflowGroup can be used only on collection with the default basic dspace workflow. "
+ "Instead, the collection: " + "Instead, the collection: "

View File

@@ -13,14 +13,15 @@ import java.util.List;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group; import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.GroupService;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.storedcomponents.CollectionRole; import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
import org.dspace.xmlworkflow.storedcomponents.WorkflowItemRole; import org.dspace.xmlworkflow.storedcomponents.WorkflowItemRole;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
import org.dspace.xmlworkflow.storedcomponents.service.WorkflowItemRoleService; import org.dspace.xmlworkflow.storedcomponents.service.WorkflowItemRoleService;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
/** /**
* The role that is responsible for a certain step * The role that is responsible for a certain step
@@ -32,38 +33,36 @@ import org.dspace.xmlworkflow.storedcomponents.service.WorkflowItemRoleService;
* @author Ben Bosman (ben at atmire dot com) * @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com) * @author Mark Diggory (markd at atmire dot com)
*/ */
public class Role { public class Role implements BeanNameAware {
@Autowired
private GroupService groupService;
@Autowired
private CollectionRoleService collectionRoleService;
@Autowired
private WorkflowItemRoleService workflowItemRoleService;
private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
private CollectionRoleService collectionRoleService = XmlWorkflowServiceFactory.getInstance()
.getCollectionRoleService();
private WorkflowItemRoleService workflowItemRoleService = XmlWorkflowServiceFactory.getInstance()
.getWorkflowItemRoleService();
private String id; private String id;
private String name; private String name;
private String description; private String description;
private boolean isInternal; private boolean isInternal = false;
private Scope scope; private Scope scope = Scope.COLLECTION;
public static enum Scope { @Override
public void setBeanName(String s) {
this.id = s;
}
public enum Scope {
REPOSITORY, REPOSITORY,
COLLECTION, COLLECTION,
ITEM ITEM
} }
public Role(String id, String name, String description, boolean isInternal, Scope scope) {
this.id = id;
this.name = name;
this.description = description;
this.isInternal = isInternal;
this.scope = scope;
}
public String getId() { public String getId() {
return id; return id;
} }
public String getName() { public String getName() {
return name; return name;
} }
@@ -118,4 +117,41 @@ public class Role {
} }
} }
/**
* The name specified in the name attribute of a role will be used to lookup the in DSpace.
* The lookup will depend on the scope specified in the "scope" attribute:
* @param name
*/
@Required
public void setName(String name) {
this.name = name;
}
/**
* Set the description of the role
* @param description the description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Set the scope attribute, depending on the scope the users will be retrieved in the following manner:
* * collection: The collection value specifies that the group will be configured at the level of the collection.
* * repository: The repository scope uses groups that are defined at repository level in DSpace.
* item: The item scope assumes that a different action in the workflow will assign a number of EPersons or
* Groups to a specific workflow-item in order to perform a step.
* @param scope the scope parameter
*/
public void setScope(Scope scope) {
this.scope = scope;
}
/**
* Optional attribute which isn't really used at the moment, false by default
* @param internal if the role is internal
*/
public void setInternal(boolean internal) {
isInternal = internal;
}
} }

View File

@@ -13,8 +13,8 @@ import java.io.StringWriter;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Date; import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -236,13 +236,13 @@ public class WorkflowUtils extends Util {
} }
public static HashMap<String, Role> getCollectionRoles(Collection thisCollection) public static Map<String, Role> getCollectionRoles(Collection thisCollection)
throws IOException, WorkflowConfigurationException, SQLException { throws IOException, WorkflowConfigurationException, SQLException {
Workflow workflow = xmlWorkflowFactory.getWorkflow(thisCollection); Workflow workflow = xmlWorkflowFactory.getWorkflow(thisCollection);
LinkedHashMap<String, Role> result = new LinkedHashMap<String, Role>(); LinkedHashMap<String, Role> result = new LinkedHashMap<String, Role>();
if (workflow != null) { if (workflow != null) {
//Make sure we find one //Make sure we find one
HashMap<String, Role> allRoles = workflow.getRoles(); Map<String, Role> allRoles = workflow.getRoles();
//We have retrieved all our roles, not get the ones which can be configured by the collection //We have retrieved all our roles, not get the ones which can be configured by the collection
for (String roleId : allRoles.keySet()) { for (String roleId : allRoles.keySet()) {
Role role = allRoles.get(roleId); Role role = allRoles.get(roleId);
@@ -257,13 +257,13 @@ public class WorkflowUtils extends Util {
} }
public static HashMap<String, Role> getCollectionAndRepositoryRoles(Collection thisCollection) public static Map<String, Role> getCollectionAndRepositoryRoles(Collection thisCollection)
throws IOException, WorkflowConfigurationException, SQLException { throws IOException, WorkflowConfigurationException, SQLException {
Workflow workflow = xmlWorkflowFactory.getWorkflow(thisCollection); Workflow workflow = xmlWorkflowFactory.getWorkflow(thisCollection);
LinkedHashMap<String, Role> result = new LinkedHashMap<String, Role>(); LinkedHashMap<String, Role> result = new LinkedHashMap<String, Role>();
if (workflow != null) { if (workflow != null) {
//Make sure we find one //Make sure we find one
HashMap<String, Role> allRoles = workflow.getRoles(); Map<String, Role> allRoles = workflow.getRoles();
//We have retrieved all our roles, not get the ones which can be configured by the collection //We have retrieved all our roles, not get the ones which can be configured by the collection
for (String roleId : allRoles.keySet()) { for (String roleId : allRoles.keySet()) {
Role role = allRoles.get(roleId); Role role = allRoles.get(roleId);
@@ -279,13 +279,13 @@ public class WorkflowUtils extends Util {
} }
public static HashMap<String, Role> getAllExternalRoles(Collection thisCollection) public static Map<String, Role> getAllExternalRoles(Collection thisCollection)
throws IOException, WorkflowConfigurationException, SQLException { throws IOException, WorkflowConfigurationException, SQLException {
Workflow workflow = xmlWorkflowFactory.getWorkflow(thisCollection); Workflow workflow = xmlWorkflowFactory.getWorkflow(thisCollection);
LinkedHashMap<String, Role> result = new LinkedHashMap<String, Role>(); LinkedHashMap<String, Role> result = new LinkedHashMap<String, Role>();
if (workflow != null) { if (workflow != null) {
//Make sure we find one //Make sure we find one
HashMap<String, Role> allRoles = workflow.getRoles(); Map<String, Role> allRoles = workflow.getRoles();
//We have retrieved all our roles, not get the ones which can be configured by the collection //We have retrieved all our roles, not get the ones which can be configured by the collection
for (String roleId : allRoles.keySet()) { for (String roleId : allRoles.keySet()) {
Role role = allRoles.get(roleId); Role role = allRoles.get(roleId);

View File

@@ -7,31 +7,15 @@
*/ */
package org.dspace.xmlworkflow; package org.dspace.xmlworkflow;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.PostConstruct;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerException;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.xpath.XPathAPI;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.Workflow; import org.dspace.xmlworkflow.state.Workflow;
import org.dspace.xmlworkflow.state.actions.UserSelectionActionConfig; import org.springframework.beans.factory.annotation.Required;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/** /**
* The workflowfactory is responsible for parsing the * The workflowfactory is responsible for parsing the
@@ -46,243 +30,81 @@ import org.w3c.dom.NodeList;
*/ */
public class XmlWorkflowFactoryImpl implements XmlWorkflowFactory { public class XmlWorkflowFactoryImpl implements XmlWorkflowFactory {
public static final String LEGACY_WORKFLOW_NAME = "defaultWorkflow";
private Logger log = org.apache.logging.log4j.LogManager.getLogger(XmlWorkflowFactoryImpl.class); private Logger log = org.apache.logging.log4j.LogManager.getLogger(XmlWorkflowFactoryImpl.class);
@Autowired(required = true) private Map<String, Workflow> workflowMapping;
protected ConfigurationService configurationService;
protected HashMap<String, Workflow> collectionHandleToWorkflowCache;
protected HashMap<String, Workflow> workflowNameToWorkflowCache;
protected List<Workflow> workflowCache;
protected HashMap<String, List<String>> workflowNameToCollectionHandlesCache;
protected String path;
@PostConstruct
protected void init() {
path = configurationService
.getProperty("dspace.dir") + File.separator + "config" + File.separator + "workflow.xml";
}
// private static String pathActions = ConfigurationManager.getProperty("dspace.dir")+"/config/workflow-actions.xml";
protected XmlWorkflowFactoryImpl() {
}
@Override @Override
public Workflow getWorkflow(Collection collection) throws WorkflowConfigurationException { public Workflow getWorkflow(Collection collection) throws WorkflowConfigurationException {
return this.getWorkflowById(collectionHandleToWorkflowCache, collection.getHandle(), "collection"); // Attempt to retrieve our workflow object
if (workflowMapping.get(collection.getHandle()) == null) {
final Workflow defaultWorkflow = workflowMapping.get(LEGACY_WORKFLOW_NAME);
if (defaultWorkflow != null) {
return defaultWorkflow;
}
} else {
return workflowMapping.get(collection.getHandle());
}
throw new WorkflowConfigurationException(
"Error while retrieving workflow for the following collection: " + collection.getHandle());
}
@Required
public void setWorkflowMapping(Map<String, Workflow> workflowMapping) {
this.workflowMapping = workflowMapping;
} }
@Override @Override
public Workflow getWorkflowByName(String workflowName) throws WorkflowConfigurationException { public Workflow getWorkflowByName(String workflowName) throws WorkflowConfigurationException {
return this.getWorkflowById(workflowNameToWorkflowCache, workflowName, "workflow"); for (Workflow workflow : workflowMapping.values()) {
} if (workflow.getID().equals(workflowName)) {
return workflow;
/**
* Tries to retrieve a workflow from the given cache by the given id, if it is not present it tries to resolve the
* id to a workflow from the workflow.xml at path (by using the workflow-map with given xpathId)
* If it gets resolved this mapping gets added to the given cache and the resolved workflow returned
* If it can't be resolved, the default mapping gets returned (and added to cache if not already present)
*
* @param cache Cache we are using for the id-workflow mapping
* @param id Id we're trying to resolve to a workflow
* @param xpathId XpathId used to resolve the id to a workflow if it wasn't present in the cache
* @return Corresponding workflow mapped to the given id
* @throws WorkflowConfigurationException If no corresponding mapping or default can be found or error trying to
* to resolve to one
*/
private Workflow getWorkflowById(HashMap<String, Workflow> cache, String id, String xpathId)
throws WorkflowConfigurationException {
//Initialize our cache if we have none
if (cache == null) {
cache = new HashMap<>();
}
// Attempt to retrieve our workflow object
if (cache.get(id) == null) {
try {
// No workflow cache found for the id, check if we have a workflowId for this id
File xmlFile = new File(path);
Document input = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
Node mainNode = input.getFirstChild();
Node workflowMap = XPathAPI.selectSingleNode(mainNode,
"//workflow-map/name-map[@" + xpathId + "='" + id + "']");
if (workflowMap == null && xpathId != "workflow") {
// No workflowId found by this id in cache, so retrieve & use the default workflow
if (cache.get("default") == null) {
Workflow defaultWorkflow = this.getDefaultWorkflow();
cache.put("default", defaultWorkflow);
cache.put(id, defaultWorkflow);
return defaultWorkflow;
} else {
return cache.get("default");
}
} else {
// We have a workflowID so retrieve it & resolve it to a workflow, also store it in our cache
String workflowID = "";
if (xpathId.equalsIgnoreCase("workflow")) {
workflowID = id;
} else {
workflowID = workflowMap.getAttributes().getNamedItem("workflow").getTextContent();
}
Node workflowNode = XPathAPI.selectSingleNode(mainNode, "//workflow[@id='" + workflowID + "']");
Workflow wf = new Workflow(workflowID, getRoles(workflowNode));
Step step = createFirstStep(wf, workflowNode);
wf.setFirstStep(step);
cache.put(id, wf);
return wf;
}
} catch (Exception e) {
log.error("Error while retrieving workflow mapping of " + xpathId + ": " + id, e);
throw new WorkflowConfigurationException(
"Error while retrieving workflow mapping of " + xpathId + ": " + id);
} }
} else {
return cache.get(id);
} }
throw new WorkflowConfigurationException(
"Error while retrieving workflow by the following name: " + workflowName);
} }
/**
* Gets the default workflow, i.e. the workflow that is mapped to collection=default in workflow.xml
*/
public Workflow getDefaultWorkflow() throws WorkflowConfigurationException {
//Initialize our cache if we have none
if (collectionHandleToWorkflowCache == null) {
collectionHandleToWorkflowCache = new HashMap<>();
}
//attempt to retrieve default workflow
if (collectionHandleToWorkflowCache.get("default") == null) {
try {
File xmlFile = new File(path);
Document input = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
Node mainNode = input.getFirstChild();
String workflowID = XPathAPI
.selectSingleNode(mainNode, "//workflow-map/name-map[@collection='default']")
.getAttributes().getNamedItem("workflow").getTextContent();
if (workflowID == null) {
throw new WorkflowConfigurationException("No default workflow mapping is present");
}
Node workflowNode = XPathAPI.selectSingleNode(mainNode, "//workflow[@id='" + workflowID + "']");
Workflow wf = new Workflow(workflowID, getRoles(workflowNode));
Step step = createFirstStep(wf, workflowNode);
wf.setFirstStep(step);
return wf;
} catch (Exception e) {
log.error("Error while retrieve default workflow", e);
throw new WorkflowConfigurationException("Error while retrieve default workflow");
}
} else {
return collectionHandleToWorkflowCache.get("default");
}
}
/**
* Creates a list of all configured workflows, or returns the cache of this if it was already created
*
* @return List of all configured workflows
* @throws WorkflowConfigurationException
*/
@Override @Override
public List<Workflow> getAllConfiguredWorkflows() throws WorkflowConfigurationException { public Workflow getDefaultWorkflow() {
// Initialize our cache if we have none return this.workflowMapping.get(LEGACY_WORKFLOW_NAME);
if (workflowCache == null || workflowCache.size() == 0) {
workflowCache = new ArrayList<>();
try {
// No workflow cache found; create it
File xmlFile = new File(path);
Document input = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
Node mainNode = input.getFirstChild();
NodeList allWorkflowNodes = XPathAPI.selectNodeList(mainNode, "//workflow");
for (int i = 0; i < allWorkflowNodes.getLength(); i++) {
String workflowID =
allWorkflowNodes.item(i).getAttributes().getNamedItem("id").getTextContent();
Node workflowNode = XPathAPI.selectSingleNode(mainNode, "//workflow[@id='" + workflowID + "']");
Workflow wf = new Workflow(workflowID, getRoles(workflowNode));
Step step = createFirstStep(wf, workflowNode);
wf.setFirstStep(step);
workflowCache.add(wf);
}
} catch (Exception e) {
log.error("Error while creating list of all configure workflows");
throw new WorkflowConfigurationException("Error while creating list of all configure workflows");
}
}
return workflowCache;
} }
/**
* Return a list of collections handles that are mapped to the given workflow in the workflow configuration.
* Makes use of a cache so it only retrieves the workflowName->List<collectionHandle> if it's not cached
*
* @param workflowName Name of workflow we want the collections of that are mapped to is
* @return List of collection handles mapped to the requested workflow
* @throws WorkflowConfigurationException
*/
@Override @Override
public List<String> getCollectionHandlesMappedToWorklow(String workflowName) throws WorkflowConfigurationException { public List<Workflow> getAllConfiguredWorkflows() {
// Initialize our cache if we have none return new ArrayList<>(this.workflowMapping.values());
if (workflowNameToCollectionHandlesCache == null) {
workflowNameToCollectionHandlesCache = new HashMap<>();
}
// Attempt to retrieve the corresponding collections
if (workflowNameToCollectionHandlesCache.get(workflowName) == null) {
try {
// No collections cached for this workflow, create it
File xmlFile = new File(path);
Document input = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
Node mainNode = input.getFirstChild();
NodeList allWorkflowNodes = XPathAPI.selectNodeList(mainNode, "//workflow-map/name-map" +
"[@workflow='" + workflowName + "']");
List<String> collectionsHandlesMappedToThisWorkflow = new ArrayList<>();
for (int i = 0; i < allWorkflowNodes.getLength(); i++) {
String collectionHandle =
allWorkflowNodes.item(i).getAttributes().getNamedItem("collection").getTextContent();
collectionsHandlesMappedToThisWorkflow.add(collectionHandle);
}
workflowNameToCollectionHandlesCache.put(workflowName, collectionsHandlesMappedToThisWorkflow);
} catch (Exception e) {
log.error("Error while getting collections mapped to this workflow: " + workflowName);
throw new WorkflowConfigurationException(
"Error while getting collections mapped to this workflow: " + workflowName);
}
}
if (workflowNameToCollectionHandlesCache.get(workflowName) != null) {
return workflowNameToCollectionHandlesCache.get(workflowName);
} else {
return new ArrayList<>();
}
} }
/**
* Check to see if there is a workflow configured by the given name
*
* @param workflowName Name of a possible configured workflow
* @return True if there is a workflow configured by this name, false otherwise
* @throws WorkflowConfigurationException
*/
@Override @Override
public boolean workflowByThisNameExists(String workflowName) throws WorkflowConfigurationException { public List<String> getCollectionHandlesMappedToWorklow(String workflowName) {
List<Workflow> allConfiguredWorkflows = this.getAllConfiguredWorkflows(); List<String> collectionsMapped = new ArrayList<>();
for (Workflow workflow : allConfiguredWorkflows) { for (String handle : this.workflowMapping.keySet()) {
if (workflow.getID().equalsIgnoreCase(workflowName)) { if (this.workflowMapping.get(handle).getID().equals(workflowName)) {
collectionsMapped.add(handle);
}
}
return collectionsMapped;
}
@Override
public boolean workflowByThisNameExists(String workflowName) {
for (Workflow workflow : this.workflowMapping.values()) {
if (workflow.getID().equals(workflowName)) {
return true; return true;
} }
} }
return false; return false;
} }
/**
* Check to see if the given workflowName is the workflow configured to be default for collections @Override
* @param workflowName Name of workflow to check if default
* @return True if given workflowName is the workflow mapped to default for collections, otherwise false
* @throws WorkflowConfigurationException
*/
public boolean isDefaultWorkflow(String workflowName) throws WorkflowConfigurationException { public boolean isDefaultWorkflow(String workflowName) throws WorkflowConfigurationException {
try { try {
Workflow defaultWorkflow = this.getDefaultWorkflow(); Workflow defaultWorkflow = this.getDefaultWorkflow();
return (defaultWorkflow.getID().equalsIgnoreCase(workflowName)); return (defaultWorkflow.getID().equals(workflowName));
} catch (Exception e) { } catch (Exception e) {
log.error("Error while trying to check if " + workflowName + " is the default workflow", e); log.error("Error while trying to check if " + workflowName + " is the default workflow", e);
throw new WorkflowConfigurationException("Error while trying to check if " + workflowName throw new WorkflowConfigurationException("Error while trying to check if " + workflowName
@@ -291,161 +113,4 @@ public class XmlWorkflowFactoryImpl implements XmlWorkflowFactory {
} }
protected Step createFirstStep(Workflow workflow, Node workflowNode)
throws TransformerException, WorkflowConfigurationException {
String firstStepID = workflowNode.getAttributes().getNamedItem("start").getTextContent();
Node stepNode = XPathAPI.selectSingleNode(workflowNode, "step[@id='" + firstStepID + "']");
if (stepNode == null) {
throw new WorkflowConfigurationException(
"First step does not exist for workflow: " + workflowNode.getAttributes().getNamedItem("id")
.getTextContent());
}
Node roleNode = stepNode.getAttributes().getNamedItem("role");
Role role = null;
if (roleNode != null) {
role = workflow.getRoles().get(roleNode.getTextContent());
}
String userSelectionActionID = stepNode.getAttributes().getNamedItem("userSelectionMethod").getTextContent();
UserSelectionActionConfig userSelection = createUserAssignmentActionConfig(userSelectionActionID);
return new Step(firstStepID, workflow, role, userSelection, getStepActionConfigs(stepNode),
getStepOutcomes(stepNode), getNbRequiredUser(stepNode));
}
protected Map<Integer, String> getStepOutcomes(Node stepNode)
throws TransformerException, WorkflowConfigurationException {
try {
NodeList outcomesNodeList = XPathAPI.selectNodeList(stepNode, "outcomes/step");
Map<Integer, String> outcomes = new HashMap<Integer, String>();
//Add our outcome, should it be null it will be interpreted as the end of the line (last step)
for (int i = 0; i < outcomesNodeList.getLength(); i++) {
Node outcomeNode = outcomesNodeList.item(i);
int index = Integer.parseInt(outcomeNode.getAttributes().getNamedItem("status").getTextContent());
if (index < 0) {
throw new WorkflowConfigurationException(
"Outcome configuration error for step: " + stepNode.getAttributes().getNamedItem("id")
.getTextContent());
}
outcomes.put(index, outcomeNode.getTextContent());
}
return outcomes;
} catch (Exception e) {
log.error("Outcome configuration error for step: " +
stepNode.getAttributes().getNamedItem("id").getTextContent(), e);
throw new WorkflowConfigurationException(
"Outcome configuration error for step: " + stepNode.getAttributes().getNamedItem("id")
.getTextContent());
}
}
protected int getNbRequiredUser(Node stepnode) {
if (stepnode.getAttributes().getNamedItem("requiredUsers") != null) {
return Integer.parseInt(stepnode.getAttributes().getNamedItem("requiredUsers").getTextContent());
}
return 1;
}
private static List<String> getStepActionConfigs(Node stepNode) throws TransformerException {
NodeList actionConfigNodes = XPathAPI.selectNodeList(stepNode, "actions/action");
List<String> actionConfigIDs = new ArrayList<String>();
for (int i = 0; i < actionConfigNodes.getLength(); i++) {
actionConfigIDs.add(actionConfigNodes.item(i).getAttributes().getNamedItem("id").getTextContent());
}
return actionConfigIDs;
}
@Override
public Step createStep(Workflow workflow, String stepID) throws WorkflowConfigurationException, IOException {
try {
File xmlFile = new File(path);
Document input = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
Node mainNode = input.getFirstChild();
Node stepNode = XPathAPI.selectSingleNode(mainNode, "//workflow[@id='" + workflow.getID()
+ "']/step[@id='" + stepID + "']");
if (stepNode == null) {
throw new WorkflowConfigurationException("Step does not exist for workflow: " + workflow.getID());
}
Node roleNode = stepNode.getAttributes().getNamedItem("role");
Role role = null;
if (roleNode != null) {
role = workflow.getRoles().get(roleNode.getTextContent());
}
String userSelectionActionID = stepNode.getAttributes().getNamedItem("userSelectionMethod")
.getTextContent();
UserSelectionActionConfig userSelection = createUserAssignmentActionConfig(userSelectionActionID);
return new Step(stepID, workflow, role, userSelection, getStepActionConfigs(stepNode),
getStepOutcomes(stepNode), getNbRequiredUser(stepNode));
} catch (Exception e) {
log.error("Error while creating step with :" + stepID, e);
throw new WorkflowConfigurationException(
"Step: " + stepID + " does not exist for workflow: " + workflow.getID());
}
}
protected UserSelectionActionConfig createUserAssignmentActionConfig(String userSelectionActionID) {
return DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName(userSelectionActionID, UserSelectionActionConfig.class);
}
@Override
public WorkflowActionConfig createWorkflowActionConfig(String actionID) {
return DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName(actionID, WorkflowActionConfig.class);
}
protected LinkedHashMap<String, Role> getRoles(Node workflowNode) throws WorkflowConfigurationException {
NodeList roleNodes = null;
try {
roleNodes = XPathAPI.selectNodeList(workflowNode, "roles/role");
} catch (Exception e) {
log.error("Error while resolving nodes", e);
throw new WorkflowConfigurationException("Error while retrieving roles");
}
LinkedHashMap<String, Role> roles = new LinkedHashMap<String, Role>();
for (int i = 0; i < roleNodes.getLength(); i++) {
String roleID = roleNodes.item(i).getAttributes().getNamedItem("id").getTextContent();
String roleName = roleNodes.item(i).getAttributes().getNamedItem("name").getTextContent();
Node descriptionNode = roleNodes.item(i).getAttributes().getNamedItem("description");
String roleDescription = null;
if (descriptionNode != null) {
roleDescription = descriptionNode.getTextContent();
}
Node scopeNode = roleNodes.item(i).getAttributes().getNamedItem("scope");
String roleScope = null;
if (scopeNode != null) {
roleScope = scopeNode.getTextContent();
}
Node internalNode = roleNodes.item(i).getAttributes().getNamedItem("internal");
String roleInternal;
boolean internal = false;
if (internalNode != null) {
roleInternal = internalNode.getTextContent();
internal = Boolean.parseBoolean(roleInternal);
}
Role.Scope scope;
if (roleScope == null || roleScope.equalsIgnoreCase("collection")) {
scope = Role.Scope.COLLECTION;
} else if (roleScope.equalsIgnoreCase("item")) {
scope = Role.Scope.ITEM;
} else if (roleScope.equalsIgnoreCase("repository")) {
scope = Role.Scope.REPOSITORY;
} else {
throw new WorkflowConfigurationException(
"An invalid role scope has been specified it must either be item or collection.");
}
Role role = new Role(roleID, roleName, roleDescription, internal, scope);
roles.put(roleID, role);
}
return roles;
}
} }

View File

@@ -7,14 +7,11 @@
*/ */
package org.dspace.xmlworkflow.factory; package org.dspace.xmlworkflow.factory;
import java.io.IOException;
import java.util.List; import java.util.List;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.xmlworkflow.WorkflowConfigurationException; import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.Workflow; import org.dspace.xmlworkflow.state.Workflow;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
/** /**
* The xmlworkflowfactory is responsible for parsing the * The xmlworkflowfactory is responsible for parsing the
@@ -31,21 +28,58 @@ public interface XmlWorkflowFactory {
public final String LEGACY_WORKFLOW_NAME = "default"; public final String LEGACY_WORKFLOW_NAME = "default";
/**
* Retrieve the workflow configuration for a single collection
*
* @param collection the collection for which we want our workflow
* @return the workflow configuration
* @throws WorkflowConfigurationException occurs if there is a configuration error in the workflow
*/
public Workflow getWorkflow(Collection collection) throws WorkflowConfigurationException; public Workflow getWorkflow(Collection collection) throws WorkflowConfigurationException;
/**
* Retrieves the workflow configuration by name
*
* @param workflowName the name for which we want our workflow
* @return the workflow configuration
* @throws WorkflowConfigurationException occurs if there is no workflow configured by that name
*/
public Workflow getWorkflowByName(String workflowName) throws WorkflowConfigurationException; public Workflow getWorkflowByName(String workflowName) throws WorkflowConfigurationException;
public List<Workflow> getAllConfiguredWorkflows() throws WorkflowConfigurationException; /**
* Creates a list of all configured workflows, or returns the cache of this if it was already created
*
* @return List of all configured workflows
*/
public List<Workflow> getAllConfiguredWorkflows();
public boolean workflowByThisNameExists(String workflowName) throws WorkflowConfigurationException; /**
* Check to see if there is a workflow configured by the given name
*
* @param workflowName Name of a possible configured workflow
* @return True if there is a workflow configured by this name, false otherwise
* @throws WorkflowConfigurationException occurs if there is no workflow configured by that name
*/
public boolean workflowByThisNameExists(String workflowName);
/**
* Check to see if the given workflowName is the workflow configured to be default for collections
* @param workflowName Name of workflow to check if default
* @return True if given workflowName is the workflow mapped to default for collections, otherwise false
*/
public boolean isDefaultWorkflow(String workflowName) throws WorkflowConfigurationException; public boolean isDefaultWorkflow(String workflowName) throws WorkflowConfigurationException;
public Workflow getDefaultWorkflow() throws WorkflowConfigurationException; /**
* Gets the default workflow, i.e. the workflow that is mapped to collection=default in workflow.xml
*/
public Workflow getDefaultWorkflow();
public List<String> getCollectionHandlesMappedToWorklow(String workflowName) throws WorkflowConfigurationException; /**
* Return a list of collections handles that are mapped to the given workflow in the workflow configuration.
public Step createStep(Workflow workflow, String stepID) throws WorkflowConfigurationException, IOException; * Makes use of a cache so it only retrieves the workflowName->List<collectionHandle> if it's not cached
*
public WorkflowActionConfig createWorkflowActionConfig(String actionID); * @param workflowName Name of workflow we want the collections of that are mapped to is
* @return List of collection handles mapped to the requested workflow
*/
public List<String> getCollectionHandlesMappedToWorklow(String workflowName);
} }

View File

@@ -7,22 +7,22 @@
*/ */
package org.dspace.xmlworkflow.state; package org.dspace.xmlworkflow.state;
import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.workflow.WorkflowException;
import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.Role;
import org.dspace.xmlworkflow.WorkflowConfigurationException; import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.actions.UserSelectionActionConfig; import org.dspace.xmlworkflow.state.actions.UserSelectionActionConfig;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
import org.dspace.xmlworkflow.storedcomponents.service.InProgressUserService; import org.dspace.xmlworkflow.storedcomponents.service.InProgressUserService;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
/** /**
* A class that contains all the data of an xlworkflow step * A class that contains all the data of an xlworkflow step
@@ -32,46 +32,36 @@ import org.dspace.xmlworkflow.storedcomponents.service.InProgressUserService;
* @author Ben Bosman (ben at atmire dot com) * @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com) * @author Mark Diggory (markd at atmire dot com)
*/ */
public class Step { public class Step implements BeanNameAware {
protected InProgressUserService inProgressUserService = XmlWorkflowServiceFactory.getInstance()
.getInProgressUserService();
protected XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory();
@Autowired
protected InProgressUserService inProgressUserService;
private UserSelectionActionConfig userSelectionMethod; private UserSelectionActionConfig userSelectionMethod;
private HashMap<String, WorkflowActionConfig> actionConfigsMap; private List<WorkflowActionConfig> actions;
private List<String> actionConfigsList; private Map<Integer, Step> outcomes = new HashMap<>();
private Map<Integer, String> outcomes;
private String id; private String id;
private Role role; private Role role;
private Workflow workflow; private Workflow workflow;
private int requiredUsers; private int requiredUsers = 1;
public Step(String id, Workflow workflow, Role role, UserSelectionActionConfig userSelectionMethod, /**
List<String> actionConfigsList, Map<Integer, String> outcomes, int requiredUsers) { * Get an WorkflowActionConfiguration object for the provided action identifier
this.actionConfigsMap = new HashMap<>(); * @param actionID the action id for which we want our action config
this.outcomes = outcomes; * @return The corresponding WorkflowActionConfiguration
this.userSelectionMethod = userSelectionMethod; * @throws WorkflowConfigurationException occurs if the provided action isn't part of the step
this.role = role; */
this.actionConfigsList = actionConfigsList; public WorkflowActionConfig getActionConfig(String actionID) throws WorkflowConfigurationException {
this.id = id; // First check the userSelectionMethod as this is not a regular "action"
userSelectionMethod.setStep(this); if (userSelectionMethod != null && StringUtils.equals(userSelectionMethod.getId(), actionID)) {
this.requiredUsers = requiredUsers; return userSelectionMethod;
this.workflow = workflow;
}
public WorkflowActionConfig getActionConfig(String actionID) {
if (actionConfigsMap.get(actionID) != null) {
return actionConfigsMap.get(actionID);
} else {
WorkflowActionConfig action = xmlWorkflowFactory.createWorkflowActionConfig(actionID);
action.setStep(this);
actionConfigsMap.put(actionID, action);
return action;
} }
for (WorkflowActionConfig actionConfig : actions) {
if (StringUtils.equals(actionConfig.getId(), actionID)) {
return actionConfig;
}
}
throw new WorkflowConfigurationException("Action configuration not found for: " + actionID);
} }
/** /**
@@ -80,8 +70,7 @@ public class Step {
* @return a boolean * @return a boolean
*/ */
public boolean hasUI() { public boolean hasUI() {
for (String actionConfigId : actionConfigsList) { for (WorkflowActionConfig actionConfig : actions) {
WorkflowActionConfig actionConfig = getActionConfig(actionConfigId);
if (actionConfig.requiresUI()) { if (actionConfig.requiresUI()) {
return true; return true;
} }
@@ -89,8 +78,12 @@ public class Step {
return false; return false;
} }
public String getNextStepID(int outcome) /**
throws WorkflowException, IOException, WorkflowConfigurationException, SQLException { * Get the next step based on out the outcome
* @param outcome the outcome of the previous step
* @return the next stepp or NULL if there is no step configured for this outcome
*/
public Step getNextStep(int outcome) {
return outcomes.get(outcome); return outcomes.get(outcome);
} }
@@ -109,9 +102,9 @@ public class Step {
} }
public WorkflowActionConfig getNextAction(WorkflowActionConfig currentAction) { public WorkflowActionConfig getNextAction(WorkflowActionConfig currentAction) {
int index = actionConfigsList.indexOf(currentAction.getId()); int index = actions.indexOf(currentAction);
if (index < actionConfigsList.size() - 1) { if (index < actions.size() - 1) {
return getActionConfig(actionConfigsList.get(index + 1)); return actions.get(index + 1);
} else { } else {
return null; return null;
} }
@@ -125,7 +118,6 @@ public class Step {
return workflow; return workflow;
} }
/** /**
* Check if enough users have finished this step for it to continue * Check if enough users have finished this step for it to continue
* *
@@ -146,6 +138,78 @@ public class Step {
return role; return role;
} }
// public boolean skipStep() { /**
// } * Set the user selection configuration, this is required as every step requires one
* @param userSelectionMethod the user selection method configuration
*/
@Required
public void setUserSelectionMethod(UserSelectionActionConfig userSelectionMethod) {
this.userSelectionMethod = userSelectionMethod;
userSelectionMethod.setStep(this);
}
/**
* Set the outcomes as a map, if no outcomes are configured this step will be last step in the workflow
* @param outcomes the map containing the outcomes.
*/
public void setOutcomes(Map<Integer, Step> outcomes) {
this.outcomes = outcomes;
}
/**
* Get the processing actions for the step. Processing actions contain the logic required to execute the required
* operations in each step.
* @return the actions configured for this step
*/
public List<WorkflowActionConfig> getActions() {
return actions;
}
/**
* Set the processing actions for the step. Processing actions contain the logic required to execute the required
* operations in each step.
* @param actions the list of actions
*/
@Required
public void setActions(List<WorkflowActionConfig> actions) {
for (WorkflowActionConfig workflowActionConfig : actions) {
workflowActionConfig.setStep(this);
}
this.actions = actions;
}
/**
* Set the workflow this step belongs to
* @param workflow the workflow configuration
*/
protected void setWorkflow(Workflow workflow) {
this.workflow = workflow;
}
/**
* Store the name of the bean in the identifier
* @param s the bean name
*/
@Override
public void setBeanName(String s) {
id = s;
}
/**
* Set the number of required users that need to execute this step before it is completed,
* the default is a single user
* @param requiredUsers the number of required users
*/
public void setRequiredUsers(int requiredUsers) {
this.requiredUsers = requiredUsers;
}
/**
* Set the role of which users role should execute this step
* @param role the role to be configured for this step
*/
public void setRole(Role role) {
this.role = role;
}
} }

View File

@@ -7,18 +7,18 @@
*/ */
package org.dspace.xmlworkflow.state; package org.dspace.xmlworkflow.state;
import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.List;
import java.util.Map;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.workflow.WorkflowException;
import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.Role;
import org.dspace.xmlworkflow.WorkflowConfigurationException; import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Required;
/** /**
* Class that contains all the steps and roles involved in a certain * Class that contains all the steps and roles involved in a certain
@@ -29,21 +29,11 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
* @author Ben Bosman (ben at atmire dot com) * @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com) * @author Mark Diggory (markd at atmire dot com)
*/ */
public class Workflow { public class Workflow implements BeanNameAware {
protected XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory();
private String id; private String id;
private Step firstStep; private Step firstStep;
private HashMap<String, Step> steps; private List<Step> steps;
private LinkedHashMap<String, Role> roles;
public Workflow(String workflowID, LinkedHashMap<String, Role> roles) {
this.id = workflowID;
this.roles = roles;
this.steps = new HashMap<String, Step>();
}
public Step getFirstStep() { public Step getFirstStep() {
return firstStep; return firstStep;
@@ -56,46 +46,72 @@ public class Workflow {
/* /*
* Return a step with a given id * Return a step with a given id
*/ */
public Step getStep(String stepID) throws WorkflowConfigurationException, IOException { public Step getStep(String stepID) throws WorkflowConfigurationException {
if (steps.get(id) != null) { for (Step step : steps) {
return steps.get(id); if (step.getId().equals(stepID)) {
} else { return step;
Step step = xmlWorkflowFactory.createStep(this, stepID);
if (step == null) {
throw new WorkflowConfigurationException("Step definition not found for: " + stepID);
} }
steps.put(stepID, step);
return step;
} }
throw new WorkflowConfigurationException("Step definition not found for: " + stepID);
} }
public Step getNextStep(Context context, XmlWorkflowItem wfi, Step currentStep, int outcome) public Step getNextStep(Context context, XmlWorkflowItem wfi, Step currentStep, int outcome)
throws IOException, WorkflowConfigurationException, WorkflowException, SQLException { throws WorkflowConfigurationException, SQLException {
String nextStepID = currentStep.getNextStepID(outcome); Step nextStep = currentStep.getNextStep(outcome);
if (nextStepID != null) { if (nextStep != null) {
Step nextStep = getStep(nextStepID);
if (nextStep == null) {
throw new WorkflowException(
"Error while processing outcome, the following action was undefined: " + nextStepID);
}
if (nextStep.isValidStep(context, wfi)) { if (nextStep.isValidStep(context, wfi)) {
return nextStep; return nextStep;
} else { } else {
return getNextStep(context, wfi, nextStep, 0); return getNextStep(context, wfi, nextStep, ActionResult.OUTCOME_COMPLETE);
} }
} else { } else {
//No next step, archive it //No next step, archive it
return null; return null;
} }
} }
@Required
public void setFirstStep(Step firstStep) { public void setFirstStep(Step firstStep) {
firstStep.setWorkflow(this);
this.firstStep = firstStep; this.firstStep = firstStep;
} }
public HashMap<String, Role> getRoles() { /**
* Get the steps that need to be executed in this workflow before the item is archived
* @return the workflow steps
*/
public List<Step> getSteps() {
return steps;
}
/**
* Set the steps that need to be executed in this workflow before the item is archived
* @param steps the workflow steps
*/
@Required
public void setSteps(List<Step> steps) {
for (Step step : steps) {
step.setWorkflow(this);
}
this.steps = steps;
}
/**
* Get the roles that are used in this workflow
* @return a map containing the roles, the role name will the key, the role itself the value
*/
public Map<String, Role> getRoles() {
Map<String, Role> roles = new HashMap<>();
for (Step step : steps) {
if (step.getRole() != null) {
roles.put(step.getRole().getName(), step.getRole());
}
}
return roles; return roles;
} }
@Override
public void setBeanName(String s) {
id = s;
}
} }

View File

@@ -0,0 +1,17 @@
--
-- 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/
--
---------------------------------------------------------------
-- DS-4239 Migrate the workflow.xml to spring
---------------------------------------------------------------
-- This script will rename the default workflow "default" name
-- to the new "defaultWorkflow" identifier
---------------------------------------------------------------
UPDATE cwf_pooltask SET workflow_id='defaultWorkflow' WHERE workflow_id='default';
UPDATE cwf_claimtask SET workflow_id='defaultWorkflow' WHERE workflow_id='default';

View File

@@ -0,0 +1,17 @@
--
-- 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/
--
---------------------------------------------------------------
-- DS-4239 Migrate the workflow.xml to spring
---------------------------------------------------------------
-- This script will rename the default workflow "default" name
-- to the new "defaultWorkflow" identifier
---------------------------------------------------------------
UPDATE cwf_pooltask SET workflow_id='defaultWorkflow' WHERE workflow_id='default';
UPDATE cwf_claimtask SET workflow_id='defaultWorkflow' WHERE workflow_id='default';

View File

@@ -0,0 +1,17 @@
--
-- 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/
--
---------------------------------------------------------------
-- DS-4239 Migrate the workflow.xml to spring
---------------------------------------------------------------
-- This script will rename the default workflow "default" name
-- to the new "defaultWorkflow" identifier
---------------------------------------------------------------
UPDATE cwf_pooltask SET workflow_id='defaultWorkflow' WHERE workflow_id='default';
UPDATE cwf_claimtask SET workflow_id='defaultWorkflow' WHERE workflow_id='default';

View File

@@ -22,7 +22,6 @@ import org.dspace.content.Collection;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService; import org.dspace.handle.service.HandleService;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -64,25 +63,20 @@ public class WorkflowDefinitionController {
@GetMapping("{workflowName}/collections") @GetMapping("{workflowName}/collections")
public List<CollectionRest> get(HttpServletRequest request, HttpServletResponse response, public List<CollectionRest> get(HttpServletRequest request, HttpServletResponse response,
@PathVariable String workflowName) throws SQLException { @PathVariable String workflowName) throws SQLException {
try { if (xmlWorkflowFactory.workflowByThisNameExists(workflowName)) {
if (xmlWorkflowFactory.workflowByThisNameExists(workflowName)) { List<String> collectionsHandlesMappedToWorkflow
List<String> collectionsHandlesMappedToWorkflow = xmlWorkflowFactory.getCollectionHandlesMappedToWorklow(workflowName);
= xmlWorkflowFactory.getCollectionHandlesMappedToWorklow(workflowName); List<CollectionRest> collectionResourcesFromHandles = new ArrayList<>();
List<CollectionRest> collectionResourcesFromHandles = new ArrayList<>(); for (String handle : collectionsHandlesMappedToWorkflow) {
for (String handle : collectionsHandlesMappedToWorkflow) { Context context = ContextUtil.obtainContext(request);
Context context = ContextUtil.obtainContext(request); Collection collection = (Collection) handleService.resolveToObject(context, handle);
Collection collection = (Collection) handleService.resolveToObject(context, handle); if (collection != null) {
if (collection != null) { collectionResourcesFromHandles.add(converter.toRest(collection, utils.obtainProjection()));
collectionResourcesFromHandles.add(converter.toRest(collection, utils.obtainProjection()));
}
} }
return collectionResourcesFromHandles;
} else {
throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured");
} }
} catch (WorkflowConfigurationException e) { return collectionResourcesFromHandles;
// TODO ? Better exception? } else {
throw new RuntimeException(e.getMessage(), e); throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured");
} }
} }
} }

View File

@@ -42,27 +42,21 @@ public class WorkflowDefinitionRestRepository extends DSpaceRestRepository<Workf
@Override @Override
public WorkflowDefinitionRest findOne(Context context, String workflowName) { public WorkflowDefinitionRest findOne(Context context, String workflowName) {
try { if (xmlWorkflowFactory.workflowByThisNameExists(workflowName)) {
if (xmlWorkflowFactory.workflowByThisNameExists(workflowName)) { try {
return converter.toRest(xmlWorkflowFactory.getWorkflowByName(workflowName), utils.obtainProjection()); return converter.toRest(xmlWorkflowFactory.getWorkflowByName(workflowName), utils.obtainProjection());
} else { } catch (WorkflowConfigurationException e) {
throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured"); throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured");
} }
} catch (WorkflowConfigurationException e) { } else {
// TODO ? Better exception? throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured");
throw new RuntimeException(e.getMessage(), e);
} }
} }
@Override @Override
public Page<WorkflowDefinitionRest> findAll(Context context, Pageable pageable) { public Page<WorkflowDefinitionRest> findAll(Context context, Pageable pageable) {
try { List<Workflow> workflows = xmlWorkflowFactory.getAllConfiguredWorkflows();
List<Workflow> workflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); return converter.toRestPage(workflows, pageable, workflows.size(), utils.obtainProjection());
return converter.toRestPage(workflows, pageable, workflows.size(), utils.obtainProjection());
} catch (WorkflowConfigurationException e) {
// TODO ? Better exception?
throw new RuntimeException(e.getMessage(), e);
}
} }
/** /**
* GET endpoint that returns the workflow definition that applies to a specific collection eventually fallback * GET endpoint that returns the workflow definition that applies to a specific collection eventually fallback
@@ -73,17 +67,17 @@ public class WorkflowDefinitionRestRepository extends DSpaceRestRepository<Workf
*/ */
@SearchRestMethod(name = "findByCollection") @SearchRestMethod(name = "findByCollection")
public WorkflowDefinitionRest findByCollection(@Parameter(value = "uuid") UUID collectionId) throws SQLException { public WorkflowDefinitionRest findByCollection(@Parameter(value = "uuid") UUID collectionId) throws SQLException {
try { Context context = obtainContext();
Context context = obtainContext(); Collection collectionFromUuid = collectionService.find(context, collectionId);
Collection collectionFromUuid = collectionService.find(context, collectionId); if (collectionFromUuid != null) {
if (collectionFromUuid != null) { try {
return converter.toRest(xmlWorkflowFactory.getWorkflow(collectionFromUuid), utils.obtainProjection()); return converter.toRest(xmlWorkflowFactory.getWorkflow(collectionFromUuid), utils.obtainProjection());
} else { } catch (WorkflowConfigurationException e) {
throw new ResourceNotFoundException("Collection with id " + collectionId + " not found"); throw new ResourceNotFoundException("No workflow for this collection fault and " +
"no defaultWorkflow found");
} }
} catch (WorkflowConfigurationException e) { } else {
// TODO ? Better exception? throw new ResourceNotFoundException("Collection with id " + collectionId + " not found");
throw new RuntimeException(e.getMessage(), e);
} }
} }

View File

@@ -19,6 +19,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CollectionBuilder;
import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.CommunityBuilder;
import org.dspace.app.rest.model.WorkflowDefinitionRest; import org.dspace.app.rest.model.WorkflowDefinitionRest;
@@ -84,16 +85,18 @@ public class WorkflowDefinitionRestRepositoryIT extends AbstractControllerIntegr
firstNonDefaultWorkflowName = workflow.getID(); firstNonDefaultWorkflowName = workflow.getID();
} }
} }
//When we call this facets endpoint if (StringUtils.isNotBlank(firstNonDefaultWorkflowName)) {
getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + firstNonDefaultWorkflowName)) //When we call this facets endpoint
//We expect a 200 OK status getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" + firstNonDefaultWorkflowName))
.andExpect(status().isOk()) //We expect a 200 OK status
//There needs to be a self link to this endpoint .andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", containsString(WORKFLOW_DEFINITIONS_ENDPOINT))) //There needs to be a self link to this endpoint
// its name is name of non-default workflow .andExpect(jsonPath("$._links.self.href", containsString(WORKFLOW_DEFINITIONS_ENDPOINT)))
.andExpect(jsonPath("$.name", equalToIgnoringCase(firstNonDefaultWorkflowName))) // its name is name of non-default workflow
// is not default .andExpect(jsonPath("$.name", equalToIgnoringCase(firstNonDefaultWorkflowName)))
.andExpect(jsonPath("$.isDefault", is(false))); // is not default
.andExpect(jsonPath("$.isDefault", is(false)));
}
} }
@Test @Test
@@ -190,25 +193,26 @@ public class WorkflowDefinitionRestRepositoryIT extends AbstractControllerIntegr
} }
} }
List<String> handlesOfMappedCollections if (StringUtils.isNotBlank(firstNonDefaultWorkflowName)) {
= xmlWorkflowFactory.getCollectionHandlesMappedToWorklow(firstNonDefaultWorkflowName); List<String> handlesOfMappedCollections
= xmlWorkflowFactory.getCollectionHandlesMappedToWorklow(firstNonDefaultWorkflowName);
//When we call this facets endpoint //When we call this facets endpoint
if (handlesOfMappedCollections.size() > 0) { if (handlesOfMappedCollections.size() > 0) {
//returns array of collection jsons that are mapped to given workflow //returns array of collection jsons that are mapped to given workflow
MvcResult result = getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" MvcResult result = getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/"
+ firstNonDefaultWorkflowName + "/collections")).andReturn(); + firstNonDefaultWorkflowName + "/collections")).andReturn();
String response = result.getResponse().getContentAsString(); String response = result.getResponse().getContentAsString();
JSONArray collectionsResult = new JSONArray(response); JSONArray collectionsResult = new JSONArray(response);
assertEquals(collectionsResult.length(), handlesOfMappedCollections.size()); assertEquals(collectionsResult.length(), handlesOfMappedCollections.size());
} else { } else {
//no collections mapped to this workflow //no collections mapped to this workflow
getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/" getClient().perform(get(WORKFLOW_DEFINITIONS_ENDPOINT + "/"
+ firstNonDefaultWorkflowName + "/collections")) + firstNonDefaultWorkflowName + "/collections"))
//We expect a 200 OK status //We expect a 200 OK status
.andExpect(status().isOk()) .andExpect(status().isOk())
//results in empty list //results in empty list
.andExpect(jsonPath("$", empty())); .andExpect(jsonPath("$", empty()));
}
} }
} }

View File

@@ -115,7 +115,6 @@
<bean class="org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItemServiceImpl"/> <bean class="org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItemServiceImpl"/>
<bean class="org.dspace.xmlworkflow.XmlWorkflowServiceImpl"/> <bean class="org.dspace.xmlworkflow.XmlWorkflowServiceImpl"/>
<bean class="org.dspace.xmlworkflow.WorkflowRequirementsServiceImpl"/> <bean class="org.dspace.xmlworkflow.WorkflowRequirementsServiceImpl"/>
<bean class="org.dspace.xmlworkflow.XmlWorkflowFactoryImpl"/>
</beans> </beans>

View File

@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean class="org.dspace.xmlworkflow.XmlWorkflowFactoryImpl">
<property name="workflowMapping">
<util:map>
<entry key="defaultWorkflow"
value-ref="defaultWorkflow"/>
<!-- <entry key="123456789/4" value-ref="selectSingleReviewer"/>-->
<!-- <entry key="123456789/5" value-ref="scoreReview"/>-->
</util:map>
</property>
</bean>
<!--Standard DSpace workflow-->
<bean name="defaultWorkflow" class="org.dspace.xmlworkflow.state.Workflow">
<property name="firstStep" ref="reviewstep"/>
<property name="steps">
<util:list>
<ref bean="reviewstep"/>
<ref bean="editstep"/>
<ref bean="finaleditstep"/>
</util:list>
</property>
</bean>
<bean name="reviewstep" class="org.dspace.xmlworkflow.state.Step">
<property name="userSelectionMethod" ref="claimaction"/>
<property name="role">
<bean name="reviewer" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).COLLECTION}"/>
<property name="name" value="Reviewer"/>
<property name="description"
value="The people responsible for this step are able to edit the metadata of incoming submissions, and then accept or reject them."/>
</bean>
</property>
<property name="outcomes">
<util:map>
<entry key="#{ T(org.dspace.xmlworkflow.state.actions.ActionResult).OUTCOME_COMPLETE}"
value-ref="editstep"/>
</util:map>
</property>
<property name="actions">
<util:list>
<ref bean="reviewaction"/>
</util:list>
</property>
</bean>
<bean name="editstep" class="org.dspace.xmlworkflow.state.Step">
<property name="userSelectionMethod" ref="claimaction"/>
<property name="role">
<bean name="editor" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).COLLECTION}"/>
<property name="name" value="Editor"/>
<property name="description"
value="The people responsible for this step are able to edit the metadata of incoming submissions, and then accept or reject them."/>
</bean>
</property>
<property name="outcomes">
<util:map>
<entry key="#{ T(org.dspace.xmlworkflow.state.actions.ActionResult).OUTCOME_COMPLETE}"
value-ref="finaleditstep"/>
</util:map>
</property>
<property name="actions">
<list>
<ref bean="editaction"/>
</list>
</property>
</bean>
<bean name="finaleditstep" class="org.dspace.xmlworkflow.state.Step">
<property name="userSelectionMethod" ref="claimaction"/>
<property name="role">
<bean name="finaleditor" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).COLLECTION}"/>
<property name="name" value="Final Editor"/>
<property name="description"
value="The people responsible for this step are able to edit the metadata of incoming submissions, but will not be able to reject them."/>
</bean>
</property>
<property name="actions">
<list>
<ref bean="finaleditaction"/>
</list>
</property>
</bean>
<!--Workflow where a reviewManager can select a single review who will then either accept/reject the item-->
<bean name="selectSingleReviewer" class="org.dspace.xmlworkflow.state.Workflow">
<property name="firstStep" ref="selectReviewerStep"/>
<property name="steps">
<util:list>
<ref bean="selectReviewerStep"/>
<ref bean="singleUserReviewStep"/>
</util:list>
</property>
</bean>
<bean name="selectReviewerStep" class="org.dspace.xmlworkflow.state.Step">
<property name="userSelectionMethod" ref="claimaction"/>
<property name="role">
<bean name="reviewmanagers" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).REPOSITORY}"/>
<property name="name" value="ReviewManagers"/>
</bean>
</property>
<property name="actions">
<list>
<ref bean="selectrevieweraction" />
</list>
</property>
<property name="outcomes">
<util:map>
<entry key="#{ T(org.dspace.xmlworkflow.state.actions.ActionResult).OUTCOME_COMPLETE}"
value-ref="singleUserReviewStep"/>
</util:map>
</property>
</bean>
<bean name="singleUserReviewStep" class="org.dspace.xmlworkflow.state.Step">
<property name="userSelectionMethod" ref="autoassignAction"/>
<property name="role">
<bean name="reviewer" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).ITEM}"/>
<property name="name" value="Reviewer"/>
</bean>
</property>
<property name="actions">
<list>
<ref bean="singleuserreviewaction" />
</list>
</property>
<property name="outcomes">
<util:map>
<entry key="#{ T(org.dspace.xmlworkflow.state.actions.processingaction.SingleUserReviewAction).OUTCOME_REJECT}" value-ref="selectReviewerStep"/>
</util:map>
</property>
</bean>
<!--Workflow where a number of users will perform reviews on an item and depending on the scores the item will be archived/rejected-->
<bean name="scoreReview" class="org.dspace.xmlworkflow.state.Workflow">
<property name="firstStep" ref="scoreReviewStep"/>
<property name="steps">
<util:list>
<ref bean="scoreReviewStep"/>
<ref bean="evaluationStep"/>
</util:list>
</property>
</bean>
<bean name="scoreReviewStep" class="org.dspace.xmlworkflow.state.Step">
<property name="userSelectionMethod" ref="claimaction"/>
<property name="role">
<bean name="scoreReviewers" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).COLLECTION}"/>
<property name="name" value="ScoreReviewers"/>
</bean>
</property>
<property name="outcomes">
<util:map>
<entry key="#{ T(org.dspace.xmlworkflow.state.actions.ActionResult).OUTCOME_COMPLETE}"
value-ref="evaluationStep"/>
</util:map>
</property>
<property name="actions">
<list>
<ref bean="scorereviewaction"/>
</list>
</property>
<property name="requiredUsers" value="2"/>
</bean>
<bean name="evaluationStep" class="org.dspace.xmlworkflow.state.Step">
<property name="userSelectionMethod" ref="noUserSelectionAction"/>
<property name="actions">
<list>
<ref bean="evaluationaction"/>
</list>
</property>
</bean>
</beans>

View File

@@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<wf-config>
<workflow-map>
<name-map collection="default" workflow="default"/>
<!--<name-map collection="123456789/4" workflow="selectSingleReviewer"/>-->
<!--<name-map collection="123456789/5" workflow="scoreReview"/>-->
</workflow-map>
<!--Standard workflow step-->
<workflow start="reviewstep" id="default">
<roles>
<role id="reviewer" name="Reviewer" description="The people responsible for this step are able to edit the metadata of incoming submissions, and then accept or reject them." />
<role id="editor" name="Editor" description="The people responsible for this step are able to edit the metadata of incoming submissions, and then accept or reject them."/>
<role id="finaleditor" name="Final Editor" description="The people responsible for this step are able to edit the metadata of incoming submissions, but will not be able to reject them."/>
</roles>
<step id="reviewstep" role="reviewer" userSelectionMethod="claimaction">
<outcomes>
<step status="0">editstep</step>
</outcomes>
<actions>
<action id="reviewaction"/>
</actions>
</step>
<step id="editstep" role="editor" userSelectionMethod="claimaction">
<outcomes>
<step status="0">finaleditstep</step>
</outcomes>
<actions>
<action id="editaction"/>
</actions>
</step>
<step id="finaleditstep" role="finaleditor" userSelectionMethod="claimaction">
<actions>
<action id="finaleditaction"/>
</actions>
</step>
</workflow>
<!--Workflow where a reviewManager can select a single review who will then either accept/reject the item-->
<workflow id="selectSingleReviewer" start="selectReviewerStep">
<roles>
<role id="reviewer" name="Reviewer" scope="item" />
<role id="reviewmanagers" name="ReviewManagers" scope="repository"/>
</roles>
<step id="selectReviewerStep" role="reviewmanagers" userSelectionMethod="claimaction">
<outcomes>
<step status="0">singleUserReviewStep</step>
</outcomes>
<actions>
<action id="selectrevieweraction"/>
</actions>
</step>
<step id="singleUserReviewStep" role="reviewer" userSelectionMethod="autoassignAction">
<outcomes>
<step status="1">selectReviewerStep</step>
</outcomes>
<actions>
<action id="singleuserreviewaction"/>
</actions>
</step>
</workflow>
<!--Workflow where a number of users will perform reviews on an item and depending on the scores the item will be archived/rejected-->
<workflow id="scoreReview" start="scoreReviewStep">
<roles>
<role id="scoreReviewers" name="ScoreReviewers" scope="collection" description="The people responsible to select a single reviewer for the submission"/>
</roles>
<step id="scoreReviewStep" role="scoreReviewers" userSelectionMethod="claimaction" requiredUsers="2">
<outcomes>
<step status="0">evaluationStep</step>
</outcomes>
<actions>
<action id="scorereviewaction"/>
</actions>
</step>
<step id="evaluationStep" userSelectionMethod="noUserSelectionAction">
<actions>
<action id="evaluationaction"/>
</actions>
</step>
</workflow>
</wf-config>