diff --git a/config/alfresco/workflow/invitation-nominated-workflow-messages.properties b/config/alfresco/workflow/invitation-nominated-workflow-messages.properties index 5b55ed86dc..9d4c7643e7 100644 --- a/config/alfresco/workflow/invitation-nominated-workflow-messages.properties +++ b/config/alfresco/workflow/invitation-nominated-workflow-messages.properties @@ -30,6 +30,8 @@ inwf_invite-workflow-model.property.inwf_inviteeRole.title=Invitee Role # Invite Process Definitions inwf_invitation-nominated.node.start.title=Start inwf_invitation-nominated.node.start.description=Send an invitation +inwf_invitation-nominated.node.start.transition.sendInvite.title=Send Invite +inwf_invitation-nominated.node.start.transition.sendInvite.description=Send Invite inwf_invitation-nominated.node.invitePending.title=Invite Pending inwf_invitation-nominated.node.invitePending.description=Invite Pending diff --git a/source/java/org/alfresco/repo/workflow/WorkflowModel.java b/source/java/org/alfresco/repo/workflow/WorkflowModel.java index 786f11333b..5f2a7e2a2d 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowModel.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowModel.java @@ -57,6 +57,7 @@ public interface WorkflowModel static final QName PROP_PACKAGE_ACTION_GROUP = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "packageActionGroup"); static final QName PROP_PACKAGE_ITEM_ACTION_GROUP = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "packageItemActionGroup"); static final QName PROP_HIDDEN_TRANSITIONS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "hiddenTransitions"); + static final QName PROP_REASSIGNABLE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "reassignable"); static final QName ASSOC_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package"); // workflow task contstants diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 344e20a080..51fd183968 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -22,6 +22,7 @@ package org.alfresco.repo.workflow; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -647,6 +648,121 @@ public class WorkflowServiceImpl implements WorkflowService return component.endTask(taskId, transition); } + /* + * @see org.alfresco.service.cmr.workflow.WorkflowService#isTaskEditable(org.alfresco.service.cmr.workflow.WorkflowTask, java.lang.String) + */ + public boolean isTaskEditable(WorkflowTask task, String username) + { + // if the task is complete it is not editable + if (task.getState() == WorkflowTaskState.COMPLETED) + { + return false; + } + + if (isUserOwnerOrInitiator(task, username)) + { + // editable if the current user is the task owner or initiator + return true; + } + + // if the current user is not the owner or initiator check whether they are + // a member of the pooled actors for the task (if it has any) + return isUserInPooledActors(task, username); + } + + /* + * @see org.alfresco.service.cmr.workflow.WorkflowService#isTaskReassignable(org.alfresco.service.cmr.workflow.WorkflowTask, java.lang.String) + */ + public boolean isTaskReassignable(WorkflowTask task, String username) + { + // if the task is complete it is not reassignable + if (task.getState() == WorkflowTaskState.COMPLETED) + { + return false; + } + + // if the task has the 'reassignable' property set to false it can not be reassigned + Map properties = task.getProperties(); + Boolean reassignable = (Boolean)properties.get(WorkflowModel.PROP_REASSIGNABLE); + if (reassignable != null && reassignable.booleanValue() == false) + { + return false; + } + + // if the task has pooled actors and an owner it can not be reassigned (it must be released) + Collection actors = (Collection) properties.get(WorkflowModel.ASSOC_POOLED_ACTORS); + String owner = (String)properties.get(ContentModel.PROP_OWNER); + if (actors != null && !actors.isEmpty() && owner != null) + { + return false; + } + + if (isUserOwnerOrInitiator(task, username)) + { + // reassignable if the current user is the task owner or initiator + return true; + } + + return false; + } + + /* + * @see org.alfresco.service.cmr.workflow.WorkflowService#isTaskClaimable(org.alfresco.service.cmr.workflow.WorkflowTask, java.lang.String) + */ + public boolean isTaskClaimable(WorkflowTask task, String username) + { + // if the task is complete it is not claimable + if (task.getState() == WorkflowTaskState.COMPLETED) + { + return false; + } + + // if the task has an owner it can not be claimed + if (task.getProperties().get(ContentModel.PROP_OWNER) != null) + { + return false; + } + + // a task can only be claimed if the user is a member of + // of the pooled actors for the task + return isUserInPooledActors(task, username); + } + + /* + * @see org.alfresco.service.cmr.workflow.WorkflowService#isTaskReleasable(org.alfresco.service.cmr.workflow.WorkflowTask, java.lang.String) + */ + public boolean isTaskReleasable(WorkflowTask task, String username) + { + // if the task is complete it is not releasable + if (task.getState() == WorkflowTaskState.COMPLETED) + { + return false; + } + + // if the task doesn't have pooled actors it is not releasable + Map properties = task.getProperties(); + Collection actors = (Collection) properties.get(WorkflowModel.ASSOC_POOLED_ACTORS); + if (actors == null || actors.isEmpty()) + { + return false; + } + + // if the task does not have an owner it is not releasable + String owner = (String)properties.get(ContentModel.PROP_OWNER); + if (owner == null) + { + return false; + } + + if (isUserOwnerOrInitiator(task, username)) + { + // releasable if the current user is the task owner or initiator + return true; + } + + return false; + } + /* * (non-Javadoc) * @see @@ -837,4 +953,88 @@ public class WorkflowServiceImpl implements WorkflowService } return contents; } + + /** + * Determines if the given user is a member of the pooled actors assigned to the task + * + * @param task The task instance to check + * @param username The username to check + * @return true if the user is a pooled actor, false otherwise + */ + private boolean isUserInPooledActors(WorkflowTask task, String username) + { + // get groups that the current user has to belong (at least one of them) + final Collection actors = (Collection)task.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS); + if (actors != null && !actors.isEmpty()) + { + for (Object actor : actors) + { + // retrieve the name of the group + Map props = nodeService.getProperties((NodeRef)actor); + String name = (String)props.get(ContentModel.PROP_AUTHORITY_NAME); + + // retrieve the users of the group + Set users = this.authorityService.getContainedAuthorities(AuthorityType.USER, name, true); + + // see if the user is one of the users in the group + if (users != null && !users.isEmpty() && users.contains(username)) + { + // they are a member of the group so stop looking! + return true; + } + } + } + + return false; + } + + /** + * Determines if the given user is the owner of the given task or + * the initiator of the workflow the task is part of + * + * @param task The task to check + * @param username The username to check + * @return true if the user is the owner or the workflow initiator + */ + private boolean isUserOwnerOrInitiator(WorkflowTask task, String username) + { + boolean result = false; + String owner = (String)task.getProperties().get(ContentModel.PROP_OWNER); + + if (username.equals(owner)) + { + // user owns the task + result = true; + } + else if (username.equals(getWorkflowInitiatorUsername(task))) + { + // user is the workflow initiator + result = true; + } + + return result; + } + + /** + * Returns the username of the user that initiated the workflow the + * given task is part of. + * + * @param task The task to get the workflow initiator for + * @return Username or null if the initiator could not be found + */ + private String getWorkflowInitiatorUsername(WorkflowTask task) + { + String initiator = null; + + NodeRef initiatorRef = task.getPath().getInstance().getInitiator(); + + if (initiator != null) + { + // TODO: deal with missing users! + + initiator = (String)this.nodeService.getProperty(initiatorRef, ContentModel.PROP_USERNAME); + } + + return initiator; + } } diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index a6720f8627..cdbc7328c0 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -207,6 +207,7 @@ public interface WorkflowService * * @param workflowDefinitionId the workflow definition id * @return the list of "in-flight" workflow instances + * @since 3.4 */ @Auditable(parameters = {"workflowDefinitionId"}) public List getCompletedWorkflows(String workflowDefinitionId); @@ -216,6 +217,7 @@ public interface WorkflowService * * @param workflowDefinitionId the workflow definition id * @return the list of "in-flight" workflow instances + * @since 3.4 */ @Auditable(parameters = {"workflowDefinitionId"}) public List getWorkflows(String workflowDefinitionId); @@ -374,6 +376,49 @@ public interface WorkflowService @Auditable(parameters = {"taskId", "transitionId"}) public WorkflowTask endTask(String taskId, String transitionId); + /** + * Determines if the given user can edit the given task + * + * @param task The task to check + * @param username The user to check + * @return true if the user can edit the task + * @since 3.4 + */ + @Auditable(parameters = {"task", "username"}) + public boolean isTaskEditable(WorkflowTask task, String username); + + /** + * Determines if the given user can reassign the given task + * + * @param task The task to check + * @param username The user to check + * @return true if the user can reassign the task + * @since 3.4 + */ + @Auditable(parameters = {"task", "username"}) + public boolean isTaskReassignable(WorkflowTask task, String username); + + /** + * Determines if the given user can claim the given task + * + * @param task The task to check + * @param username The user to check + * @return true if the user can claim the task + * @since 3.4 + */ + @Auditable(parameters = {"task", "username"}) + public boolean isTaskClaimable(WorkflowTask task, String username); + + /** + * Determines if the given user can release the given task + * + * @param task The task to check + * @param username The user to check + * @return true if the user can release the task + * @since 3.4 + */ + @Auditable(parameters = {"task", "username"}) + public boolean isTaskReleasable(WorkflowTask task, String username); // // Package Management