
org.dspace.xmlworkflow.XmlWorkflowServiceImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dspace-api Show documentation
Show all versions of dspace-api Show documentation
DSpace core data model and service APIs.
The newest version!
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.xmlworkflow;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.UUID;
import jakarta.mail.MessagingException;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.DCDate;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.MetadataValue;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.BundleService;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.core.LogHelper;
import org.dspace.curate.service.XmlWorkflowCuratorService;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.event.Event;
import org.dspace.handle.service.HandleService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.usage.UsageWorkflowEvent;
import org.dspace.workflow.WorkflowException;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.service.WorkflowRequirementsService;
import org.dspace.xmlworkflow.service.XmlWorkflowService;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.Workflow;
import org.dspace.xmlworkflow.state.actions.Action;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
import org.dspace.xmlworkflow.storedcomponents.PoolTask;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService;
import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService;
import org.dspace.xmlworkflow.storedcomponents.service.WorkflowItemRoleService;
import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* When an item is submitted and is somewhere in a workflow, it has a row in the
* {@code cwf_workflowitem} table pointing to it.
*
* Once the item has completed the workflow it will be archived.
*
* @author Bram De Schouwer (bram.deschouwer at dot com)
* @author Kevin Van de Velde (kevin at atmire dot com)
* @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com)
*/
public class XmlWorkflowServiceImpl implements XmlWorkflowService {
/* support for 'no notification' */
protected Map noEMail = new HashMap<>();
private final Logger log = org.apache.logging.log4j.LogManager.getLogger(XmlWorkflowServiceImpl.class);
@Autowired(required = true)
protected AuthorizeService authorizeService;
@Autowired(required = true)
protected CollectionRoleService collectionRoleService;
@Autowired(required = true)
protected ClaimedTaskService claimedTaskService;
@Autowired(required = true)
protected HandleService handleService;
@Autowired(required = true)
protected InstallItemService installItemService;
@Autowired(required = true)
protected ItemService itemService;
@Autowired(required = true)
protected PoolTaskService poolTaskService;
@Autowired(required = true)
protected WorkflowItemRoleService workflowItemRoleService;
@Autowired(required = true)
protected WorkflowRequirementsService workflowRequirementsService;
@Autowired(required = true)
protected XmlWorkflowFactory xmlWorkflowFactory;
@Autowired(required = true)
protected WorkspaceItemService workspaceItemService;
@Autowired(required = true)
protected XmlWorkflowItemService xmlWorkflowItemService;
@Autowired(required = true)
protected GroupService groupService;
@Autowired(required = true)
protected BundleService bundleService;
@Autowired(required = true)
protected BitstreamFormatService bitstreamFormatService;
@Autowired(required = true)
protected BitstreamService bitstreamService;
@Autowired(required = true)
protected ConfigurationService configurationService;
@Autowired(required = true)
protected XmlWorkflowCuratorService xmlWorkflowCuratorService;
protected XmlWorkflowServiceImpl() {
}
@Override
public void deleteCollection(Context context, Collection collection)
throws SQLException, IOException, AuthorizeException {
xmlWorkflowItemService.deleteByCollection(context, collection);
collectionRoleService.deleteByCollection(context, collection);
}
@Override
public List getEPersonDeleteConstraints(Context context, EPerson ePerson) throws SQLException {
List constraints = new ArrayList<>();
if (CollectionUtils.isNotEmpty(claimedTaskService.findByEperson(context, ePerson))) {
constraints.add("cwf_claimtask");
}
if (CollectionUtils.isNotEmpty(poolTaskService.findByEPerson(context, ePerson))) {
constraints.add("cwf_pooltask");
}
if (CollectionUtils.isNotEmpty(workflowItemRoleService.findByEPerson(context, ePerson))) {
constraints.add("cwf_workflowitemrole");
}
return constraints;
}
@Override
public Group getWorkflowRoleGroup(Context context, Collection collection, String roleName, Group roleGroup)
throws SQLException, IOException, WorkflowException, AuthorizeException {
try {
Role role = WorkflowUtils.getCollectionAndRepositoryRoles(collection).get(roleName);
if (role.getScope() == Role.Scope.COLLECTION || role.getScope() == Role.Scope.REPOSITORY) {
roleGroup = WorkflowUtils.getRoleGroup(context, collection, role);
}
return roleGroup;
} catch (WorkflowConfigurationException e) {
throw new WorkflowException(e);
}
}
@Override
public Group createWorkflowRoleGroup(Context context, Collection collection, String roleName)
throws AuthorizeException, SQLException, IOException, WorkflowConfigurationException {
Group roleGroup;
authorizeService.authorizeAction(context, collection, Constants.WRITE);
roleGroup = groupService.create(context);
Role role = WorkflowUtils.getCollectionAndRepositoryRoles(collection).get(roleName);
if (role.getScope() == Role.Scope.COLLECTION) {
groupService.setName(roleGroup,
"COLLECTION_" + collection.getID().toString()
+ "_WORKFLOW_ROLE_" + roleName);
} else {
groupService.setName(roleGroup, role.getName());
}
groupService.update(context, roleGroup);
authorizeService.addPolicy(context, collection, Constants.ADD, roleGroup);
if (role.getScope() == Role.Scope.COLLECTION) {
WorkflowUtils.createCollectionWorkflowRole(context, collection, roleName, roleGroup);
}
return roleGroup;
}
@Override
public List getFlywayMigrationLocations() {
return Collections.singletonList("classpath:org/dspace/storage/rdbms/xmlworkflow");
}
@Override
public XmlWorkflowItem start(Context context, WorkspaceItem wsi)
throws SQLException, AuthorizeException, IOException, WorkflowException {
try {
Item myitem = wsi.getItem();
Collection collection = wsi.getCollection();
Workflow wf = xmlWorkflowFactory.getWorkflow(collection);
XmlWorkflowItem wfi = xmlWorkflowItemService.create(context, myitem, collection);
wfi.setMultipleFiles(wsi.hasMultipleFiles());
wfi.setMultipleTitles(wsi.hasMultipleTitles());
wfi.setPublishedBefore(wsi.isPublishedBefore());
xmlWorkflowItemService.update(context, wfi);
removeUserItemPolicies(context, myitem, myitem.getSubmitter());
grantSubmitterReadPolicies(context, myitem);
context.turnOffAuthorisationSystem();
Step firstStep = wf.getFirstStep();
if (firstStep.isValidStep(context, wfi)) {
activateFirstStep(context, wf, firstStep, wfi);
} else {
//Get our next step, if none is found, archive our item
firstStep = wf.getNextStep(context, wfi, firstStep, ActionResult.OUTCOME_COMPLETE);
if (firstStep == null) {
// record the submitted provenance message
recordStart(context, wfi.getItem(),null);
archive(context, wfi);
} else {
activateFirstStep(context, wf, firstStep, wfi);
}
}
// remove the WorkspaceItem
workspaceItemService.deleteWrapper(context, wsi);
context.restoreAuthSystemState();
context.addEvent(new Event(Event.MODIFY, Constants.ITEM, wfi.getItem().getID(), null,
itemService.getIdentifiers(context, wfi.getItem())));
return wfi;
} catch (WorkflowConfigurationException e) {
throw new WorkflowException(e);
}
}
//TODO: this is currently not used in our notifications. Look at the code used by the original WorkflowManager
@Override
public XmlWorkflowItem startWithoutNotify(Context context, WorkspaceItem wsi)
throws SQLException, AuthorizeException, IOException, WorkflowException {
// make a hash table entry with item ID for no notify
// notify code checks no notify hash for item id
noEMail.put(wsi.getItem().getID(), Boolean.TRUE);
return start(context, wsi);
}
@Override
public void alertUsersOnTaskActivation(Context c, XmlWorkflowItem wfi, String emailTemplate, List epa,
String... arguments) throws IOException, SQLException, MessagingException {
if (noEMail.containsKey(wfi.getItem().getID())) {
// suppress email, and delete key
noEMail.remove(wfi.getItem().getID());
} else {
Email mail = Email.getEmail(I18nUtil.getEmailFilename(c.getCurrentLocale(), emailTemplate));
for (String argument : arguments) {
mail.addArgument(argument);
}
for (EPerson anEpa : epa) {
mail.addRecipient(anEpa.getEmail());
}
mail.send();
}
}
protected void grantSubmitterReadPolicies(Context context, Item item) throws SQLException, AuthorizeException {
EPerson submitter = item.getSubmitter();
if (null != submitter) {
//A list of policies the user has for this item
List userHasPolicies = new ArrayList<>();
List itempols = authorizeService.getPolicies(context, item);
for (ResourcePolicy resourcePolicy : itempols) {
if (submitter.equals(resourcePolicy.getEPerson())) {
//The user has already got this policy so add it to the list
userHasPolicies.add(resourcePolicy.getAction());
}
}
//Make sure we don't add duplicate policies
if (!userHasPolicies.contains(Constants.READ)) {
addPolicyToItem(context, item, Constants.READ, submitter, ResourcePolicy.TYPE_SUBMISSION);
}
}
}
/**
* Activate the first step in a workflow for a WorkflowItem.
* If the step has no UI then execute it as well.
*
* @param context current DSpace session.
* @param wf workflow being traversed.
* @param firstStep the step to be activated.
* @param wfi the item upon which to activate the step.
* @throws AuthorizeException passed through.
* @throws IOException passed through.
* @throws SQLException passed through.
* @throws WorkflowException passed through.
* @throws WorkflowConfigurationException unused.
*/
protected void activateFirstStep(Context context, Workflow wf, Step firstStep, XmlWorkflowItem wfi)
throws AuthorizeException, IOException, SQLException, WorkflowException, WorkflowConfigurationException {
WorkflowActionConfig firstActionConfig = firstStep.getUserSelectionMethod();
// Check for curation tasks at the start of this step.
//
// If doCuration returns true, either no curation tasks are mapped to
// this step, or they were run and succeeded. In this scenario, we
// continue with the current step.
//
// If doCuration returns false, either curation tasks were queued, or
// they resulted in rejection of the item. In this scenario, the
// current step cannot be completed and we must exit immediately.
if (!xmlWorkflowCuratorService.doCuration(context, wfi)) {
// don't proceed - either curation tasks queued, or item rejected
log.info(LogHelper.getHeader(context, "start_workflow",
"workflow_item_id=" + wfi.getID()
+ ",item_id=" + wfi.getItem().getID()
+ ",collection_id=" + wfi.getCollection().getID()
+ ",current_action=" + firstActionConfig.getId()
+ ",doCuration=false"));
return;
}
// Activate the step.
firstActionConfig.getProcessingAction().activate(context, wfi);
log.info(LogHelper.getHeader(context, "start_workflow",
firstActionConfig.getProcessingAction()
+ " workflow_item_id=" + wfi.getID()
+ "item_id=" + wfi.getItem().getID()
+ "collection_id=" + wfi.getCollection().getID()));
// record the start of the workflow w/provenance message
recordStart(context, wfi.getItem(), firstActionConfig.getProcessingAction());
//Fire an event !
logWorkflowEvent(context, firstStep.getWorkflow().getID(), null, null, wfi, null, firstStep, firstActionConfig);
//If we don't have a UI then execute the action.
if (!firstActionConfig.requiresUI()) {
ActionResult outcome = firstActionConfig.getProcessingAction().execute(context, wfi, firstStep, null);
processOutcome(context, null, wf, firstStep, firstActionConfig, outcome, wfi, true);
}
}
@Override
public WorkflowActionConfig doState(Context c, EPerson user,
HttpServletRequest request, int workflowItemId, Workflow workflow,
WorkflowActionConfig currentActionConfig)
throws SQLException, AuthorizeException, IOException,
MessagingException, WorkflowException {
XmlWorkflowItem wi = xmlWorkflowItemService.find(c, workflowItemId);
// Check for curation tasks.
//
// If doCuration returns true, either no curation tasks are mapped to
// this step, or they were run and succeeded. In this scenario, we
// continue with the current step.
//
// If doCuration returns false, either curation tasks were queued, or
// they resulted in rejection of the item. In this scenario, the
// current step cannot be completed and we must exit immediately.
if (!xmlWorkflowCuratorService.doCuration(c, wi)) {
// don't proceed - either curation tasks queued, or item rejected
log.info(LogHelper.getHeader(c, "advance_workflow",
"workflow_item_id=" + wi.getID()
+ ",item_id=" + wi.getItem().getID()
+ ",collection_id=" + wi.getCollection().getID()
+ ",current_action=" + currentActionConfig.getId()
+ ",doCuration=false"));
return currentActionConfig;
}
try {
Step currentStep = currentActionConfig.getStep();
if (currentActionConfig.getProcessingAction().isAuthorized(c, request, wi)) {
ActionResult outcome = currentActionConfig.getProcessingAction().execute(c, wi, currentStep, request);
// the cancel action is the default when the request is not understood or a "back to mydspace" was
// pressed in the old UI
if (outcome.getType() == ActionResult.TYPE.TYPE_CANCEL) {
throw new WorkflowException("Unprocessable request for the action " + currentStep.getId());
}
c.addEvent(new Event(Event.MODIFY, Constants.ITEM, wi.getItem().getID(), null,
itemService.getIdentifiers(c, wi.getItem())));
return processOutcome(c, user, workflow, currentStep, currentActionConfig, outcome, wi, false);
} else {
throw new AuthorizeException("You are not allowed to to perform this task.");
}
} catch (WorkflowConfigurationException e) {
log.error(LogHelper.getHeader(c, "error while executing state",
"workflow: " + workflow.getID()
+ " action: " + currentActionConfig.getId()
+ " workflowItemId: " + workflowItemId), e);
WorkflowUtils.sendAlert(request, e);
throw new WorkflowException(e);
}
}
@Override
public WorkflowActionConfig processOutcome(Context c, EPerson user, Workflow workflow, Step currentStep,
WorkflowActionConfig currentActionConfig, ActionResult currentOutcome,
XmlWorkflowItem wfi, boolean enteredNewStep)
throws IOException, AuthorizeException, SQLException, WorkflowException {
if (currentOutcome.getType() == ActionResult.TYPE.TYPE_PAGE
|| currentOutcome.getType() == ActionResult.TYPE.TYPE_ERROR) {
//Our outcome is a page or an error, so return our current action
c.restoreAuthSystemState();
return currentActionConfig;
} else if (currentOutcome.getType() == ActionResult.TYPE.TYPE_CANCEL
|| currentOutcome.getType() == ActionResult.TYPE.TYPE_SUBMISSION_PAGE) {
//We either pressed the cancel button or got an order to return to the submission page, so don't return
// an action
//By not returning an action we ensure ourselves that we go back to the submission page
c.restoreAuthSystemState();
return null;
} else if (currentOutcome.getType() == ActionResult.TYPE.TYPE_OUTCOME) {
// An action was taken by a reviewer.
Step nextStep = null;
WorkflowActionConfig nextActionConfig = null;
try {
if (currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE) {
//We have completed our action. Retrieve the next action.
nextActionConfig = currentStep.getNextAction(currentActionConfig);
}
if (nextActionConfig != null) {
//We remain in the current step since another action is found.
nextStep = currentStep;
nextActionConfig.getProcessingAction().activate(c, wfi);
if (nextActionConfig.requiresUI() && !enteredNewStep) {
createOwnedTask(c, wfi, currentStep, nextActionConfig, user);
return nextActionConfig;
} else if (nextActionConfig.requiresUI() && enteredNewStep) {
//We have entered a new step and have encountered a UI, return null since the current user
// doesn't have anything to do with this
c.restoreAuthSystemState();
return null;
} else {
// UI not required by next action.
ActionResult newOutcome = nextActionConfig.getProcessingAction()
.execute(c, wfi, currentStep, null);
return processOutcome(c, user, workflow, currentStep, nextActionConfig, newOutcome, wfi,
enteredNewStep);
}
} else if (enteredNewStep) {
// If the user finished their step, we keep processing until there is a UI step action or no
// step at all
nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult());
c.turnOffAuthorisationSystem();
nextActionConfig = processNextStep(c, user, workflow, currentOutcome, wfi, nextStep);
//If we require a user interface return null so that the user is redirected to the "submissions
// page"
if (nextActionConfig == null || nextActionConfig.requiresUI()) {
return null;
} else {
return nextActionConfig;
}
} else {
ClaimedTask task = claimedTaskService.findByWorkflowIdAndEPerson(c, wfi, user);
//Check if we have a task for this action (might not be the case with automatic steps)
//First add it to our list of finished users, since no more actions remain
workflowRequirementsService.addFinishedUser(c, wfi, user);
c.turnOffAuthorisationSystem();
//Check if our requirements have been met
if ((currentStep.isFinished(c, wfi) && currentOutcome
.getResult() == ActionResult.OUTCOME_COMPLETE) || currentOutcome
.getResult() != ActionResult.OUTCOME_COMPLETE) {
//Delete all the table rows containing the users who performed this task
workflowRequirementsService.clearInProgressUsers(c, wfi);
//Remove all the tasks
deleteAllTasks(c, wfi);
nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult());
nextActionConfig = processNextStep(c, user, workflow, currentOutcome, wfi, nextStep);
//If we require a user interface return null so that the user is redirected to the
// "submissions page"
if (nextActionConfig == null || nextActionConfig.requiresUI()) {
return null;
} else {
return nextActionConfig;
}
} else {
//We are done with our actions so go to the submissions page but remove action ClaimedAction
// first
deleteClaimedTask(c, wfi, task);
c.restoreAuthSystemState();
nextStep = currentStep;
nextActionConfig = currentActionConfig;
return null;
}
}
} catch (IOException | SQLException | AuthorizeException
| WorkflowException | WorkflowConfigurationException e) {
log.error("error while processing workflow outcome", e);
} finally {
if ((nextStep != null && currentStep != null && nextActionConfig != null)
|| (wfi.getItem().isArchived() && currentStep != null)) {
logWorkflowEvent(c, currentStep.getWorkflow().getID(), currentStep.getId(),
currentActionConfig.getId(), wfi, user, nextStep, nextActionConfig);
}
}
}
log.error(LogHelper.getHeader(c, "Invalid step outcome", "Workflow item id: " + wfi.getID()));
throw new WorkflowException("Invalid step outcome");
}
protected void logWorkflowEvent(Context c, String workflowId, String previousStepId, String previousActionConfigId,
XmlWorkflowItem wfi, EPerson actor, Step newStep,
WorkflowActionConfig newActionConfig) throws SQLException {
try {
//Fire an event so we can log our action !
Item item = wfi.getItem();
Collection myCollection = wfi.getCollection();
String workflowStepString = null;
List currentEpersonOwners = new ArrayList<>();
List currentGroupOwners = new ArrayList<>();
//These are only null if our item is sent back to the submission
if (newStep != null && newActionConfig != null) {
workflowStepString = workflowId + "." + newStep.getId() + "." + newActionConfig.getId();
//Retrieve the current owners of the task
List claimedTasks = claimedTaskService.find(c, wfi, newStep.getId());
List pooledTasks = poolTaskService.find(c, wfi);
for (PoolTask poolTask : pooledTasks) {
if (poolTask.getEperson() != null) {
currentEpersonOwners.add(poolTask.getEperson());
} else {
currentGroupOwners.add(poolTask.getGroup());
}
}
for (ClaimedTask claimedTask : claimedTasks) {
currentEpersonOwners.add(claimedTask.getOwner());
}
}
String previousWorkflowStepString = null;
if (previousStepId != null && previousActionConfigId != null) {
previousWorkflowStepString = workflowId + "." + previousStepId + "." + previousActionConfigId;
}
//Fire our usage event !
UsageWorkflowEvent usageWorkflowEvent = new UsageWorkflowEvent(c, item, wfi, workflowStepString,
previousWorkflowStepString, myCollection,
actor);
usageWorkflowEvent.setEpersonOwners(currentEpersonOwners.toArray(new EPerson[currentEpersonOwners.size()]));
usageWorkflowEvent.setGroupOwners(currentGroupOwners.toArray(new Group[currentGroupOwners.size()]));
DSpaceServicesFactory.getInstance().getEventService().fireEvent(usageWorkflowEvent);
} catch (SQLException e) {
//Catch all errors we do not want our workflow to crash because the logging threw an exception
log.error(LogHelper.getHeader(c, "Error while logging workflow event", "Workflow Item: " + wfi.getID()),
e);
}
}
protected WorkflowActionConfig processNextStep(Context c, EPerson user, Workflow workflow,
ActionResult currentOutcome, XmlWorkflowItem wfi, Step nextStep)
throws SQLException, IOException, AuthorizeException, WorkflowException, WorkflowConfigurationException {
WorkflowActionConfig nextActionConfig;
if (nextStep != null) {
nextActionConfig = nextStep.getUserSelectionMethod();
nextActionConfig.getProcessingAction().activate(c, wfi);
// nextActionConfig.getProcessingAction().generateTasks();
if (nextActionConfig.requiresUI()) {
//Since a new step has been started, stop executing actions once one with a user interface is present.
c.restoreAuthSystemState();
return nextActionConfig;
} else {
ActionResult newOutcome = nextActionConfig.getProcessingAction().execute(c, wfi, nextStep, null);
c.restoreAuthSystemState();
return processOutcome(c, user, workflow, nextStep, nextActionConfig, newOutcome, wfi, true);
}
} else {
if (currentOutcome.getResult() != ActionResult.OUTCOME_COMPLETE) {
c.restoreAuthSystemState();
throw new WorkflowException("No alternate step was found for outcome: " + currentOutcome.getResult());
}
archive(c, wfi);
c.restoreAuthSystemState();
return null;
}
}
/**
* Commit the contained item to the main archive. The item is associated
* with the relevant collection, added to the search index, and any other
* tasks such as assigning dates are performed.
*
* @param context The relevant DSpace Context.
* @param wfi workflow item
* @return the fully archived item.
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
*/
protected Item archive(Context context, XmlWorkflowItem wfi)
throws SQLException, IOException, AuthorizeException {
// FIXME: Check auth
Item item = wfi.getItem();
Collection collection = wfi.getCollection();
// Remove (if any) the workflowItemroles for this item
workflowItemRoleService.deleteForWorkflowItem(context, wfi);
log.info(LogHelper.getHeader(context, "archive_item", "workflow_item_id="
+ wfi.getID() + "item_id=" + item.getID() + "collection_id="
+ collection.getID()));
installItemService.installItem(context, wfi);
//Notify
notifyOfArchive(context, item, collection);
//Clear any remaining workflow metadata
itemService
.clearMetadata(context, item, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY);
itemService.update(context, item);
// Log the event
log.info(LogHelper.getHeader(context, "install_item", "workflow_item_id="
+ wfi.getID() + ", item_id=" + item.getID() + "handle=FIXME"));
return item;
}
/**
* notify the submitter that the item is archived
*
* @param context The relevant DSpace Context.
* @param item which item was archived
* @param coll collection name to display in template
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
*/
protected void notifyOfArchive(Context context, Item item, Collection coll)
throws SQLException, IOException {
try {
// Get submitter
EPerson ep = item.getSubmitter();
// send the notification to the submitter unless the submitter eperson has been deleted
if (null != ep) {
// Get the Locale
Locale supportedLocale = I18nUtil.getEPersonLocale(ep);
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_archive"));
// Get the item handle to email to user
String handle = handleService.findHandle(context, item);
// Get title
List titles = itemService
.getMetadata(item, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY);
String title = "";
try {
title = I18nUtil.getMessage("org.dspace.xmlworkflow.XMLWorkflowService.untitled");
} catch (MissingResourceException e) {
title = "Untitled";
}
if (titles.size() > 0) {
title = titles.iterator().next().getValue();
}
email.addRecipient(ep.getEmail());
email.addArgument(title);
email.addArgument(coll.getName());
email.addArgument(handleService.getCanonicalForm(handle));
email.send();
}
} catch (MessagingException e) {
log.warn(LogHelper.getHeader(context, "notifyOfArchive",
"cannot email user" + " item_id=" + item.getID()), e);
}
}
// send notices of curation activity
@Override
public void notifyOfCuration(Context c, XmlWorkflowItem wi,
List ePeople, String taskName, String action, String message)
throws SQLException, IOException {
try {
// Get the item title
String title = getItemTitle(wi);
// Get the submitter's name
String submitter = getSubmitterName(wi);
// Get the collection
Collection coll = wi.getCollection();
for (EPerson epa : ePeople) {
Locale supportedLocale = I18nUtil.getEPersonLocale(epa);
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "flowtask_notify"));
email.addArgument(title);
email.addArgument(coll.getName());
email.addArgument(submitter);
email.addArgument(taskName);
email.addArgument(message);
email.addArgument(action);
email.addRecipient(epa.getEmail());
email.send();
}
} catch (MessagingException e) {
log.warn(LogHelper.getHeader(c, "notifyOfCuration",
"cannot email users of workflow_item_id " + wi.getID()
+ ": " + e.getMessage()), e);
}
}
protected String getItemTitle(XmlWorkflowItem wi) throws SQLException {
Item myitem = wi.getItem();
String title = myitem.getName();
// only return the first element, or "Untitled"
if (StringUtils.isNotBlank(title)) {
return title;
} else {
return I18nUtil.getMessage("org.dspace.xmlworkflow.XMLWorkflowService.untitled ");
}
}
protected String getSubmitterName(XmlWorkflowItem wi) throws SQLException {
EPerson e = wi.getSubmitter();
return getEPersonName(e);
}
/***********************************
* WORKFLOW TASK MANAGEMENT
**********************************/
@Override
public void deleteAllTasks(Context context, XmlWorkflowItem wi) throws SQLException, AuthorizeException {
deleteAllPooledTasks(context, wi);
Iterator allClaimedTasks = claimedTaskService.findByWorkflowItem(context, wi).iterator();
while (allClaimedTasks.hasNext()) {
ClaimedTask task = allClaimedTasks.next();
allClaimedTasks.remove();
deleteClaimedTask(context, wi, task);
}
}
@Override
public void deleteAllPooledTasks(Context context, XmlWorkflowItem wi) throws SQLException, AuthorizeException {
Iterator allPooledTasks = poolTaskService.find(context, wi).iterator();
while (allPooledTasks.hasNext()) {
PoolTask poolTask = allPooledTasks.next();
allPooledTasks.remove();
deletePooledTask(context, wi, poolTask);
}
}
@Override
public void deletePooledTask(Context context, XmlWorkflowItem wi, PoolTask task)
throws SQLException, AuthorizeException {
if (task != null) {
if (task.getEperson() != null) {
removeUserItemPolicies(context, wi.getItem(), task.getEperson());
} else {
removeGroupItemPolicies(context, wi.getItem(), task.getGroup());
}
poolTaskService.delete(context, task);
}
}
@Override
public void deleteClaimedTask(Context c, XmlWorkflowItem wi, ClaimedTask task)
throws SQLException, AuthorizeException {
if (task != null) {
removeUserItemPolicies(c, wi.getItem(), task.getOwner());
claimedTaskService.delete(c, task);
}
c.addEvent(new Event(Event.MODIFY, Constants.ITEM, wi.getItem().getID(), null,
itemService.getIdentifiers(c, wi.getItem())));
}
@Override
public void createPoolTasks(Context context, XmlWorkflowItem wi, RoleMembers assignees, Step step,
WorkflowActionConfig action)
throws SQLException, AuthorizeException {
// create a tasklist entry for each eperson
for (EPerson anEpa : assignees.getEPersons()) {
PoolTask task = poolTaskService.create(context);
task.setStepID(step.getId());
task.setWorkflowID(step.getWorkflow().getID());
task.setEperson(anEpa);
task.setActionID(action.getId());
task.setWorkflowItem(wi);
poolTaskService.update(context, task);
//Make sure this user has a task
grantUserAllItemPolicies(context, wi.getItem(), anEpa, ResourcePolicy.TYPE_WORKFLOW);
}
for (Group group : assignees.getGroups()) {
PoolTask task = poolTaskService.create(context);
task.setStepID(step.getId());
task.setWorkflowID(step.getWorkflow().getID());
task.setGroup(group);
task.setActionID(action.getId());
task.setWorkflowItem(wi);
poolTaskService.update(context, task);
//Make sure this user has a task
grantGroupAllItemPolicies(context, wi.getItem(), group, ResourcePolicy.TYPE_WORKFLOW);
}
}
@Override
public void createOwnedTask(Context context, XmlWorkflowItem wi, Step step, WorkflowActionConfig action, EPerson e)
throws SQLException, AuthorizeException {
ClaimedTask task = claimedTaskService.create(context);
task.setWorkflowItem(wi);
task.setStepID(step.getId());
task.setActionID(action.getId());
task.setOwner(e);
task.setWorkflowID(step.getWorkflow().getID());
claimedTaskService.update(context, task);
//Make sure this user has a task
grantUserAllItemPolicies(context, wi.getItem(), e, ResourcePolicy.TYPE_WORKFLOW);
}
@Override
public void grantUserAllItemPolicies(Context context, Item item, EPerson epa, String policyType)
throws AuthorizeException, SQLException {
if (epa != null) {
//A list of policies the user has for this item
List userHasPolicies = new ArrayList<>();
List itempols = authorizeService.getPolicies(context, item);
for (ResourcePolicy resourcePolicy : itempols) {
if (epa.equals(resourcePolicy.getEPerson())) {
//The user has already got this policy so it it to the list
userHasPolicies.add(resourcePolicy.getAction());
}
}
//Make sure we don't add duplicate policies
if (!userHasPolicies.contains(Constants.READ)) {
addPolicyToItem(context, item, Constants.READ, epa, policyType);
}
if (!userHasPolicies.contains(Constants.WRITE)) {
addPolicyToItem(context, item, Constants.WRITE, epa, policyType);
}
if (!userHasPolicies.contains(Constants.DELETE)) {
addPolicyToItem(context, item, Constants.DELETE, epa, policyType);
}
if (!userHasPolicies.contains(Constants.ADD)) {
addPolicyToItem(context, item, Constants.ADD, epa, policyType);
}
if (!userHasPolicies.contains(Constants.REMOVE)) {
addPolicyToItem(context, item, Constants.REMOVE, epa, policyType);
}
}
}
protected void grantGroupAllItemPolicies(Context context, Item item, Group group, String policyType)
throws AuthorizeException, SQLException {
if (group != null) {
//A list of policies the user has for this item
List groupHasPolicies = new ArrayList<>();
List itempols = authorizeService.getPolicies(context, item);
for (ResourcePolicy resourcePolicy : itempols) {
if (group.equals(resourcePolicy.getGroup())) {
//The user has already got this policy so it it to the list
groupHasPolicies.add(resourcePolicy.getAction());
}
}
//Make sure we don't add duplicate policies
if (!groupHasPolicies.contains(Constants.READ)) {
addGroupPolicyToItem(context, item, Constants.READ, group, policyType);
}
if (!groupHasPolicies.contains(Constants.WRITE)) {
addGroupPolicyToItem(context, item, Constants.WRITE, group, policyType);
}
if (!groupHasPolicies.contains(Constants.DELETE)) {
addGroupPolicyToItem(context, item, Constants.DELETE, group, policyType);
}
if (!groupHasPolicies.contains(Constants.ADD)) {
addGroupPolicyToItem(context, item, Constants.ADD, group, policyType);
}
if (!groupHasPolicies.contains(Constants.REMOVE)) {
addGroupPolicyToItem(context, item, Constants.REMOVE, group, policyType);
}
}
}
protected void addPolicyToItem(Context context, Item item, int action, EPerson epa, String policyType)
throws AuthorizeException, SQLException {
if (epa != null) {
authorizeService.addPolicy(context, item, action, epa, policyType);
List bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.addPolicy(context, bundle, action, epa, policyType);
List bits = bundle.getBitstreams();
for (Bitstream bit : bits) {
authorizeService.addPolicy(context, bit, action, epa, policyType);
}
}
}
}
protected void addGroupPolicyToItem(Context context, Item item, int action, Group group, String policyType)
throws AuthorizeException, SQLException {
if (group != null) {
authorizeService.addPolicy(context, item, action, group, policyType);
List bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.addPolicy(context, bundle, action, group, policyType);
List bits = bundle.getBitstreams();
for (Bitstream bit : bits) {
authorizeService.addPolicy(context, bit, action, group, policyType);
}
}
}
}
@Override
public void removeUserItemPolicies(Context context, Item item, EPerson e) throws SQLException, AuthorizeException {
if (e != null && item.getSubmitter() != null) {
//Also remove any lingering authorizations from this user
authorizeService.removeEPersonPolicies(context, item, e);
//Remove the bundle rights
List bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.removeEPersonPolicies(context, bundle, e);
List bitstreams = bundle.getBitstreams();
for (Bitstream bitstream : bitstreams) {
authorizeService.removeEPersonPolicies(context, bitstream, e);
}
}
// Ensure that the submitter always retains their resource policies
if (e.getID().equals(item.getSubmitter().getID())) {
grantSubmitterReadPolicies(context, item);
}
}
}
protected void removeGroupItemPolicies(Context context, Item item, Group e)
throws SQLException, AuthorizeException {
if (e != null && item.getSubmitter() != null) {
//Also remove any lingering authorizations from this user
authorizeService.removeGroupPolicies(context, item, e);
//Remove the bundle rights
List bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.removeGroupPolicies(context, bundle, e);
List bitstreams = bundle.getBitstreams();
for (Bitstream bitstream : bitstreams) {
authorizeService.removeGroupPolicies(context, bitstream, e);
}
}
}
}
@Override
public void deleteWorkflowByWorkflowItem(Context context, XmlWorkflowItem wi, EPerson e)
throws SQLException, AuthorizeException, IOException {
Item myitem = wi.getItem();
UUID itemID = myitem.getID();
Integer workflowID = wi.getID();
UUID collID = wi.getCollection().getID();
// stop workflow
deleteAllTasks(context, wi);
context.turnOffAuthorisationSystem();
//Also clear all info for this step
workflowRequirementsService.clearInProgressUsers(context, wi);
// Remove (if any) the workflowItemroles for this item
workflowItemRoleService.deleteForWorkflowItem(context, wi);
// Now remove the workflow object manually from the database
xmlWorkflowItemService.deleteWrapper(context, wi);
// Now delete the item
itemService.delete(context, myitem);
log.info(LogHelper.getHeader(context, "delete_workflow", "workflow_item_id="
+ workflowID + "item_id=" + itemID
+ "collection_id=" + collID + "eperson_id="
+ e.getID()));
context.restoreAuthSystemState();
}
@Override
public WorkspaceItem sendWorkflowItemBackSubmission(Context context, XmlWorkflowItem wi, EPerson e,
String provenance,
String rejection_message)
throws SQLException, AuthorizeException,
IOException {
String workflowID = null;
String currentStepId = null;
String currentActionConfigId = null;
ClaimedTask claimedTask = claimedTaskService.findByWorkflowIdAndEPerson(context, wi, e);
if (claimedTask != null) {
//Log it
workflowID = claimedTask.getWorkflowID();
currentStepId = claimedTask.getStepID();
currentActionConfigId = claimedTask.getActionID();
}
context.turnOffAuthorisationSystem();
// rejection provenance
Item myitem = wi.getItem();
// Get current date
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = getEPersonName(e);
// Here's what happened
String provDescription = provenance + " Rejected by " + usersName + ", reason: "
+ rejection_message + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService
.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription);
//Clear any workflow schema related metadata
itemService
.clearMetadata(context, myitem, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY);
itemService.update(context, myitem);
// remove policy for controller
removeUserItemPolicies(context, myitem, e);
revokeReviewerPolicies(context, myitem);
// convert into personal workspace
WorkspaceItem wsi = returnToWorkspace(context, wi);
// notify that it's been rejected
notifyOfReject(context, wi, e, rejection_message);
log.info(LogHelper.getHeader(context, "reject_workflow", "workflow_item_id="
+ wi.getID() + "item_id=" + wi.getItem().getID()
+ "collection_id=" + wi.getCollection().getID() + "eperson_id="
+ e.getID()));
logWorkflowEvent(context, workflowID, currentStepId, currentActionConfigId, wi, e, null, null);
context.restoreAuthSystemState();
return wsi;
}
@Override
public WorkspaceItem abort(Context c, XmlWorkflowItem wi, EPerson e)
throws AuthorizeException, SQLException, IOException {
if (!authorizeService.isAdmin(c)) {
throw new AuthorizeException(
"You must be an admin to abort a workflow");
}
c.turnOffAuthorisationSystem();
//Restore permissions for the submitter
// convert into personal workspace
WorkspaceItem wsi = returnToWorkspace(c, wi);
log.info(LogHelper.getHeader(c, "abort_workflow", "workflow_item_id="
+ wi.getID() + "item_id=" + wsi.getItem().getID()
+ "collection_id=" + wi.getCollection().getID() + "eperson_id="
+ e.getID()));
c.addEvent(new Event(Event.MODIFY, Constants.ITEM, wsi.getItem().getID(), null,
itemService.getIdentifiers(c, wsi.getItem())));
c.restoreAuthSystemState();
return wsi;
}
@Override
public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance)
throws SQLException, AuthorizeException, IOException, WorkflowException {
if (!authorizeService.isAdmin(context)) {
throw new AuthorizeException("You must be an admin to restart a workflow");
}
context.turnOffAuthorisationSystem();
// rejection provenance
Item myitem = wi.getItem();
// Here's what happened
String provDescription =
provenance + " Declined by " + getEPersonName(decliner) + " on " + DCDate.getCurrent().toString() +
" (GMT) ";
// Add to item as a DC field
itemService
.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription);
//Clear any workflow schema related metadata
itemService
.clearMetadata(context, myitem, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY);
itemService.update(context, myitem);
// remove policy for controller
removeUserItemPolicies(context, myitem, decliner);
revokeReviewerPolicies(context, myitem);
// convert into personal workspace
WorkspaceItem wsi = returnToWorkspace(context, wi);
// Because of issue of xmlWorkflowItemService not realising wfi wrapper has been deleted
context.commit();
wsi = context.reloadEntity(wsi);
log.info(LogHelper.getHeader(context, "decline_workflow", "workflow_item_id="
+ wi.getID() + "item_id=" + wi.getItem().getID() + "collection_id=" + wi.getCollection().getID() +
"eperson_id=" + decliner.getID()));
// Restart workflow
this.startWithoutNotify(context, wsi);
context.restoreAuthSystemState();
}
/**
* Return the workflow item to the workspace of the submitter. The workflow
* item is removed, and a workspace item created.
*
* @param c Context
* @param wfi WorkflowItem to be 'dismantled'
* @return the workspace item
* @throws java.io.IOException ...
* @throws java.sql.SQLException ...
* @throws org.dspace.authorize.AuthorizeException ...
*/
protected WorkspaceItem returnToWorkspace(Context c, XmlWorkflowItem wfi)
throws SQLException, IOException, AuthorizeException {
// authorize a DSpaceActions.REJECT
// stop workflow
deleteAllTasks(c, wfi);
c.turnOffAuthorisationSystem();
//Also clear all info for this step
workflowRequirementsService.clearInProgressUsers(c, wfi);
// Remove (if any) the workflowItemroles for this item
workflowItemRoleService.deleteForWorkflowItem(c, wfi);
Item myitem = wfi.getItem();
//Restore permissions for the submitter
grantUserAllItemPolicies(c, myitem, myitem.getSubmitter(), ResourcePolicy.TYPE_SUBMISSION);
// FIXME: How should this interact with the workflow system?
// FIXME: Remove license
// FIXME: Provenance statement?
// Create the new workspace item row
WorkspaceItem workspaceItem = workspaceItemService.create(c, wfi);
workspaceItem.setMultipleFiles(wfi.hasMultipleFiles());
workspaceItem.setMultipleTitles(wfi.hasMultipleTitles());
workspaceItem.setPublishedBefore(wfi.isPublishedBefore());
workspaceItemService.update(c, workspaceItem);
//myitem.update();
log.info(LogHelper.getHeader(c, "return_to_workspace",
"workflow_item_id=" + wfi.getID() + "workspace_item_id="
+ workspaceItem.getID()));
// Now remove the workflow object manually from the database
xmlWorkflowItemService.deleteWrapper(c, wfi);
return workspaceItem;
}
@Override
public String getEPersonName(EPerson ePerson) {
String submitter = ePerson.getFullName();
submitter = submitter + " (" + ePerson.getEmail() + ")";
return submitter;
}
// Create workflow start provenance message
protected void recordStart(Context context, Item myitem, Action action)
throws SQLException, IOException, AuthorizeException {
// get date
DCDate now = DCDate.getCurrent();
// Create provenance description
StringBuffer provmessage = new StringBuffer();
//behavior to generate provenance message, if set true, personal data (e.g. email) of submitter will be hidden
//default value false, personal data of submitter will be shown in provenance message
String isProvenancePrivacyActiveProperty =
configurationService.getProperty("metadata.privacy.dc.description.provenance", "false");
boolean isProvenancePrivacyActive = Boolean.parseBoolean(isProvenancePrivacyActiveProperty);
if (myitem.getSubmitter() != null && !isProvenancePrivacyActive) {
provmessage.append("Submitted by ").append(myitem.getSubmitter().getFullName())
.append(" (").append(myitem.getSubmitter().getEmail()).append(") on ")
.append(now.toString());
} else {
// else, null submitter
provmessage.append("Submitted by unknown (probably automated or submitter hidden) on ")
.append(now.toString());
}
if (action != null) {
provmessage.append(" workflow start=").append(action.getProvenanceStartId()).append("\n");
} else {
provmessage.append("\n");
}
// add sizes and checksums of bitstreams
provmessage.append(installItemService.getBitstreamProvenanceMessage(context, myitem));
// Add message to the DC
itemService
.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provmessage.toString());
itemService.update(context, myitem);
}
protected void notifyOfReject(Context c, XmlWorkflowItem wi, EPerson e,
String reason) {
try {
// send the notification only if the person was not deleted in the
// meantime between submission and archiving.
EPerson eperson = wi.getSubmitter();
if (eperson != null) {
// Get the item title
String title = wi.getItem().getName();
// Get the collection
Collection coll = wi.getCollection();
// Get rejector's name
String rejector = getEPersonName(e);
Locale supportedLocale = I18nUtil.getEPersonLocale(e);
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_reject"));
email.addRecipient(eperson.getEmail());
email.addArgument(title);
email.addArgument(coll.getName());
email.addArgument(rejector);
email.addArgument(reason);
email.addArgument(configurationService.getProperty("dspace.ui.url") + "/mydspace");
email.send();
} else {
// DO nothing
}
} catch (IOException | MessagingException ex) {
// log this email error
log.warn(LogHelper.getHeader(c, "notify_of_reject",
"cannot email user" + " eperson_id" + e.getID()
+ " eperson_email" + e.getEmail()
+ " workflow_item_id" + wi.getID()), ex);
}
}
@Override
public String getMyDSpaceLink() {
return configurationService.getProperty("dspace.ui.url") + "/mydspace";
}
protected void revokeReviewerPolicies(Context context, Item item) throws SQLException, AuthorizeException {
List bundles = item.getBundles();
for (Bundle originalBundle : bundles) {
// remove bitstream and bundle level policies
for (Bitstream bitstream : originalBundle.getBitstreams()) {
authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_WORKFLOW);
}
authorizeService.removeAllPoliciesByDSOAndType(context, originalBundle, ResourcePolicy.TYPE_WORKFLOW);
}
// remove item level policies
authorizeService.removeAllPoliciesByDSOAndType(context, item, ResourcePolicy.TYPE_WORKFLOW);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy