/**
* 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 org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.*;
import org.dspace.content.Collection;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.*;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.handle.service.HandleService;
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.*;
import org.dspace.xmlworkflow.storedcomponents.*;
import org.dspace.xmlworkflow.storedcomponents.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
/**
* When an item is submitted and is somewhere in a workflow, it has a row in the
* 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<UUID, Boolean> noEMail = new HashMap<>();
private Logger log = Logger.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;
protected XmlWorkflowServiceImpl()
{
}
@Override
public void addInitialWorkspaceItemPolicies(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException {
//Not used, rights are added dynamically.
}
@Override
public void deleteCollection(Context context, Collection collection) throws SQLException, IOException, AuthorizeException {
xmlWorkflowItemService.deleteByCollection(context, collection);
collectionRoleService.deleteByCollection(context, collection);
}
@Override
public List<String> getEPersonDeleteConstraints(Context context, EPerson ePerson) throws SQLException {
List<String> constraints = new ArrayList<String>();
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);
if (roleGroup == null){
authorizeService.authorizeAction(context, collection, Constants.WRITE);
roleGroup = groupService.create(context);
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;
} catch (WorkflowConfigurationException e) {
throw new WorkflowException(e);
}
}
@Override
public List<String> 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){
archive(context, wfi);
}else{
activateFirstStep(context, wf, firstStep, wfi);
}
}
// remove the WorkspaceItem
workspaceItemService.deleteWrapper(context, wsi);
context.restoreAuthSystemState();
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
/**
* startWithoutNotify() starts the workflow normally, but disables
* notifications (useful for large imports,) for the first workflow step -
* subsequent notifications happen normally
*/
@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<EPerson> 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 {
//A list of policies the user has for this item
List<Integer> userHasPolicies = new ArrayList<Integer>();
List<ResourcePolicy> itempols = authorizeService.getPolicies(context, item);
EPerson submitter = item.getSubmitter();
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);
}
protected void activateFirstStep(Context context, Workflow wf, Step firstStep, XmlWorkflowItem wfi) throws AuthorizeException, IOException, SQLException, WorkflowException, WorkflowConfigurationException{
WorkflowActionConfig firstActionConfig = firstStep.getUserSelectionMethod();
firstActionConfig.getProcessingAction().activate(context, wfi);
log.info(LogManager.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 activate it
if (!firstActionConfig.requiresUI()){
ActionResult outcome = firstActionConfig.getProcessingAction().execute(context, wfi, firstStep, null);
processOutcome(context, null, wf, firstStep, firstActionConfig, outcome, wfi, true);
}
}
/*
* Executes an action and returns the next.
*/
@Override
public WorkflowActionConfig doState(Context c, EPerson user, HttpServletRequest request, int workflowItemId, Workflow workflow, WorkflowActionConfig currentActionConfig) throws SQLException, AuthorizeException, IOException, MessagingException, WorkflowException {
try {
XmlWorkflowItem wi = xmlWorkflowItemService.find(c, workflowItemId);
Step currentStep = currentActionConfig.getStep();
if (currentActionConfig.getProcessingAction().isAuthorized(c, request, wi)){
ActionResult outcome = currentActionConfig.getProcessingAction().execute(c, wi, currentStep, request);
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(LogManager.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 ourselfs that we go back to the submission page
c.restoreAuthSystemState();
return null;
}else
if (currentOutcome.getType() == ActionResult.TYPE.TYPE_OUTCOME) {
Step nextStep = null;
WorkflowActionConfig nextActionConfig = null;
try {
//We have completed our action search & retrieve the next action
if (currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE){
nextActionConfig = currentStep.getNextAction(currentActionConfig);
}
if (nextActionConfig != null) {
//We remain in the current step since an 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 {
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 his/her 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 (Exception e){
log.error("error while processing workflow outcome", e);
e.printStackTrace();
}
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(LogManager.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<EPerson> currentEpersonOwners = new ArrayList<EPerson>();
List<Group> currentGroupOwners = new ArrayList<Group>();
//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<ClaimedTask> claimedTasks = claimedTaskService.find(c, wfi, newStep.getId());
List<PoolTask> 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 (Exception e) {
//Catch all errors we do not want our workflow to crash because the logging threw an exception
log.error(LogManager.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.
*/
@Override
public 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(LogManager.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(LogManager.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();
// 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<MetadataValue> titles = itemService.getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY);
String title = "";
try {
title = I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.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(LogManager.getHeader(context, "notifyOfArchive",
"cannot email user" + " item_id=" + item.getID()));
}
}
/***********************************
* WORKFLOW TASK MANAGEMENT
**********************************/
/**
* Deletes all tasks from this workflowflowitem
* @param context the dspace context
* @param wi the workflow item for which we are to delete the tasks
* @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.
*/
@Override
public void deleteAllTasks(Context context, XmlWorkflowItem wi) throws SQLException, AuthorizeException {
deleteAllPooledTasks(context, wi);
Iterator<ClaimedTask> 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<PoolTask> allPooledTasks = poolTaskService.find(context, wi).iterator();
while (allPooledTasks.hasNext()) {
PoolTask poolTask = allPooledTasks.next();
allPooledTasks.remove();
deletePooledTask(context, wi, poolTask);
}
}
/*
* Deletes an eperson from the taskpool of a step
*/
@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);
}
}
/*
* Creates a task pool for a given step
*/
@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);
}
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);
}
}
/*
* Claims an action for a given eperson
*/
@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);
}
public void grantUserAllItemPolicies(Context context, Item item, EPerson epa) throws AuthorizeException, SQLException {
if (epa != null){
//A list of policies the user has for this item
List<Integer> userHasPolicies = new ArrayList<Integer>();
List<ResourcePolicy> 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);
if (!userHasPolicies.contains(Constants.WRITE))
addPolicyToItem(context, item, Constants.WRITE, epa);
if (!userHasPolicies.contains(Constants.DELETE))
addPolicyToItem(context, item, Constants.DELETE, epa);
if (!userHasPolicies.contains(Constants.ADD))
addPolicyToItem(context, item, Constants.ADD, epa);
if (!userHasPolicies.contains(Constants.REMOVE))
addPolicyToItem(context, item, Constants.REMOVE, epa);
}
}
protected void grantGroupAllItemPolicies(Context context, Item item, Group group) throws AuthorizeException, SQLException {
if (group != null){
//A list of policies the user has for this item
List<Integer> groupHasPolicies = new ArrayList<Integer>();
List<ResourcePolicy> 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);
if (!groupHasPolicies.contains(Constants.WRITE))
addGroupPolicyToItem(context, item, Constants.WRITE, group);
if (!groupHasPolicies.contains(Constants.DELETE))
addGroupPolicyToItem(context, item, Constants.DELETE, group);
if (!groupHasPolicies.contains(Constants.ADD))
addGroupPolicyToItem(context, item, Constants.ADD, group);
if (!groupHasPolicies.contains(Constants.REMOVE))
addGroupPolicyToItem(context, item, Constants.REMOVE, group);
}
}
protected void addPolicyToItem(Context context, Item item, int type, EPerson epa) throws AuthorizeException, SQLException {
if (epa != null){
authorizeService.addPolicy(context, item, type, epa);
List<Bundle> bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.addPolicy(context, bundle, type, epa);
List<Bitstream> bits = bundle.getBitstreams();
for (Bitstream bit : bits) {
authorizeService.addPolicy(context, bit, type, epa);
}
}
}
}
protected void addGroupPolicyToItem(Context context, Item item, int type, Group group) throws AuthorizeException, SQLException {
if (group != null){
authorizeService.addPolicy(context, item, type, group);
List<Bundle> bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.addPolicy(context, bundle, type, group);
List<Bitstream> bits = bundle.getBitstreams();
for (Bitstream bit : bits) {
authorizeService.addPolicy(context, bit, type, group);
}
}
}
}
public void removeUserItemPolicies(Context context, Item item, EPerson e) throws SQLException, AuthorizeException {
if (e != null){
//Also remove any lingering authorizations from this user
authorizeService.removeEPersonPolicies(context, item, e);
//Remove the bundle rights
List<Bundle> bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.removeEPersonPolicies(context, bundle, e);
List<Bitstream> bitstreams = bundle.getBitstreams();
for (Bitstream bitstream : bitstreams) {
authorizeService.removeEPersonPolicies(context, bitstream, e);
}
}
// Ensure that the submitter always retains his 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){
//Also remove any lingering authorizations from this user
authorizeService.removeGroupPolicies(context, item, e);
//Remove the bundle rights
List<Bundle> bundles = item.getBundles();
for (Bundle bundle : bundles) {
authorizeService.removeGroupPolicies(context, bundle, e);
List<Bitstream> bitstreams = bundle.getBitstreams();
for (Bitstream bitstream : bitstreams) {
authorizeService.removeGroupPolicies(context, bitstream, e);
}
}
}
}
@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, MetadataSchema.DC_SCHEMA, "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);
// convert into personal workspace
WorkspaceItem wsi = returnToWorkspace(context, wi);
// notify that it's been rejected
notifyOfReject(context, wi, e, rejection_message);
log.info(LogManager.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(LogManager.getHeader(c, "abort_workflow", "workflow_item_id="
+ wi.getID() + "item_id=" + wsi.getItem().getID()
+ "collection_id=" + wi.getCollection().getID() + "eperson_id="
+ e.getID()));
c.restoreAuthSystemState();
return wsi;
}
/**
* 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());
// 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(LogManager.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
String provmessage = "";
if (myitem.getSubmitter() != null)
{
provmessage = "Submitted by " + myitem.getSubmitter().getFullName()
+ " (" + myitem.getSubmitter().getEmail() + ") on "
+ now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n";
}
else
// null submitter
{
provmessage = "Submitted by unknown (probably automated) on"
+ now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n";
}
// add sizes and checksums of bitstreams
provmessage += installItemService.getBitstreamProvenanceMessage(context, myitem);
// Add message to the DC
itemService.addMetadata(context, myitem, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provmessage);
itemService.update(context, myitem);
}
protected void notifyOfReject(Context c, XmlWorkflowItem wi, EPerson e,
String reason)
{
try
{
// 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(wi.getSubmitter().getEmail());
email.addArgument(title);
email.addArgument(coll.getName());
email.addArgument(rejector);
email.addArgument(reason);
email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/mydspace");
email.send();
}
catch (Exception ex)
{
// log this email error
log.warn(LogManager.getHeader(c, "notify_of_reject",
"cannot email user" + " eperson_id" + e.getID()
+ " eperson_email" + e.getEmail()
+ " workflow_item_id" + wi.getID()));
}
}
@Override
public String getMyDSpaceLink() {
return ConfigurationManager.getProperty("dspace.url") + "/mydspace";
}
}