diff --git a/config/alfresco/activiti-context.xml b/config/alfresco/activiti-context.xml new file mode 100644 index 0000000000..9d8566e264 --- /dev/null +++ b/config/alfresco/activiti-context.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 8e19af941c..5d0d8183eb 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -288,6 +288,46 @@ text/xml false + + + + activiti + alfresco/workflow/adhoc.bpmn20.xml + text/xml + false + + + + + activiti + alfresco/workflow/review.bpmn20.xml + text/xml + false + + + + + activiti + alfresco/workflow/review-pooled.bpmn20.xml + text/xml + false + + + + + activiti + alfresco/workflow/parallel-review.bpmn20.xml + text/xml + false + + + + + activiti + alfresco/workflow/parallel-review-group.bpmn20.xml + text/xml + false + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 3a2a8b77e4..40c225ff1a 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -478,6 +478,7 @@ alfresco.messages.avm-messages alfresco.messages.content-filter-languages alfresco.messages.jbpm-engine-messages + alfresco.messages.activiti-engine-messages diff --git a/config/alfresco/extension/activiti-adhoc-timer-messages.properties.sample b/config/alfresco/extension/activiti-adhoc-timer-messages.properties.sample new file mode 100644 index 0000000000..dffb291c2b --- /dev/null +++ b/config/alfresco/extension/activiti-adhoc-timer-messages.properties.sample @@ -0,0 +1,3 @@ +# For Activiti Timer Workflow Example +activitiAdhocTimer.workflow.title=Activiti Adhoc Timer +activitiAdhocTimer.workflow.description=Activiti Adhoc Timer Sample Process \ No newline at end of file diff --git a/config/alfresco/extension/activiti-adhoc-timer-workflow-context.xml.sample b/config/alfresco/extension/activiti-adhoc-timer-workflow-context.xml.sample new file mode 100644 index 0000000000..1c6248958b --- /dev/null +++ b/config/alfresco/extension/activiti-adhoc-timer-workflow-context.xml.sample @@ -0,0 +1,22 @@ + + + + + + + + + activiti + alfresco/extension/activiti-adhoc-timer.bpmn20.xml + text/xml + false + + + + + + alfresco/extension/activiti-adhoc-timer-messages + + + + diff --git a/config/alfresco/extension/activiti-adhoc-timer.bpmn20.xml.sample b/config/alfresco/extension/activiti-adhoc-timer.bpmn20.xml.sample new file mode 100644 index 0000000000..58cacbf13d --- /dev/null +++ b/config/alfresco/extension/activiti-adhoc-timer.bpmn20.xml.sample @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + ${bpm_assignee.properties.userName} + + + + + + + + + PT1H + + + + + + + + The urgent task was completed on time by ${bpm_assignee.properties.userName}. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + ${initiator.properties.userName} + + + + + + + The urgent task was NOT completed on time by ${bpm_assignee.properties.userName}. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + ${initiator.properties.userName} + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/lifecycle-messages.properties.sample b/config/alfresco/extension/lifecycle-messages.properties.sample index 0d41867744..1b28f212e4 100644 --- a/config/alfresco/extension/lifecycle-messages.properties.sample +++ b/config/alfresco/extension/lifecycle-messages.properties.sample @@ -1,7 +1,11 @@ -# For Lifecycle Workflow Example +# For JBPM Lifecycle Workflow Example wfl_lifecycleapproval.workflow.title=Lifecycle Review & Approve wfl_lifecycleapproval.workflow.description=Lifecycle Review & Approval workflow (Auto updates document status) wfl_lifecycleapproval.node.review.transition.reject.title=Reject wfl_lifecycleapproval.node.review.transition.reject.description=Reject wfl_lifecycleapproval.node.review.transition.approve.title=Approve -wfl_lifecycleapproval.node.review.transition.approve.description=Approve \ No newline at end of file +wfl_lifecycleapproval.node.review.transition.approve.description=Approve + +# For Activiti Lifecycle Workflow Example +activitiLifecycleApproval=Lifecycle Review & Approve +activitiLifecycleApproval=Lifecycle Review & Approval workflow Activiti (Auto updates document status) \ No newline at end of file diff --git a/config/alfresco/extension/lifecycle-process.bpmn20.xml.sample b/config/alfresco/extension/lifecycle-process.bpmn20.xml.sample new file mode 100644 index 0000000000..09fa0c48fb --- /dev/null +++ b/config/alfresco/extension/lifecycle-process.bpmn20.xml.sample @@ -0,0 +1,173 @@ + + + + + + + + + + + if(cancelled || deleted) { + for (var i = 0; i < bpm_package.children.length; i++) + { + if (!bpm_package.children[i].hasAspect("wfl:status")) + { + bpm_package.children[i].properties["wfl:status"] = "Draft"; + bpm_package.children[i].save(); + } + } + } + + + + + + + + + + + + for (var i = 0; i < bpm_package.children.length; i++) + { + if (!bpm_package.children[i].hasAspect("wfl:status")) + { + bpm_package.children[i].addAspect("wfl:status"); + } + } + + + + + + + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + for (var i = 0; i < bpm_package.children.length; i++) + { + if (bpm_package.children[0].hasAspect("wfl:status")) { + bpm_package.children[i].properties["wfl:status"] = "In Review"; + bpm_package.children[i].save(); + } + } + + + + + + + execution.setVariable('wf_reviewOutcome', task.getVariable('wf_reviewOutcome')); + + + + + + + ${bpm_assignee.properties.userName} + + + + + + + + + + ${wf_reviewOutcome == 'Approve'} + + + + + + + The document was reviewed and approved. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + for (var i = 0; i < bpm_package.children.length; i++) + { + if (bpm_package.children[0].hasAspect("wfl:status")) { + bpm_package.children[i].properties["wfl:status"] = "Approved"; + bpm_package.children[i].save(); + } + } + + + + + + + ${initiator.properties.userName} + + + + + + + The document was reviewed and rejected. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + for (var i = 0; i < bpm_package.children.length; i++) + { + if (bpm_package.children[0].hasAspect("wfl:status")) + { + bpm_package.children[i].properties["wfl:status"] = "Draft"; + bpm_package.children[i].save(); + } + } + + + + + + + ${initiator.properties.userName} + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/lifecycle-workflow-context.xml.sample b/config/alfresco/extension/lifecycle-workflow-context.xml.sample index e2668b7b63..650c878a19 100644 --- a/config/alfresco/extension/lifecycle-workflow-context.xml.sample +++ b/config/alfresco/extension/lifecycle-workflow-context.xml.sample @@ -6,12 +6,20 @@ + jbpm alfresco/extension/lifecycle_processdefinition.xml text/xml false + + + activiti + alfresco/extension/lifecycle-process.bpmn20.xml + text/xml + false + diff --git a/config/alfresco/messages/activiti-engine-messages.properties b/config/alfresco/messages/activiti-engine-messages.properties new file mode 100644 index 0000000000..36227e3ab6 --- /dev/null +++ b/config/alfresco/messages/activiti-engine-messages.properties @@ -0,0 +1,54 @@ +activiti.engine.mandatory.properties.missing=Mandatory task properties have not been provided! {0} +activiti.engine.deploy.workflow.error=Failed to deploy workflow definition. +activiti.engine.is.workflow.deployed.error=Failed to determine if workflow definition is already deployed. +activiti.engine.undeploy.workflow.error=Failed to undeploy workflow definition {0}. +activiti.engine.undeploy.workflow.unexisting.error=Failed to undeploy unexisting workflow definition {0}. +activiti.engine.get.workflow.definition.error=Failed to retrieve workflow definitions. +activiti.engine.get.workflow.definition.by.id.error=Failed to retrieve workflow definition for id {0}. +activiti.engine.get.workflow.definition.by.name.error=Failed to retrieve workflow definition for name {0}. +activiti.engine.get.all.workflow.definitions.by.name.error=Failed to retrieve all definitions for workflow {0}. +activiti.engine.get.workflow.definition.image.error=Failed to retrieve workflow definition image for {0}. +activiti.engine.get.workflow.definition.unexisting.image.error=Failed to retrieve workflow definition image for {0}, the definition does not exist. +activiti.engine.get.task.definitions.error=Failed to retrieve workflow task definitions for workflow definition {0}. +activiti.engine.get.process.definition.error=Workflow definition {0} does not exist. +activiti.engine.start.workflow.error=Failed to start workflow {0}. +activiti.engine.start.workflow.no.start.task.error=Failed to start workflow {0}, no start task defined on process definition. +activiti.engine.get.workflows.error=Failed to retrieve workflow instances for definition {0}. +activiti.engine.get.active.workflows.error=Failed to retrieve workflow instances for definition {0}. +activiti.engine.get.completed.workflows.error=Failed to retrieve workflow instances for definition {0}. +activiti.engine.get.workflow.instance.by.id.error=Failed to retrieve workflow instance with Id {0}. +activiti.engine.get.process.instance.error=Workflow instance does not exist for Id {0}. +activiti.engine.get.workflow.paths.error=Failed to retrieve workflow paths for workflow instance {0}. +activiti.engine.get.path.properties.error=Failed to retrieve properties of path {0}. +activiti.engine.cancel.workflow.error=Failed to cancel workflow instance {0}. +activiti.engine.cancel.unexisting.workflow.error=Failed to cancel unexisting workflow instance {0}. +activiti.engine.delete.workflow.error=Failed to delete workflow instance {0}. +activiti.engine.delete.unexisting.workflow.error=Failed to delete unexisting workflow instance {0}. +activiti.engine.signal.transition.error=Failed to signal transition {0} from workflow path {1}.. +activiti.engine.event.unsupported=Firing events is not supported on WorkflowPaths runnin in the Activiti engine. +activiti.engine.fire.event.error=Failed to fire event {0} on workflow path {1}. +activiti.engine.get.tasks.for.path.error=Failed to retrieve tasks assigned to Workflow path {0}. +activiti.engine.get.timers.error=Could not retrieve any timers for workflow {0}. +activiti.engine.find.completed.task.instances.error=Failed to retrieve completed task instances list for actor {0}. +activiti.engine.get.assigned.tasks.error=Failed to retrieve tasks assigned to authority {0} in state {1}. +activiti.engine.get.pooled.tasks.error=Failed to retrieve pooled tasks for authorities {0}. +activiti.engine.query.tasks.error=Failed to query tasks. Query: {0}. +activiti.engine.get.task.instance.error=Task instance {0} does not exist. +activiti.engine.update.task.error=Failed to update workflow task {0}. +activiti.engine.update.task.unexisting.error=Failed to update workflow task {0}, the task doesn't exist. +activiti.engine.update.starttask.illegal.error=Failed to update workflow task {0}, start tasks cannot be updated in activiti. +activiti.engine.end.task.invalid.transition=Transition {0} is invalid for Workflow task {1}, only transition {1} is allowed. +activiti.engine.end.task.unexisting.error=Failed to signal transition on task {0}, the task doesn't exist. +activiti.engine.end.task.error=Failed to signal transition {0} from workflow task {1}. +activiti.engine.get.task.by.id.error=Failed to retrieve task {0}. +activiti.engine.compile.process.definition.zip.error=Failed to parse process definition from activiti zip archive stream. +activiti.engine.compile.process.definition.xml.error=Failed to parse process definition from activiti xml stream. +activiti.engine.compile.process.definition.unsupported.error=Failed to parse process definition - unsupported mime type {0} +activiti.engine.get.activiti.id.error=Format of id {0} is invalid. +activiti.engine.get.workflow.token.invalid=Invalid workflow path {0}. +activiti.engine.get.workflow.token.is.null=Workflow path {0} does not exist. +activiti.engine.set.task.properties.invalid.value=The value {0} is invalid for the task property {1}. +activiti.engine.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". +activiti.engine.convert.value.error=Unable to convert activiti value {0} to Alfresco Value since it is not Serializable. +activiti.engine.get.company.home.invalid=Invalid company home path {0}. +activiti.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. \ No newline at end of file diff --git a/config/alfresco/messages/bpm-messages.properties b/config/alfresco/messages/bpm-messages.properties index 242036920c..cc68f91eb1 100644 --- a/config/alfresco/messages/bpm-messages.properties +++ b/config/alfresco/messages/bpm-messages.properties @@ -70,3 +70,7 @@ bpm_businessprocessmodel.association.bpm_groupAssignee.title=Workflow Group Assi bpm_businessprocessmodel.association.bpm_groupAssignee.description=Workflow Group Assignee bpm_businessprocessmodel.association.bpm_groupAssignees.title=Workflow Group Assignees bpm_businessprocessmodel.association.bpm_groupAssignees.description=Workflow Group Assignees + +# Error Messages +workflow.get.task.definition.metadata.error=Failed to find task type definition {0}. +workflow.package.already.associated.error=This node is already being used as a workflow package! NodeRef: {0} \ No newline at end of file diff --git a/config/alfresco/messages/jbpm-engine-messages.properties b/config/alfresco/messages/jbpm-engine-messages.properties index b47470bf71..55b09a012f 100644 --- a/config/alfresco/messages/jbpm-engine-messages.properties +++ b/config/alfresco/messages/jbpm-engine-messages.properties @@ -31,15 +31,14 @@ jbpm.engine.update.task.error=Failed to update workflow task {0}. jbpm.engine.end.task.invalid.transition=Transition {0} is invalid for Workflow task {1}. jbpm.engine.end.task.error=Failed to signal transition {0} from workflow task {1}. jbpm.engine.get.task.by.id.error=Failed to retrieve task {0}. +jbpm.engine.get.start.task.error=Failed to retrieve start task for workflow instance {0}. jbpm.engine.compile.process.definition.zip.error=Failed to parse process definition from jBPM zip archive stream. jbpm.engine.compile.process.definition.xml.error=Failed to parse process definition from jBPM xml stream. jbpm.engine.compile.process.definition.unsupported.error=Failed to parse process definition - unsupported mime type {0} -jbpm.engine.get.task.definition.error=Failed to find type definition {0}. jbpm.engine.get.jbpm.id.error=Format of id {0} is invalid. jbpm.engine.get.workflow.token.invalid=Invalid workflow path {0}. jbpm.engine.get.workflow.token.is.null=Workflow path {0} does not exist. jbpm.engine.set.task.properties.invalid.value=The value {0} is invalid for the task property {1}. -jbpm.engine.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". jbpm.engine.convert.value.error=Unable to convert jBPM value {0} to Alfresco Value since it is not Serializable. jbpm.engine.get.company.home.invalid=Invalid company home path {0}. jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. \ No newline at end of file diff --git a/config/alfresco/messages/workflow-interpreter-help.txt b/config/alfresco/messages/workflow-interpreter-help.txt index 096b372290..86e9889f21 100644 --- a/config/alfresco/messages/workflow-interpreter-help.txt +++ b/config/alfresco/messages/workflow-interpreter-help.txt @@ -37,11 +37,12 @@ class path to workflow definition file. - ok> deploy + ok> deploy Deploy workflow definition to Alfresco server. class path to workflow definition. + workflow engine name (eg. activiti) ok> redeploy diff --git a/config/alfresco/messages/workflow-package-messages.properties b/config/alfresco/messages/workflow-package-messages.properties new file mode 100644 index 0000000000..68c24f681a --- /dev/null +++ b/config/alfresco/messages/workflow-package-messages.properties @@ -0,0 +1,3 @@ + +# Error Messages +workflow.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". diff --git a/config/alfresco/model/bpmModel.xml b/config/alfresco/model/bpmModel.xml index 72d13d8eee..010bab12a8 100644 --- a/config/alfresco/model/bpmModel.xml +++ b/config/alfresco/model/bpmModel.xml @@ -184,7 +184,7 @@ false - + sys:base false true @@ -271,6 +271,19 @@ + + + + + + bpm:workflowTask + + + + d:qname + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 94b54a2027..7ec1014431 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1792,8 +1792,8 @@ classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-PropertyValueTables.sql - - + + patch.db-V3.2-AuditTables patch.schemaUpgradeScript.description 0 diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 14e12b263d..e93b7c3545 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -7,7 +7,7 @@ version.major=3 version.minor=4 version.revision=0 -version.label=d +version.label=e # Edition label diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index 596de6b2e4..5cecc77b2d 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -40,6 +40,7 @@ + @@ -80,8 +81,12 @@ - + + + + + @@ -100,20 +105,21 @@ - - - - ${spaces.store} /${spaces.company_home.childname} - + + + + + + diff --git a/config/alfresco/workflow/adhoc.bpmn20.xml b/config/alfresco/workflow/adhoc.bpmn20.xml new file mode 100644 index 0000000000..7f11df1dea --- /dev/null +++ b/config/alfresco/workflow/adhoc.bpmn20.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + ${bpm_assignee.properties.userName} + + + + + + + + + Verify the arbitrary task was completed. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + if (wf_notifyMe) + { + var mail = actions.create("mail"); + mail.parameters.to = initiator.properties.email; + mail.parameters.subject = "Adhoc Task " + bpm_workflowDescription; + mail.parameters.from = bpm_assignee.properties.email; + mail.parameters.text = "It's done"; + mail.execute(bpm_package); + } + + + + + + + ${initiator.properties.userName} + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow/parallel-review-group.bpmn20.xml b/config/alfresco/workflow/parallel-review-group.bpmn20.xml new file mode 100644 index 0000000000..449915c219 --- /dev/null +++ b/config/alfresco/workflow/parallel-review-group.bpmn20.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + execution.setVariable('wf_approveCount', 0); + execution.setVariable('wf_actualPercent', 0); + execution.setVariable('wf_requiredPercent', wf_requiredApprovePercent); + + + + + + + + + + + + + + + var members = people.getMembers(bpm_groupAssignee); + var memberNames = new java.util.ArrayList(); + + for(var i in members) { + memberNames.add(members[i].properties.userName); + } + execution.setVariable('wf_groupMembers', memberNames); + execution.setVariable('wf_reviewerCount', memberNames.size()); + + + + + + + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + if(task.getVariableLocal('wf_reviewOutcome') == 'Approve') { + var newApprovedCount = wf_approveCount + 1; + var newApprovedPercentage = (newApprovedCount / wf_reviewerCount) * 100; + + execution.setVariable('wf_approveCount', newApprovedCount); + execution.setVariable('wf_actualPercent', newApprovedPercentage); + } + + + + + + + + ${reviewAssignee} + + + + + + wf_groupMembers + + ${wf_actualPercent >= wf_requiredApprovePercent} + + + + + + + + + ${wf_actualPercent >= wf_requiredApprovePercent} + + + + + + + The document was reviewed and approved. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + // Set parallel review params on task, to be kept in history + task.setVariableLocal('wf_reviewerCount', wf_reviewerCount); + task.setVariableLocal('wf_requiredPercent', wf_requiredPercent); + task.setVariableLocal('wf_actualPercent', wf_actualPercent); + task.setVariableLocal('wf_approveCount', wf_approveCount); + + + + + + + ${initiator.properties.userName} + + + + + + + The document was reviewed and rejected. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + // Set parallel review params on task, to be kept in history + task.setVariableLocal('wf_reviewerCount', wf_reviewerCount); + task.setVariableLocal('wf_requiredPercent', wf_requiredPercent); + task.setVariableLocal('wf_actualPercent', wf_actualPercent); + task.setVariableLocal('wf_approveCount', wf_approveCount); + + + + + + + ${initiator.properties.userName} + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow/parallel-review.bpmn20.xml b/config/alfresco/workflow/parallel-review.bpmn20.xml new file mode 100644 index 0000000000..de9a43b5ff --- /dev/null +++ b/config/alfresco/workflow/parallel-review.bpmn20.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + execution.setVariable('wf_approveCount', 0); + execution.setVariable('wf_actualPercent', 0); + execution.setVariable('wf_reviewerCount', bpm_assignees.size()); + execution.setVariable('wf_requiredPercent', wf_requiredApprovePercent); + + + + + + + + + + + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + if(task.getVariableLocal('wf_reviewOutcome') == 'Approve') { + var newApprovedCount = wf_approveCount + 1; + var newApprovedPercentage = (newApprovedCount / wf_reviewerCount) * 100; + + execution.setVariable('wf_approveCount', newApprovedCount); + execution.setVariable('wf_actualPercent', newApprovedPercentage); + } + + + + + + + + ${review_assignee.properties.userName} + + + + + + bpm_assignees + + ${wf_actualPercent >= wf_requiredApprovePercent} + + + + + + + + + ${wf_actualPercent >= wf_requiredApprovePercent} + + + + + + + The document was reviewed and approved. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + // Set parallel review params on task, to be kept in history + task.setVariableLocal('wf_reviewerCount', wf_reviewerCount); + task.setVariableLocal('wf_requiredPercent', wf_requiredPercent); + task.setVariableLocal('wf_actualPercent', wf_actualPercent); + task.setVariableLocal('wf_approveCount', wf_approveCount); + + + + + + + ${initiator.properties.userName} + + + + + + + The document was reviewed and rejected. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + // Set parallel review params on task, to be kept in history + task.setVariableLocal('wf_reviewerCount', wf_reviewerCount); + task.setVariableLocal('wf_requiredPercent', wf_requiredPercent); + task.setVariableLocal('wf_actualPercent', wf_actualPercent); + task.setVariableLocal('wf_approveCount', wf_approveCount); + + + + + + + ${initiator.properties.userName} + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow/review-pooled.bpmn20.xml b/config/alfresco/workflow/review-pooled.bpmn20.xml new file mode 100644 index 0000000000..f6cdd9ed34 --- /dev/null +++ b/config/alfresco/workflow/review-pooled.bpmn20.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariable('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + execution.setVariable('wf_reviewOutcome', task.getVariable('wf_reviewOutcome')); + + + + + + + + ${bpm_groupAssignee.properties.authorityName} + + + + + + + + + + ${wf_reviewOutcome == 'Approve'} + + + + + + + The document was reviewed and approved. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + execution.setVariable('bpm_assignee', person); + + + + + + + ${initiator.properties.userName} + + + + + + + The document was reviewed and rejected. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + bpm_assignee = person; + + + + + + + ${initiator.properties.userName} + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow/review.bpmn20.xml b/config/alfresco/workflow/review.bpmn20.xml new file mode 100644 index 0000000000..9c30cc7691 --- /dev/null +++ b/config/alfresco/workflow/review.bpmn20.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + execution.setVariable('wf_reviewOutcome', task.getVariable('wf_reviewOutcome')); + + + + + + + ${bpm_assignee.properties.userName} + + + + + + + + + + ${wf_reviewOutcome == 'Approve'} + + + + + + + The document was reviewed and approved. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + ${initiator.properties.userName} + + + + + + + The document was reviewed and rejected. + + + + + + if (typeof bpm_workflowDueDate != 'undefined') task.setVariableLocal('bpm_dueDate', bpm_workflowDueDate); + if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority; + + + + + + + ${initiator.properties.userName} + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow/workflow-messages.properties b/config/alfresco/workflow/workflow-messages.properties index 3a0d830a81..345e247e4e 100644 --- a/config/alfresco/workflow/workflow-messages.properties +++ b/config/alfresco/workflow/workflow-messages.properties @@ -1,5 +1,9 @@ # Display labels for out-of-the-box Content-oriented Workflows +#################### +# JBPM WORKFLOWS # +#################### + # # Review & Approve Workflow # @@ -64,7 +68,9 @@ wf_workflowmodel.property.wf_requiredPercent.description=Required Approval Perce wf_workflowmodel.property.wf_approveCount.title=Reviewers Who Approved wf_workflowmodel.property.wf_approveCount.description=Reviewers who approved wf_workflowmodel.property.wf_actualPercent.title=Actual Approval Percentage -wf_workflowmodel.property.wf_actualPercentdescription=Actual approval percentage +wf_workflowmodel.property.wf_actualPercent.description=Actual approval percentage +wf_workflowmodel.property.wf_reviewOutcome.title=Review Outcome +wf_workflowmodel.property.wf_reviewOutcome.description=Review Outcome # Group Review & Approve Process Definitions @@ -120,3 +126,60 @@ wf_workflowmodel.type.wf_adhocTask.title=Adhoc Task wf_workflowmodel.type.wf_adhocTask.description=Adhoc Task allocated by colleague wf_workflowmodel.type.wf_completedAdhocTask.title=Adhoc Task Completed wf_workflowmodel.type.wf_completedAdhocTask.description=Adhoc Task Completed + +####################### +# ACTIVITI WORKFLOWS # +####################### + +# +# Activiti Adhoc Task Workflow +# + +activitiAdhoc.workflow.title=Activiti Adhoc +activitiAdhoc.workflow.description=Assign Activiti task to colleague + +# Activiti Adhoc Task Definitions + +activitiAdhocmodel.type.wf_submitAdhocTask.title=Start Activiti Adhoc Task +activitiAdhocmodel.type.wf_submitAdhocTask.description=Allocate Activiti task to colleague +activitiAdhocmodel.property.wf_notifyMe.title=Notify Me +activitiAdhocmodel.property.wf_notifyMe.description=Notify me when task is complete +activitiAdhocmodel.type.wf_adhocTask.title=Activiti Adhoc Task +activitiAdhocmodel.type.wf_adhocTask.description=Activiti Adhoc Task allocated by colleague +activitiAdhocmodel.type.wf_completedAdhocTask.title=Activiti Adhoc Task Completed +activitiAdhocmodel.type.wf_completedAdhocTask.description=Activiti Adhoc Task Completed + +# +# Activiti Review & Approve Workflow +# + +activitiReview.workflow.title=Activiti Review & Approve +activitiReview.workflow.description=Review & approval of content + +# Activiti Review & Approve Task Definitions + +activitiReviewmodel.type.wf_submitReviewTask.title=Start Activiti Review +activitiReviewmodel.type.wf_submitReviewTask.description=Submit documents for review & approval +activitiReviewmodel.type.wf_reviewTask.title=Activiti Review +activitiReviewmodel.type.wf_reviewTask.description=Review Documents to Approve or Reject them +activitiReviewmodel.type.wf_rejectedTask.title=Rejected +activitiReviewmodel.type.wf_rejectedTask.description=Rejected +activitiReviewmodel.type.wf_approvedTask.title=Approved +activitiReviewmodel.type.wf_approvedTask.description=Approved + +# Activiti Review & Approve Process Definitions + +activitiReview.node.start.title=Start +activitiReview.node.start.description=Start +activitiReview.node.review.title=Review +activitiReview.node.review.description=Review +activitiReview.node.rejected.title=Rejected +activitiReview.node.rejected.description=Rejected +activitiReview.task.wf_rejectedTask.title=Rejected +wf_review.task.wf_rejectedTask.description=Rejected +activitiReview.node.approved.title=Approved +activitiReview.node.approved.description=Approved +activitiReview.task.wf_approvedTask.title=Approved +activitiReview.task.wf_approvedTask.description=Approved +activitiReview.node.end.title=End +activitiReview.node.end.description=End diff --git a/config/alfresco/workflow/workflowModel.xml b/config/alfresco/workflow/workflowModel.xml index bee2cb7edd..371444be5f 100644 --- a/config/alfresco/workflow/workflowModel.xml +++ b/config/alfresco/workflow/workflowModel.xml @@ -66,6 +66,36 @@ + + bpm:activitiOutcomeTask + + + d:text + Reject + + + + + + Approve + Reject + + + + + + + + + edit_package_item_actions + + + {http://www.alfresco.org/model/workflow/1.0}reviewOutcome + + + + bpm:workflowTask diff --git a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java index be4277ff52..5de79d3191 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java @@ -34,9 +34,12 @@ import org.alfresco.repo.forms.FormData.FieldData; import org.alfresco.repo.forms.PropertyFieldDefinition.FieldConstraint; import org.alfresco.repo.forms.processor.node.FormFieldConstants; import org.alfresco.repo.forms.processor.node.TypeFormProcessor; +import org.alfresco.repo.forms.processor.workflow.TransitionFieldProcessor; import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.security.person.TestPersonManager; import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; @@ -46,6 +49,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowInstance; @@ -58,7 +62,6 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.util.GUID; -import org.alfresco.util.PropertyMap; import org.springframework.util.StringUtils; /** @@ -72,10 +75,10 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest private FormService formService; private NamespaceService namespaceService; private ScriptService scriptService; - private PersonService personService; private ContentService contentService; private WorkflowService workflowService; - + private TestPersonManager personManager; + private NodeRef document; private NodeRef associatedDoc; private NodeRef childDoc; @@ -134,16 +137,19 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest this.formService = (FormService)this.applicationContext.getBean("FormService"); this.namespaceService = (NamespaceService)this.applicationContext.getBean("NamespaceService"); this.scriptService = (ScriptService)this.applicationContext.getBean("ScriptService"); - this.personService = (PersonService)this.applicationContext.getBean("PersonService"); + PersonService personService = (PersonService)this.applicationContext.getBean("PersonService"); this.contentService = (ContentService)this.applicationContext.getBean("ContentService"); this.workflowService = (WorkflowService)this.applicationContext.getBean("WorkflowService"); + MutableAuthenticationService mutableAuthenticationService = (MutableAuthenticationService)applicationContext.getBean("AuthenticationService"); + this.personManager = new TestPersonManager(mutableAuthenticationService, personService, nodeService); + // create users - createUser(USER_ONE); - createUser(USER_TWO); + personManager.createPerson(USER_ONE); + personManager.createPerson(USER_TWO); // Do the tests as userOne - authenticationComponent.setCurrentUser(USER_ONE); + personManager.setUser(USER_ONE); String guid = GUID.generate(); @@ -224,23 +230,6 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest this.nodeService.createAssociation(this.document, this.associatedDoc, ContentModel.ASSOC_REFERENCES); } - private void createUser(String userName) - { - if (this.authenticationService.authenticationExists(userName) == false) - { - this.authenticationService.createAuthentication(userName, "PWD".toCharArray()); - - PropertyMap ppOne = new PropertyMap(4); - ppOne.put(ContentModel.PROP_USERNAME, userName); - ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); - ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); - ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); - ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); - - this.personService.createPerson(ppOne); - } - } - @SuppressWarnings("unchecked") public void testGetAllDocForm() throws Exception { @@ -775,7 +764,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest String updatedTitle = (String)updatedProps.get(ContentModel.PROP_TITLE); String updatedAuthor = (String)updatedProps.get(ContentModel.PROP_AUTHOR); String updatedOriginator = (String)updatedProps.get(ContentModel.PROP_ORIGINATOR); - List updatedAddressees = (List)updatedProps.get(ContentModel.PROP_ADDRESSEES); + List updatedAddressees = (List)updatedProps.get(ContentModel.PROP_ADDRESSEES); String wrong = (String)updatedProps.get(QName.createQName("cm", "wrong", this.namespaceService)); Date sentDate = (Date)updatedProps.get(ContentModel.PROP_SENTDATE); @@ -1085,7 +1074,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertEquals(expectedContent, content); } - @SuppressWarnings({ "deprecation", "null", "unchecked" }) + @SuppressWarnings({ "deprecation", "null" }) public void disabledTestFDKModel() throws Exception { // NOTE: The FDK is not loaded by default, for this test to work you must @@ -1216,7 +1205,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertEquals(duplicateValue, values.getFieldData(duplicatePropField.getDataKeyName()).getValue()); FieldData fieldData = values.getFieldData(duplicateAssocField.getDataKeyName()); assertNotNull(fieldData); - List assocs = (List)fieldData.getValue(); + List assocs = (List)fieldData.getValue(); assertNotNull(assocs); assertEquals(0, assocs.size()); @@ -1264,10 +1253,20 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertEquals(1, assocs.size()); } - public void testGetFormForTask() throws Exception + public void testGetFormForJbpmTask() throws Exception { - WorkflowTask task = getWorkflowTask(); - Item item = new Item("task", task.id); + checkGetFormForTask("jbpm$wf:review"); + } + + public void testGetFormForActivitiTask() throws Exception + { + checkGetFormForTask("activiti$activitiReview"); + } + + private void checkGetFormForTask(String defName) + { + WorkflowTask task = getWorkflowTask(defName); + Item item = new Item("task", task.getId()); Form form = formService.getForm(item); assertNotNull(form); @@ -1276,34 +1275,112 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest List fieldDefNames = form.getFieldDefinitionNames(); assertTrue(fieldDefNames.size() > 0); + // Check the correct field names are present. List expFields = getExpectedTaskFields(); assertTrue(fieldDefNames.containsAll(expFields)); + + // Check default value for priority is correct. + List definitions = form.getFieldDefinitions(); + String priorityName = WorkflowModel.PROP_PRIORITY.toPrefixString(namespaceService); + for (FieldDefinition definition : definitions) + { + if(priorityName.equals(definition.getName())) + { + assertEquals("2", definition.getDefaultValue()); + break; + } + } } - public void testSaveTask() throws Exception + public void testSaveJbpmTask() throws Exception { - WorkflowTask task = getWorkflowTask(); + checkSaveTask("jbpm$wf:review"); + } + + public void testSaveActivitiTask() throws Exception + { + checkSaveTask("activiti$activitiReview"); + } + + private void checkSaveTask(String defName) + { + WorkflowTask task = getWorkflowTask(defName); QName descName = WorkflowModel.PROP_DESCRIPTION; - Serializable initialDesc = task.properties.get(descName); + Serializable initialDesc = task.getProperties().get(descName); String testDesc = "Foo-Bar-Test-String"; assertFalse(testDesc.equals(initialDesc)); - Item item = new Item("task", task.id); + Item item = new Item("task", task.getId()); FormData data = new FormData(); String descFieldName = FormFieldConstants.PROP_DATA_PREFIX + descName.toPrefixString(namespaceService).replace(":", "_"); data.addFieldData(descFieldName, testDesc, true); formService.saveForm(item, data); - WorkflowTask newTask = workflowService.getTaskById(task.id); - assertEquals(testDesc, newTask.properties.get(descName)); + WorkflowTask newTask = workflowService.getTaskById(task.getId()); + assertEquals(testDesc, newTask.getProperties().get(descName)); + } + + public void testTransitionJbpmTask() throws Exception + { + checkTransitionTask("jbpm$wf:review", "approve", "approve"); + } + + public void testTransitionActivitiTask() throws Exception + { + checkTransitionTask("activiti$activitiReview", ActivitiConstants.DEFAULT_TRANSITION_NAME, "Approve"); + } + + private void checkTransitionTask(String defName, String transitionId, String expOutcome) + { + WorkflowTask task = getWorkflowTask(defName); + QName descName = WorkflowModel.PROP_DESCRIPTION; + Serializable initialDesc = task.getProperties().get(descName); + String testDesc = "Foo-Bar-Test-String"; + assertFalse(testDesc.equals(initialDesc)); + + Item item = new Item("task", task.getId()); + FormData data = new FormData(); + String descFieldName = FormFieldConstants.PROP_DATA_PREFIX + descName.toPrefixString(namespaceService).replace(":", "_"); + data.addFieldData(descFieldName, testDesc, true); + + String reviewOutcomeFieldName = FormFieldConstants.PROP_DATA_PREFIX + "wf_reviewOutcome"; + data.addFieldData(reviewOutcomeFieldName, "Approve", true); + + String transitionDataKey = FormFieldConstants.PROP_DATA_PREFIX + TransitionFieldProcessor.KEY; + data.addFieldData(transitionDataKey, transitionId); + + formService.saveForm(item, data); + WorkflowTask newTask = workflowService.getTaskById(task.getId()); + assertEquals("The description should have been updated!", testDesc, newTask.getProperties().get(descName)); + + // Check the task is completed + assertEquals("The task should have been completed!", WorkflowTaskState.COMPLETED, newTask.getState()); + + Serializable outcome = newTask.getProperties().get(WorkflowModel.PROP_OUTCOME); + assertEquals("The transition is wrong!", expOutcome, outcome); } - private WorkflowTask getWorkflowTask() + private WorkflowTask getWorkflowTask(String definitionName) { - WorkflowDefinition reviewDef = workflowService.getDefinitionByName("jbpm$wf:review"); - WorkflowPath path = workflowService.startWorkflow(reviewDef.id, null); - List tasks = workflowService.getTasksForWorkflowPath(path.id); + WorkflowDefinition reviewDef = workflowService.getDefinitionByName(definitionName); + Map properties = new HashMap(); + properties.put(WorkflowModel.ASSOC_ASSIGNEE, personManager.get(USER_ONE)); + properties.put(WorkflowModel.ASSOC_PACKAGE, folder); + WorkflowPath path = workflowService.startWorkflow(reviewDef.getId(), properties); + WorkflowTask task = getTaskForPath(path); + String startTaskId = reviewDef.getStartTaskDefinition().getId(); + if (startTaskId.equals(task.getDefinition().getId())) + { + workflowService.endTask(task.getId(), null); + task = getTaskForPath(path); + } + return task; + } + + private WorkflowTask getTaskForPath(WorkflowPath path) + { + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks); assertTrue(tasks.size() > 0); WorkflowTask task = tasks.get(0); @@ -1321,18 +1398,32 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest return fields; } - public void testWorkflowForms() throws Exception + public void testJbpmWorkflowForm() throws Exception + { + checkWorkflowForms("jbpm_wf_adhoc", "|Task Done"); + } + + public void testActivitiWorkflowForm() throws Exception + { + checkWorkflowForms("activiti_activitiAdhoc", "Next|Next"); + } + + private void checkWorkflowForms(String workflowDefName, String transitionLabels) throws Exception { // generate a form for a well known workflow-definition supplying // a legitimate set of fields for the workflow List fields = new ArrayList(8); - fields.add("bpm:taskId"); - fields.add("bpm:workflowDescription"); - fields.add("bpm:workflowDueDate"); - fields.add("packageItems"); + String taskIdName = WorkflowModel.PROP_TASK_ID.toPrefixString(namespaceService); + String workflowDescName = WorkflowModel.PROP_WORKFLOW_DESCRIPTION.toPrefixString(namespaceService); + String workflowDueDateName = WorkflowModel.PROP_WORKFLOW_DUE_DATE.toPrefixString(namespaceService); + String packageItemsName = "packageItems"; + + fields.add(taskIdName); + fields.add(workflowDescName); + fields.add(workflowDueDateName); + fields.add(packageItemsName); // Use URL-friendly format. - String workflowDefName = "jbpm_wf_adhoc"; Form form = this.formService.getForm(new Item(WORKFLOW_FORM_ITEM_KIND, workflowDefName), fields); // check a form got returned @@ -1355,10 +1446,10 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest } // find the fields - PropertyFieldDefinition idField = (PropertyFieldDefinition)fieldDefMap.get("bpm:taskId"); - PropertyFieldDefinition descriptionField = (PropertyFieldDefinition)fieldDefMap.get("bpm:workflowDescription"); - PropertyFieldDefinition dueDateField = (PropertyFieldDefinition)fieldDefMap.get("bpm:workflowDueDate"); - AssociationFieldDefinition packageItemsField = (AssociationFieldDefinition)fieldDefMap.get("packageItems"); + PropertyFieldDefinition idField = (PropertyFieldDefinition)fieldDefMap.get(taskIdName); + PropertyFieldDefinition descriptionField = (PropertyFieldDefinition)fieldDefMap.get(workflowDescName); + PropertyFieldDefinition dueDateField = (PropertyFieldDefinition)fieldDefMap.get(workflowDueDateName); + AssociationFieldDefinition packageItemsField = (AssociationFieldDefinition)fieldDefMap.get(packageItemsName); // check fields are present assertNotNull("Expecting to find the bpm:taskId field", idField); @@ -1367,45 +1458,48 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertNotNull("Expecting to find the packageItems field", packageItemsField); // get the number of tasks now - List tasks = this.workflowService.getAssignedTasks(USER_ONE, + List tasks = workflowService.getAssignedTasks(USER_ONE, WorkflowTaskState.IN_PROGRESS); int tasksBefore = tasks.size(); // persist the form FormData data = new FormData(); data.addFieldData("prop_bpm_workflowDescription", "This is a new adhoc task"); - data.addFieldData("assoc_bpm_assignee_added", - this.personService.getPerson(USER_ONE).toString()); - data.addFieldData("assoc_packageItems_added", this.document.toString()); + data.addFieldData("assoc_bpm_assignee_added", personManager.get(USER_ONE).toString()); + data.addFieldData("assoc_packageItems_added", document.toString()); + +// data.addFieldData("prop_bpm_workflowDueDate", new Date()); +// data.addFieldData("prop_bpm_workflowPriority", 1); // persist the data - WorkflowInstance workflow = (WorkflowInstance)this.formService.saveForm( + WorkflowInstance workflow = (WorkflowInstance)formService.saveForm( new Item(WORKFLOW_FORM_ITEM_KIND, workflowDefName), data); // verify that the workflow was started by checking the user has one // more task and the details on the workflow instance - tasks = this.workflowService.getAssignedTasks(USER_ONE, - WorkflowTaskState.IN_PROGRESS); + tasks = workflowService.getAssignedTasks(USER_ONE, WorkflowTaskState.IN_PROGRESS); int tasksAfter = tasks.size(); assertTrue("Expecting there to be more tasks", tasksAfter > tasksBefore); // check workflow instance details - assertEquals("jbpm$wf:adhoc", workflow.definition.name); + String actualWfName = workflow.getDefinition().getName(); + assertEquals(workflowDefName, actualWfName.replace('$', '_').replace(':', '_')); // get the task form and verify data String taskId = tasks.get(0).getId(); fields.clear(); - fields.add("bpm:taskId"); + fields.add(taskIdName); fields.add("transitions"); fields.add("message"); fields.add("taskOwner"); - fields.add("packageItems"); - form = this.formService.getForm(new Item(TASK_FORM_ITEM_KIND, taskId), fields); + fields.add(packageItemsName); + form = formService.getForm(new Item(TASK_FORM_ITEM_KIND, taskId), fields); FormData taskData = form.getFormData(); - assertEquals(taskId, "jbpm$" + taskData.getFieldData("prop_bpm_taskId").getValue().toString()); - assertEquals("|Task Done", taskData.getFieldData("prop_transitions").getValue()); - assertEquals("UserOne_FormServiceImplTest|firstName|lastName", taskData.getFieldData("prop_taskOwner").getValue()); + assertEquals(taskId.substring(taskId.indexOf('$')+1), taskData.getFieldData("prop_bpm_taskId").getValue().toString()); + assertEquals(transitionLabels, taskData.getFieldData("prop_transitions").getValue()); + String expOwner = USER_ONE + "|" + personManager.getFirstName(USER_ONE) + "|" + personManager.getLastName(USER_ONE); + assertEquals(expOwner, taskData.getFieldData("prop_taskOwner").getValue()); assertEquals("This is a new adhoc task", taskData.getFieldData("prop_message").getValue()); assertNotNull(taskData.getFieldData("assoc_packageItems").getValue()); @@ -1413,7 +1507,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest String comment = "This is a comment"; data = new FormData(); data.addFieldData("prop_bpm_comment", comment); - this.formService.saveForm(new Item(TASK_FORM_ITEM_KIND, taskId), data); + formService.saveForm(new Item(TASK_FORM_ITEM_KIND, taskId), data); // check the comment was updated WorkflowTask task = workflowService.getTaskById(taskId); @@ -1421,7 +1515,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertEquals(comment, taskComment); // make sure unauthorized user can not update the task - authenticationComponent.setCurrentUser(USER_TWO); + personManager.setUser(USER_TWO); try { @@ -1483,7 +1577,6 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest } } - @SuppressWarnings("unchecked") public void testFormData() throws Exception { FormData formData = new FormData(); @@ -1503,14 +1596,14 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertTrue("Expecting 'multipleValues' to be a List object", (value instanceof List)); formData.addFieldData("multipleValues", "three"); - List list = (List)formData.getFieldData("multipleValues").getValue(); + List list = (List)formData.getFieldData("multipleValues").getValue(); assertEquals("Expecting 'multipleValues' List to have 3 items", 3, list.size()); // add a List initially then add a value to it - formData.addFieldData("listValue", new ArrayList()); + formData.addFieldData("listValue", new ArrayList()); formData.addFieldData("listValue", "one"); formData.addFieldData("listValue", "two"); - list = (List)formData.getFieldData("listValue").getValue(); + list = (List)formData.getFieldData("listValue").getValue(); assertEquals("Expecting 'listValue' List to have 2 items", 2, list.size()); // test overwrite parameter diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java index b974473565..606d12f965 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java @@ -67,8 +67,8 @@ public class TaskFormPersister extends ContentModelFormPersister this.updater = new TaskUpdater(item.id, workflowService, nodeService); } - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.ContentModelFormPersister#addAssociation(org.alfresco.service.namespace.QName, java.util.List) + /** + * {@inheritDoc} */ @Override protected boolean addAssociation(QName qName, List values) @@ -77,8 +77,8 @@ public class TaskFormPersister extends ContentModelFormPersister return true; } - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.ContentModelFormPersister#removeAssociation(org.alfresco.service.namespace.QName, java.util.List) + /** + * {@inheritDoc} */ @Override protected boolean removeAssociation(QName qName, List values) @@ -87,8 +87,8 @@ public class TaskFormPersister extends ContentModelFormPersister return true; } - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.ContentModelFormPersister#updateProperty(org.alfresco.service.namespace.QName, java.io.Serializable) + /** + * {@inheritDoc} */ @Override protected boolean updateProperty(QName qName, Serializable value) @@ -97,8 +97,8 @@ public class TaskFormPersister extends ContentModelFormPersister return true; } - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.ContentModelFormPersister#addTransientAssociation(java.lang.String, java.util.List) + /** + * {@inheritDoc} */ @Override protected boolean addTransientAssociation(String fieldName, List values) @@ -112,8 +112,8 @@ public class TaskFormPersister extends ContentModelFormPersister return false; } - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.ContentModelFormPersister#removeTransientAssociation(java.lang.String, java.util.List) + /** + * {@inheritDoc} */ @Override protected boolean removeTransientAssociation(String fieldName, List values) @@ -127,8 +127,8 @@ public class TaskFormPersister extends ContentModelFormPersister return false; } - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.ContentModelFormPersister#addTransientProperty(java.lang.String, org.alfresco.repo.forms.FormData.FieldData) + /** + * {@inheritDoc} */ @Override protected boolean updateTransientProperty(String fieldName, FieldData fieldData) @@ -148,8 +148,8 @@ public class TaskFormPersister extends ContentModelFormPersister return false; } - /* (non-Javadoc) - * @see org.alfresco.repo.forms.processor.workflow.ContentModelFormPersister#persist() + /** + * {@inheritDoc} */ @Override public WorkflowTask persist() diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java index 8d148ca039..4fdeba8070 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java @@ -19,28 +19,10 @@ package org.alfresco.repo.forms.processor.workflow; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_ADDED_SUFFIX; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_PREFIX; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.ASSOC_DATA_REMOVED_SUFFIX; -import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX; -import static org.alfresco.repo.workflow.WorkflowModel.ASPECT_WORKFLOW_PACKAGE; -import static org.alfresco.repo.workflow.WorkflowModel.ASSOC_ASSIGNEE; -import static org.alfresco.repo.workflow.WorkflowModel.ASSOC_PACKAGE_CONTAINS; -import static org.alfresco.repo.workflow.WorkflowModel.ASSOC_POOLED_ACTORS; -import static org.alfresco.repo.workflow.WorkflowModel.PROP_DESCRIPTION; -import static org.alfresco.repo.workflow.WorkflowModel.PROP_HIDDEN_TRANSITIONS; -import static org.alfresco.repo.workflow.WorkflowModel.PROP_PACKAGE_ACTION_GROUP; -import static org.alfresco.repo.workflow.WorkflowModel.PROP_PACKAGE_ITEM_ACTION_GROUP; -import static org.alfresco.repo.workflow.WorkflowModel.PROP_STATUS; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyMap; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.alfresco.repo.forms.processor.node.FormFieldConstants.*; +import static org.alfresco.repo.workflow.WorkflowModel.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; import java.io.Serializable; import java.util.ArrayList; @@ -149,13 +131,13 @@ public class TaskFormProcessorTest extends TestCase Item item = new Item("task", TASK_ID); WorkflowTask result = processor.getTypedItem(item); assertNotNull(result); - assertEquals(TASK_ID, result.id); + assertEquals(TASK_ID, result.getId()); // Check URI-encoded id. item = new Item("task", TASK_ID.replace('$', '_')); result = processor.getTypedItem(item); assertNotNull(result); - assertEquals(TASK_ID, result.id); + assertEquals(TASK_ID, result.getId()); } public void testGenerateSetsItemAndUrl() throws Exception @@ -251,13 +233,11 @@ public class TaskFormProcessorTest extends TestCase WorkflowTransition transition1 = makeTransition("id1", "title1"); WorkflowTransition transition2 = makeTransition("id2", "title2"); WorkflowTransition transition3 = makeTransition("id3", "title3"); - WorkflowTransition[] transitions = new WorkflowTransition[] {transition1, transition2, transition3}; - task.definition.node = new WorkflowNode(); - task.definition.node.transitions = transitions; + task = makeTask(transition1, transition2, transition3); // Hide transition with id3. Serializable hiddenValue = (Serializable) Collections.singletonList("id3"); - task.properties.put(PROP_HIDDEN_TRANSITIONS, hiddenValue ); + task.getProperties().put(PROP_HIDDEN_TRANSITIONS, hiddenValue ); form = processForm(fieldName); transitionValues = "id1|title1,id2|title2"; @@ -273,14 +253,14 @@ public class TaskFormProcessorTest extends TestCase // add a description to the task and check it comes back message = "This is some text the user may have entered"; - this.task.properties.put(PROP_DESCRIPTION, message); + this.task.getProperties().put(PROP_DESCRIPTION, message); form = processForm(fieldName); checkSingleProperty(form, fieldName, message); // set the description to the same as the task title // and make sure the message comes back as null - this.task.properties.put(PROP_DESCRIPTION, this.task.title); + this.task.getProperties().put(PROP_DESCRIPTION, this.task.getTitle()); form = processForm(fieldName); checkSingleProperty(form, fieldName, null); } @@ -322,10 +302,8 @@ public class TaskFormProcessorTest extends TestCase private WorkflowTransition makeTransition(String id, String title) { - WorkflowTransition transition = new WorkflowTransition(); - transition.id = id; - transition.title = title; - return transition; + return new WorkflowTransition( + id, title, null, false); } public void testPersistPropertyChanged() throws Exception @@ -610,22 +588,25 @@ public class TaskFormProcessorTest extends TestCase return defaultProcessor; } - private WorkflowTask makeTask() + private WorkflowTask makeTask(WorkflowTransition... transitions) { - WorkflowTask result = new WorkflowTask(); - result.id = TASK_ID; - result.state = WorkflowTaskState.IN_PROGRESS; - result.title = "Test"; - result.definition = makeTaskDefinition(); - result.properties = makeTaskProperties(); + String id = TASK_ID; + String title = "Test"; + WorkflowTaskState state = WorkflowTaskState.IN_PROGRESS; + WorkflowTaskDefinition taskDef = makeTaskDefinition(transitions); + Map properties = makeTaskProperties(); - result.path = new WorkflowPath(); - result.path.node = new WorkflowNode(); - result.path.node.transitions = new WorkflowTransition[0]; - result.path.instance = new WorkflowInstance(); - result.path.instance.definition = new WorkflowDefinition("42", "Test", "1.0", "Test", "Test", null); - result.path.instance.workflowPackage = PCKG_NODE; - return result; + WorkflowDefinition definition = new WorkflowDefinition("42", "Test", "1.0", "Test", "Test", null); + NodeRef wfPackage = PCKG_NODE; + WorkflowInstance instance = new WorkflowInstance(null, + definition, null, + null, wfPackage, + null, true, null, null); + WorkflowNode node = new WorkflowNode("", "", "", "", true, new WorkflowTransition[0]); + WorkflowPath path = new WorkflowPath(null, instance, node, true); + return new WorkflowTask(id, + taskDef, null, title, null, state, path, properties); + } private HashMap makeTaskProperties() @@ -636,13 +617,13 @@ public class TaskFormProcessorTest extends TestCase return properties; } - private WorkflowTaskDefinition makeTaskDefinition() + private WorkflowTaskDefinition makeTaskDefinition(WorkflowTransition... transitions) { - WorkflowTaskDefinition definition = new WorkflowTaskDefinition(); - definition.id = "DefinitionId"; - definition.metadata = makeTypeDef(); - definition.node = mock(WorkflowNode.class); - return definition; + String id = "DefinitionId"; + TypeDefinition metadata = makeTypeDef(); + WorkflowNode node = new WorkflowNode("", "", "", "", true, transitions); + return new WorkflowTaskDefinition(id, + node, metadata); } private TypeDefinition makeTypeDef() @@ -727,7 +708,7 @@ public class TaskFormProcessorTest extends TestCase private DictionaryService makeDictionaryService() { DictionaryService mock = mock(DictionaryService.class); - when(mock.getAnonymousType((QName) any(), (Collection) any())).thenReturn(task.definition.metadata); + when(mock.getAnonymousType((QName) any(), (Collection) any())).thenReturn(task.getDefinition().getMetadata()); return mock; } @@ -761,8 +742,7 @@ public class TaskFormProcessorTest extends TestCase } }); - this.newTask = new WorkflowTask(); - newTask.id = TASK_ID; + this.newTask = new WorkflowTask(TASK_ID, null, null, null, null, null, null, null); when(service.updateTask(anyString(), anyMap(), anyMap(), anyMap())) .thenAnswer(new Answer() diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java index 733041dbd5..6720a7591e 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java @@ -482,11 +482,11 @@ public class WorkflowFormProcessorTest extends TestCase private WorkflowTaskDefinition makeTaskDefinition() { - WorkflowTaskDefinition taskDef = new WorkflowTaskDefinition(); - taskDef.id = "foo$startTaskDefId"; - taskDef.metadata = makeTypeDef(); - taskDef.node = new WorkflowNode(); - return taskDef; + String id = "foo$startTaskDefId"; + TypeDefinition metadata = makeTypeDef(); + WorkflowNode node = new WorkflowNode("", "", "", "", false); + return new WorkflowTaskDefinition(id, + node, metadata); } private TypeDefinition makeTypeDef() @@ -587,13 +587,13 @@ public class WorkflowFormProcessorTest extends TestCase WorkflowService service = mock(WorkflowService.class); when(service.getDefinitionByName(WF_DEF_NAME)).thenReturn(definition); - newInstance = new WorkflowInstance(); - newInstance.id = "foo$instanceId"; - WorkflowTask startTask = new WorkflowTask(); - startTask.id = "foo$taskId"; - final WorkflowPath path = new WorkflowPath(); - path.id = "foo$pathId"; - path.instance = newInstance; + String instanceId = "foo$instanceId"; + newInstance = new WorkflowInstance(instanceId, + definition, null, null, null, + null, true, null, null); + WorkflowTask startTask = new WorkflowTask("foo$taskId", null, null, null, null, null, null, null); + String pathId = "foo$pathId"; + final WorkflowPath path = new WorkflowPath(pathId, newInstance, null, true); when(service.startWorkflow(eq(definition.getId()), anyMap())) .thenAnswer(new Answer() diff --git a/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java b/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java index 6a1cda2efb..06eb91052a 100644 --- a/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java @@ -19,7 +19,6 @@ package org.alfresco.repo.rating; -import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -29,13 +28,11 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.rating.Rating; import org.alfresco.service.cmr.rating.RatingScheme; import org.alfresco.service.cmr.rating.RatingService; import org.alfresco.service.cmr.rating.RatingServiceException; import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; @@ -54,17 +51,17 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest { private static final String USER_ONE = "UserOne"; private static final String USER_TWO = "UserTwo"; - private CopyService copyService; +// private CopyService copyService; private PersonService personService; private RatingService ratingService; private Repository repositoryHelper; private ScriptService scriptService; - private RetryingTransactionHelper transactionHelper; +// private RetryingTransactionHelper transactionHelper; private NodeRef companyHome; // These NodeRefs are used by the test methods. private NodeRef testFolder; - private NodeRef testFolderCopyDest; +// private NodeRef testFolderCopyDest; private NodeRef testDoc_Admin; private NodeRef testDoc_UserOne; private NodeRef testDoc_UserTwo; @@ -73,15 +70,16 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest private static final String LIKES_SCHEME_NAME = "likesRatingScheme"; private static final String FIVE_STAR_SCHEME_NAME = "fiveStarRatingScheme"; + @SuppressWarnings("deprecation") @Override protected void onSetUpInTransaction() throws Exception { super.onSetUpInTransaction(); - this.copyService = (CopyService)this.applicationContext.getBean("CopyService"); +// this.copyService = (CopyService)this.applicationContext.getBean("CopyService"); this.personService = (PersonService)this.applicationContext.getBean("PersonService"); this.ratingService = (RatingService) this.applicationContext.getBean("ratingService"); this.repositoryHelper = (Repository) this.applicationContext.getBean("repositoryHelper"); - this.transactionHelper = (RetryingTransactionHelper) this.applicationContext.getBean("retryingTransactionHelper"); +// this.transactionHelper = (RetryingTransactionHelper) this.applicationContext.getBean("retryingTransactionHelper"); this.scriptService = (ScriptService) this.applicationContext.getBean("scriptService"); // Set the current security context as admin @@ -90,7 +88,7 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest companyHome = this.repositoryHelper.getCompanyHome(); testFolder = createNode(companyHome, "testFolder", ContentModel.TYPE_FOLDER); - testFolderCopyDest = createNode(companyHome, "testFolderCopyDest", ContentModel.TYPE_FOLDER); +// testFolderCopyDest = createNode(companyHome, "testFolderCopyDest", ContentModel.TYPE_FOLDER); testDoc_Admin = createNode(testFolder, "testDocInFolder", ContentModel.TYPE_CONTENT); createUser(USER_ONE); @@ -113,20 +111,6 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest deleteUser(USER_ONE); } - private NodeRef createNode(NodeRef parentNode, String name, QName type) - { - Map props = new HashMap(); - String fullName = name + System.currentTimeMillis(); - props.put(ContentModel.PROP_NAME, fullName); - QName docContentQName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, fullName); - NodeRef node = nodeService.createNode(parentNode, - ContentModel.ASSOC_CONTAINS, - docContentQName, - type, - props).getChildRef(); - return node; - } - /** * This method tests that the expected 'out of the box' rating schemes are available * and correctly initialised. diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java index 46784031a1..bce353b7b7 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -168,21 +168,6 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest return createNode(companyHome, name, ContentModel.TYPE_CONTENT); } - private NodeRef createNode(NodeRef companyHome, String name, QName type) - { - Map props = new HashMap(); - String fullName = name + System.currentTimeMillis(); - props.put(ContentModel.PROP_NAME, fullName); - QName docContentQName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, fullName); - NodeRef node = nodeService.createNode(companyHome, - ContentModel.ASSOC_CONTAINS, - docContentQName, - type, - props) - .getChildRef(); - return node; - } - private NodeRef createFreeMarkerNode(NodeRef companyHome) { NodeRef fmNode = createContentNode(companyHome, "testFreeMarkerNode"); @@ -1733,7 +1718,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest */ public void testPathBasedRenditionOverwrite() throws Exception { - + //TODO Implement Test } /** @@ -1759,8 +1744,6 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest QName.createQName("CompositePart2"), DummyHelloWorldRenditionEngine.ENGINE_NAME )); - NodeRef renditionNode; - // ============================================== // // Anonymous Rendition, no existing one there // // ============================================== // @@ -1852,7 +1835,6 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest "/" + (String) nodeService.getProperty(testTargetFolder, ContentModel.PROP_NAME) + "/" + "HelloWorld.txt"; - ; rdPlain.setParameterValue( RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, path diff --git a/source/java/org/alfresco/repo/security/person/TestGroupManager.java b/source/java/org/alfresco/repo/security/person/TestGroupManager.java new file mode 100644 index 0000000000..ff66642cf4 --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/TestGroupManager.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.security.person; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class TestGroupManager +{ + private final AuthorityService authorityService; + private final SearchService searchService; + + private final Map groups = new HashMap(); + + public TestGroupManager(AuthorityService authorityService, SearchService searchService) + { + this.authorityService = authorityService; + this.searchService = searchService; + } + + /** + * Creates a group with the given name if one does not already exist. + * @param groupShortName + * @return The group's full name. + */ + public String createGroupIfNotExist(String groupShortName) + { + String fullName = authorityService.getName(AuthorityType.GROUP, groupShortName); + if(groups.containsKey(groupShortName) == false) + { + if (authorityService.authorityExists(fullName) == false) + { + fullName = authorityService.createAuthority(AuthorityType.GROUP, groupShortName, groupShortName, null); + groups.put(groupShortName, findGroupNode(groupShortName)); + } + } + return fullName; + } + + /** + * Adds the child group as a sub-authority of the parent group. Creates the + * child group and parent group if they do not exist. + * + * @param parentGroupShortName + * @param childGroupShortName + * @return The full name of the child group. + */ + public String addGroupToParent(String parentGroupShortName, String childGroupShortName) + { + String parentFullName = createGroupIfNotExist(parentGroupShortName); + String groupFullName = createGroupIfNotExist(childGroupShortName); + authorityService.addAuthority(parentFullName, groupFullName); + return groupFullName; + } + + /** + * Adds the user as a sub-authroity of the specified group. + * Creates the group if it doesn't exist. + * + * @param groupShortName + * @param userName + */ + public void addUserToGroup(String groupShortName, String userName) + { + String fullGroupName = createGroupIfNotExist(groupShortName); + authorityService.addAuthority(fullGroupName, userName); + } + + public void deleteGroup(String groupShortName) + { + String groupFullName = authorityService.getName(AuthorityType.GROUP, groupShortName); + if (authorityService.authorityExists(groupFullName)) + { + authorityService.deleteAuthority(groupFullName); + } + } + + public void clearGroups() + { + for (String group : groups.keySet()) + { + String fullName = authorityService.getName(AuthorityType.GROUP, group); + if(authorityService.authorityExists(fullName)) + { + authorityService.deleteAuthority(fullName); + } + } + groups.clear(); + } + + public NodeRef get(String groupShortName) + { + NodeRef result = groups.get(groupShortName); + if(result == null) + { + result = findGroupNode(groupShortName); + } + return result; + } + + private NodeRef findGroupNode(String groupShortName) + { + //TODO Use new AuthorityService.getNode() method on HEAD + NodeRef group = null; + + String query = "+TYPE:\"cm:authorityContainer\" AND @cm\\:authorityName:*" + groupShortName; + + ResultSet results = null; + try + { + results = searchService.query( + new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"), + SearchService.LANGUAGE_LUCENE, query); + + if (results.length() > 0) + { + group = results.getNodeRefs().get(0); + } + } + finally + { + if (results != null) + { + results.close(); + } + } + return group; + } +} diff --git a/source/java/org/alfresco/repo/security/person/TestPersonManager.java b/source/java/org/alfresco/repo/security/person/TestPersonManager.java index 068b1f6533..c9699a3a86 100644 --- a/source/java/org/alfresco/repo/security/person/TestPersonManager.java +++ b/source/java/org/alfresco/repo/security/person/TestPersonManager.java @@ -135,5 +135,4 @@ public class TestPersonManager NodeRef person = get(userName); return (String) nodeService.getProperty(person, ContentModel.PROP_LASTNAME); } - -} +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/AbstractWorkflowNodeConverter.java b/source/java/org/alfresco/repo/workflow/AbstractWorkflowNodeConverter.java new file mode 100644 index 0000000000..1bacb03a5b --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/AbstractWorkflowNodeConverter.java @@ -0,0 +1,95 @@ +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; + +public abstract class AbstractWorkflowNodeConverter implements WorkflowNodeConverter +{ + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object convertNodes(Object value, boolean isMany) + { + if(value instanceof NodeRef) + { + return convertNode((NodeRef) value, isMany); + } + else if(value instanceof Collection) + { + return convertNodes((Collection) value, isMany); + } + return value; //If null or not a supported type. + } + + /** + * {@inheritDoc} + */ + public Object convertNode(NodeRef value, boolean isMany) + { + if (isMany) + { + return convertNodes(Collections.singleton(value)); + } + return convertNode(value); + } + + /** + * {@inheritDoc} + */ + public Object convertNodes(Collection values, boolean isMany) + { + if (isMany) + { + return convertNodes(values); + } + if (values.isEmpty()) + { + return null; + } + return convertNode(values.iterator().next()); + } + + /** + * {@inheritDoc} + */ + public List convertToNodes(Object value) + { + if (value instanceof Collection) + { + return convertToNodes((Collection) value); + } + return Collections.singletonList(convertToNode(value)); + } + + public List convertToNodes(Collection toConvert) + { + List results = new ArrayList(toConvert.size()); + for (Object obj : toConvert) + { + results.add(convertToNode(obj)); + } + return results; + } + + public Serializable convert(Object object) + { + if(object instanceof Collection) + { + return (Serializable) convertToNodes((Collection)object); + } + return convertToNode(object); + } + + public abstract Object convertNode(NodeRef node); + + public abstract List convertNodes(Collection values); + + public abstract NodeRef convertToNode(Object toConvert); + +} diff --git a/source/java/org/alfresco/repo/workflow/AbstractWorkflowPropertyHandler.java b/source/java/org/alfresco/repo/workflow/AbstractWorkflowPropertyHandler.java new file mode 100644 index 0000000000..65b4cb756a --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/AbstractWorkflowPropertyHandler.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.Collection; + +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.repository.datatype.TypeConverter; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public abstract class AbstractWorkflowPropertyHandler implements WorkflowPropertyHandler +{ + private static final String ERR_SET_TASK_PROPS_INVALID_VALUE = "activiti.engine.set.task.properties.invalid.value"; + private static final TypeConverter typeConverter = DefaultTypeConverter.INSTANCE; + + protected WorkflowNodeConverter nodeConverter; + protected MessageService messageService; + + /** + * @param task + * @param value + * @param assocDef + */ + protected Object handleAssociation(Serializable value, AssociationDefinition assocDef) + { + if(assocDef.isProtected()) + { + return DO_NOT_ADD; + } + // Convert association to ActivitiScriptNode / List + return convertAssociationValue(assocDef, value); + } + + /** + * @param task + * @param value + * @param propDef + * @return + */ + protected Object handleProperty(Serializable value, PropertyDefinition propDef) + { + if (propDef.isProtected()) + { + // Protected properties are ignored + return DO_NOT_ADD; + } + // The value is converted to the correct type + return convertPropertyValue(propDef, value); + } + + protected Object convertPropertyValue(PropertyDefinition propDef, Serializable value) + { + Object newValue = value; + // Convert property value using a default type converter + if (value instanceof Collection) + { + // Convert a collecion of values + newValue =typeConverter.convert(propDef.getDataType(), (Collection) value); + } + else + { + // Convert a single value + newValue = typeConverter.convert(propDef.getDataType(), value); + } + + // Convert NodeRefs to ActivitiScriptNodes + DataTypeDefinition dataTypeDef = propDef.getDataType(); + if (dataTypeDef.getName().equals(DataTypeDefinition.NODE_REF)) + { + newValue = nodeConverter.convertNodes(newValue, propDef.isMultiValued()); + } + return newValue; + } + + protected Object convertAssociationValue(AssociationDefinition assocDef, Serializable value) + { + return nodeConverter.convertNodes(value, assocDef.isTargetMany()); + } + + protected WorkflowException getInvalidPropertyValueException(QName key, Object value) + { + String msg = messageService.getMessage(ERR_SET_TASK_PROPS_INVALID_VALUE, value, key); + return new WorkflowException(msg); + } + + /** + * Register this WorkflowPropertyHandler with the provided registry. + * @param registry + */ + public void setRegistry(WorkflowPropertyHandlerRegistry registry) + { + QName key = getKey(); + if(key!=null) + { + registry.registerHandler(key, this); + } + } + + /** + * @param nodeConverter the nodeConverter to set + */ + public void setNodeConverter(WorkflowNodeConverter nodeConverter) + { + this.nodeConverter = nodeConverter; + } + + /** + * @param messageService the messageService to set + */ + public void setMessageService(MessageService messageService) + { + this.messageService = messageService; + } + + protected abstract QName getKey(); + + protected void checkType(QName key, Object value, Class type) + { + if (value != null && !(type.isAssignableFrom(value.getClass()))) + { + throw getInvalidPropertyValueException(key, value); + } + } + + protected Object handleDefaultProperty(Object task, TypeDefinition type, QName key, Serializable value) + { + PropertyDefinition propDef = type.getProperties().get(key); + if(propDef!=null) + { + return handleProperty(value, propDef); + } + else + { + AssociationDefinition assocDef = type.getAssociations().get(key); + if(assocDef!=null) + { + return handleAssociation(value, assocDef); + } + else if (value instanceof NodeRef) + { + return nodeConverter.convertNode((NodeRef)value, false); + } + } + return value; + } +} diff --git a/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java new file mode 100644 index 0000000000..e1f119158d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java @@ -0,0 +1,978 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.person.TestGroupManager; +import org.alfresco.repo.security.person.TestPersonManager; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; + +/** + * @since 4.0 + * @author Nick Smith + * @author Frederik Heremans + * + */ +public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringTest +{ + private static final String XML = MimetypeMap.MIMETYPE_XML; + protected final static String USER1 = "WFUser1" + GUID.generate(); + protected final static String USER2 = "WFUser2" + GUID.generate(); + protected final static String USER3 = "WFUser3" + GUID.generate(); + protected final static String GROUP = "WFGroup" + GUID.generate(); + protected final static String SUB_GROUP = "WFSubGroup" + GUID.generate(); + + protected WorkflowService workflowService; + protected AuthenticationComponent authenticationComponent; + protected TestPersonManager personManager; + protected TestGroupManager groupManager; + protected NodeService nodeService; + private NodeRef companyHome; + + public void testDeployWorkflowDefinition() + { + List defs = workflowService.getDefinitions(); + + WorkflowDefinition definition = deployDefinition(getTestDefinitionPath()); + String id = definition.getId(); + + // Check the initial set of definitions doesn't contain the newly deployed definition. + checkDefinitions(definition, false, defs.toArray(new WorkflowDefinition[0])); + + // Check getDefinitions(). + List newDefs = workflowService.getDefinitions(); + checkDefinitions(definition, true, newDefs.toArray(new WorkflowDefinition[0])); + assertEquals(defs.size()+1, newDefs.size()); + + // Check getDefinitionById(). + WorkflowDefinition defById = workflowService.getDefinitionById(id); + checkDefinition(definition, defById); + + // Check getDefinitionByName(). + WorkflowDefinition defByName = workflowService.getDefinitionByName(definition.getName()); + checkDefinition(definition, defByName); + } + + public void testStartWorkflow() + { + WorkflowDefinition definition = deployDefinition(getTestDefinitionPath()); + + WorkflowPath path = workflowService.startWorkflow(definition.getId(), null); + assertNotNull(path); + assertTrue(path.isActive()); + assertNotNull(path.getNode()); + WorkflowInstance instance = path.getInstance(); + assertNotNull(instance); + assertEquals(definition.getId(), instance.getDefinition().getId()); + } + + public void testStartTask() + { + WorkflowDefinition workflowDef = deployDefinition(getAdhocDefinitionPath()); + WorkflowTaskDefinition startTaskDef = workflowDef.getStartTaskDefinition(); + assertNotNull(startTaskDef); + + // Create workflow parameters + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_DUE_DATE, dueDate); // task instance field + params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); // task instance field + + // Start a workflow instance + WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); + assertNotNull(path); + assertTrue(path.isActive()); + + String startTaskDefId = startTaskDef.getId(); + + // Check start task was created properly. + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask startTask = tasks.get(0); + assertEquals(startTaskDefId, startTask.getDefinition().getId()); + + // Check getStartTask() returns correct task. + startTask = workflowService.getStartTask(path.getInstance().getId()); + assertNotNull(startTask); + assertEquals(startTaskDefId, startTask.getDefinition().getId()); + + // End start task to progress workflow. + workflowService.endTask(startTask.getId(), null); + + // Check start task was persisted and contains correct property values. + WorkflowTask task = workflowService.getTaskById(startTask.getId()); + Map props = task.getProperties(); + assertEquals(dueDate, props.get(WorkflowModel.PROP_DUE_DATE)); + assertEquals(1, props.get(WorkflowModel.PROP_PRIORITY)); + assertEquals(assignee, props.get(WorkflowModel.ASSOC_ASSIGNEE)); + } + + public void testGetPathProperties() throws Exception + { + WorkflowDefinition workflowDef = deployDefinition(getAdhocDefinitionPath()); + + // Create workflow parameters + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); // task instance field + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); // task instance field + String description = "Some Description"; + params.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, description); // task instance field + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); // task instance field + + personManager.setUser(USER1); + + // Start a workflow instance + WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); + assertNotNull(path); + assertTrue(path.isActive()); + + String instanceId = path.getInstance().getId(); + WorkflowTask startTask = workflowService.getStartTask(instanceId); + workflowService.endTask(startTask.getId(), null); + + Map properties = workflowService.getPathProperties(path.getId()); + assertNotNull(properties); + + assertEquals(false, properties.get(QName.createQName("{}" + WorkflowConstants.PROP_CANCELLED))); + assertEquals(instanceId, properties.get(QName.createQName("{}" + WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID))); + + NodeRef initiator = (NodeRef) properties.get(QName.createQName("{}" + WorkflowConstants.PROP_INITIATOR)); + assertEquals(personManager.get(USER1), initiator); + + NodeRef expInitiatorHome = (NodeRef) nodeService.getProperty(initiator, ContentModel.PROP_HOMEFOLDER); + NodeRef initiatorHome = (NodeRef) properties.get(QName.createQName("{}" + WorkflowConstants.PROP_INITIATOR_HOME)); + assertEquals(expInitiatorHome, initiatorHome); + + NodeRef actualCompanyHome = (NodeRef) properties.get(QName.createQName("{}" + WorkflowConstants.PROP_COMPANY_HOME)); + assertEquals(companyHome, actualCompanyHome); + + assertEquals(wfPackage, properties.get(WorkflowModel.ASSOC_PACKAGE)); + assertEquals(dueDate, properties.get(WorkflowModel.PROP_WORKFLOW_DUE_DATE)); + assertEquals(1, properties.get(WorkflowModel.PROP_WORKFLOW_PRIORITY)); + assertEquals(assignee, properties.get(WorkflowModel.ASSOC_ASSIGNEE)); + assertEquals(description, properties.get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + } + + public void testAssociateWorkflowPackage() + { + // create workflow package + authenticationComponent.setSystemUserAsCurrentUser(); + NodeRef rootNode = nodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore")); + ChildAssociationRef childAssoc = nodeService.createNode(rootNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "test"), ContentModel.TYPE_CONTENT, null); + NodeRef contentNode = childAssoc.getChildRef(); + + NodeRef pckgNode = workflowService.createPackage(null); + assertNotNull(pckgNode); + assertTrue(nodeService.hasAspect(pckgNode, WorkflowModel.ASPECT_WORKFLOW_PACKAGE)); + + List existingInstances = workflowService.getWorkflowsForContent(contentNode, true); + assertNotNull(existingInstances); + assertEquals(0, existingInstances.size()); + existingInstances = workflowService.getWorkflowsForContent(contentNode, false); + assertNotNull(existingInstances); + assertEquals(0, existingInstances.size()); + + // Add content to the package + nodeService.addChild(pckgNode, contentNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "test123")); + + // start workflow + WorkflowDefinition workflowDef = deployDefinition(getAdhocDefinitionPath()); + Map parameters = new HashMap(); + parameters.put(WorkflowModel.ASSOC_PACKAGE, pckgNode); + parameters.put(WorkflowModel.ASSOC_ASSIGNEE, personManager.get(USER2)); + WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), parameters); + + assertNotNull(path); + assertTrue(path.isActive()); + assertNotNull(path.getNode()); + assertNotNull(path.getInstance()); + assertEquals(workflowDef.getId(), path.getInstance().getDefinition().getId()); + String workflowDefId = (String)nodeService.getProperty(pckgNode, WorkflowModel.PROP_WORKFLOW_DEFINITION_ID); + assertEquals(workflowDefId, workflowDef.getId()); + String workflowDefName = (String)nodeService.getProperty(pckgNode, WorkflowModel.PROP_WORKFLOW_DEFINITION_NAME); + assertEquals(workflowDefName, workflowDef.getName()); + String workflowInstanceId = (String)nodeService.getProperty(pckgNode, WorkflowModel.PROP_WORKFLOW_INSTANCE_ID); + assertEquals(workflowInstanceId, path.getInstance().getId()); + + // get workflows for content + List instances = workflowService.getWorkflowsForContent(contentNode, true); + assertNotNull(instances); + assertEquals(1, instances.size()); + + WorkflowInstance newInstance = instances.get(0); + assertNotNull(newInstance); + assertEquals(path.getInstance().getId(), newInstance.getId()); + + List completedInstances = workflowService.getWorkflowsForContent(contentNode, false); + assertNotNull(completedInstances); + assertEquals(0, completedInstances.size()); + + // TODO End the workfow instance and test the completed workflow is returned + } + + public void testTaskCapabilities() + { + // start Adhoc workflow as USER1 and assign to USER2 + personManager.setUser(USER1); + + // Get the workflow definition. + WorkflowDefinition workflowDef = deployDefinition(getAdhocDefinitionPath()); + assertNotNull(workflowDef); + + // Create workflow parameters + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); + + // Start a workflow instance + WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); + assertNotNull(path); + assertTrue(path.isActive()); + String workflowInstanceId = path.getInstance().getId(); + + // End start task to progress workflow + WorkflowTask startTask = workflowService.getStartTask(workflowInstanceId); + String startTaskId = startTask.getId(); + workflowService.endTask(startTaskId, null); + + // Fetch start task and check capabilities + startTask = workflowService.getTaskById(startTaskId); + assertNotNull(startTask); + assertEquals(startTask.getState(), WorkflowTaskState.COMPLETED); + + // check nothing can be done to the task as its completed + assertFalse(workflowService.isTaskClaimable(startTask, USER1)); + assertFalse(workflowService.isTaskEditable(startTask, USER1)); + assertFalse(workflowService.isTaskReassignable(startTask, USER1)); + assertFalse(workflowService.isTaskReleasable(startTask, USER1)); + + // Fetch the current task in the workflow + List paths = workflowService.getWorkflowPaths(workflowInstanceId); + assertNotNull(paths); + assertEquals(1, paths.size()); + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask currentTask = tasks.get(0); + assertEquals(currentTask.getState(), WorkflowTaskState.IN_PROGRESS); + assertEquals(currentTask.getProperties().get(ContentModel.PROP_OWNER), USER2); + + // check the task is not claimable or releasable by any user as it is not a pooled task + assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); + assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); + assertFalse(workflowService.isTaskClaimable(currentTask, USER3)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER3)); + + // user1 (initiator) and user2 (owner) should be able to edit and reassign task + assertTrue(workflowService.isTaskEditable(currentTask, USER1)); + assertTrue(workflowService.isTaskEditable(currentTask, USER2)); + assertTrue(workflowService.isTaskReassignable(currentTask, USER1)); + assertTrue(workflowService.isTaskReassignable(currentTask, USER2)); + + // user3 should not be able to edit or reassign task + assertFalse(workflowService.isTaskEditable(currentTask, USER3)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); + + // cancel the workflow + workflowService.cancelWorkflow(workflowInstanceId); + } + + public void testPooledTaskCapabilities() + { + // make admin current user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // start pooled review and approve workflow + WorkflowDefinition workflowDef = deployDefinition(getPooledReviewDefinitionPath()); + assertNotNull(workflowDef); + + // Create workflow parameters + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); + NodeRef group = groupManager.get(GROUP); + assertNotNull(group); + params.put(WorkflowModel.ASSOC_GROUP_ASSIGNEE, group); + + // Start a workflow instance + WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); + assertNotNull(path); + assertTrue(path.isActive()); + String workflowInstanceId = path.getInstance().getId(); + + // End start task to progress workflow + WorkflowTask startTask = workflowService.getStartTask(workflowInstanceId); + String startTaskId = startTask.getId(); + workflowService.endTask(startTaskId, null); + + // Fetch the current task in the workflow + List paths = workflowService.getWorkflowPaths(workflowInstanceId); + assertNotNull(paths); + assertEquals(1, paths.size()); + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask currentTask = tasks.get(0); + assertEquals(currentTask.getState(), WorkflowTaskState.IN_PROGRESS); + assertNull(currentTask.getProperties().get(ContentModel.PROP_OWNER)); + + // ensure the task is not reassignable by any user + assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER2)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); + + // ensure the task is not releasable by any user + assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER3)); + + // ensure the task is claimable by the members of the group and sub group + assertTrue(workflowService.isTaskClaimable(currentTask, USER1)); + assertTrue(workflowService.isTaskClaimable(currentTask, USER2)); + + // ensure the task is not claimable by members outside of the group + assertFalse(workflowService.isTaskClaimable(currentTask, USER3)); + + // ensure the task can be edited + assertTrue(workflowService.isTaskEditable(currentTask, USER1)); + assertTrue(workflowService.isTaskEditable(currentTask, USER2)); + assertFalse(workflowService.isTaskEditable(currentTask, USER3)); + + // claim the task for USER1 + Map properties = new HashMap(8); + properties.put(ContentModel.PROP_OWNER, USER1); + workflowService.updateTask(currentTask.getId(), properties, null, null); + currentTask = workflowService.getTaskById(currentTask.getId()); + + // check flags are correct now USER1 is the owner + assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); + assertTrue(workflowService.isTaskReleasable(currentTask, USER1)); + assertTrue(workflowService.isTaskEditable(currentTask, USER1)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); + assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); + assertFalse(workflowService.isTaskEditable(currentTask, USER2)); + + // cancel the workflow + workflowService.cancelWorkflow(workflowInstanceId); + } + + public void testGetWorkflowTaskDefinitions() + { + //TODO Implement + WorkflowDefinition definition = deployDefinition(getAdhocDefinitionPath()); + String workflowDefId = definition.getId(); + List taskDefs = workflowService.getTaskDefinitions(workflowDefId); + assertEquals(3, taskDefs.size()); + } + + public void testGetTimers() + { + // Make admin current user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // Start process that will have a timer + WorkflowDefinition workflowDef = deployDefinition(getTestTimerDefinitionPath()); + assertNotNull(workflowDef); + + // Create params + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); + + // Assign to USER2 + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); + + // Start a workflow instance + WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); + assertNotNull(path); + assertTrue(path.isActive()); + String workflowInstanceId = path.getInstance().getId(); + + // End start task to progress workflow + WorkflowTask startTask = workflowService.getStartTask(workflowInstanceId); + String startTaskId = startTask.getId(); + workflowService.endTask(startTaskId, null); + + // Query the active task, where timer should be on + WorkflowTask task = getNextTaskForWorkflow(workflowInstanceId); + + // Query for timers, timer should be active + List timers = workflowService.getTimers(workflowInstanceId); + assertNotNull(timers); + assertEquals(1, timers.size()); + + WorkflowTimer timer = timers.get(0); + assertNotNull(timer.getId()); + assertNotNull(timer.getDueDate()); + assertNotNull(timer.getName()); + assertNull(timer.getError()); + + // Check path, should be waiting in task-node + assertNotNull(timer.getPath()); + assertEquals(task.getPath().getId(), timer.getPath().getId()); + assertEquals(workflowInstanceId, timer.getPath().getInstance().getId()); + assertNotNull(timer.getPath().getNode()); + assertTrue(timer.getPath().getNode().isTaskNode()); + + // Check task + assertNotNull(timer.getTask()); + assertEquals(task.getId(), timer.getTask().getId()); + + // We finish the task, timer should be gone + workflowService.endTask(task.getId(), null); + timers = workflowService.getTimers(workflowInstanceId); + assertNotNull(timers); + assertEquals(0, timers.size()); + } + + public void testQueryTasks() + { + // Start adhoc Workflow + WorkflowDefinition workflowDef = deployDefinition(getAdhocDefinitionPath()); + assertNotNull(workflowDef); + + // Create params + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); + params.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "Test workflow description"); + QName customStringProp = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "customStringProp"); + params.put(customStringProp, "stringValue"); + + // Assign to USER2 + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); + + // Start a workflow instance + WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); + assertNotNull(path); + assertTrue(path.isActive()); + String workflowInstanceId = path.getInstance().getId(); + + // End start task to progress workflow + WorkflowTask startTask = workflowService.getStartTask(workflowInstanceId); + String startTaskId = startTask.getId(); + workflowService.endTask(startTaskId, null); + + WorkflowTask theTask = getNextTaskForWorkflow(workflowInstanceId); + + // Set some custom properties on the task + params = new HashMap(); + params.put(customStringProp, "stringValue"); + workflowService.updateTask(theTask.getId(), params, null, null); + + // Test all query features for running tasks + testTaskQueryInProgress(workflowInstanceId, theTask); + + // Test all query features for the start-task + testTaskQueryStartTaskCompleted(workflowInstanceId, startTask); + + // Finish the task adhoc-task + workflowService.endTask(theTask.getId(), null); + + // Test all query features for completed tasks + testTaskQueryTaskCompleted(workflowInstanceId, theTask, startTask); + + // Finally end the workflow and check the querying isActive == false + WorkflowTask lastTask = getNextTaskForWorkflow(workflowInstanceId); + workflowService.endTask(lastTask.getId(), null); + + testQueryTasksInactiveWorkflow(workflowInstanceId); + } + + protected void testQueryTasksInactiveWorkflow(String workflowInstanceId) { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setActive(false); + + List tasks = workflowService.queryTasks(taskQuery); + assertNotNull(tasks); + assertEquals(3, tasks.size()); + + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setActive(true); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void testTaskQueryTaskCompleted(String workflowInstanceId, + WorkflowTask theTask, WorkflowTask startTask) + { + List expectedTasks = Arrays.asList(theTask.getId(), startTask.getId()); + checkProcessIdQuery(workflowInstanceId, expectedTasks, WorkflowTaskState.COMPLETED); + + // Adhoc task should only be returned + QName taskName = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "adhocTask"); + checkTaskNameQuery(taskName, Arrays.asList(theTask.getId()), WorkflowTaskState.COMPLETED); + + // Completed adhocTask is assigned to USER2 + checkActorIdQuery(USER2, Arrays.asList(theTask.getId()), WorkflowTaskState.COMPLETED); + + // Workflow is still active, both tasks will be returned + checkIsActiveQuery(expectedTasks, WorkflowTaskState.COMPLETED); + + // Both tasks have custom property set + checkTaskPropsQuery(expectedTasks, WorkflowTaskState.COMPLETED); + } + + protected void testTaskQueryInProgress(String workflowInstanceId, WorkflowTask expectedTask) + { + List expectedTasks = Arrays.asList(expectedTask.getId()); + + checkProcessIdQuery(workflowInstanceId, expectedTasks, WorkflowTaskState.IN_PROGRESS); + checkTaskIdQuery(expectedTask.getId(), WorkflowTaskState.IN_PROGRESS); + + QName taskName = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "adhocTask"); + checkTaskNameQuery(taskName, expectedTasks, WorkflowTaskState.IN_PROGRESS); + checkActorIdQuery(USER2, expectedTasks, WorkflowTaskState.IN_PROGRESS); + checkIsActiveQuery(expectedTasks, WorkflowTaskState.IN_PROGRESS); + checkTaskPropsQuery(expectedTasks, WorkflowTaskState.IN_PROGRESS); + } + + protected void testTaskQueryStartTaskCompleted(String workflowInstanceId, WorkflowTask startTask) { + List expectedTasks = Arrays.asList(startTask.getId()); + + checkProcessIdQuery(workflowInstanceId, expectedTasks, WorkflowTaskState.COMPLETED); + checkTaskIdQuery(startTask.getId(), WorkflowTaskState.COMPLETED); + + QName startTaskName = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "submitAdhocTask"); + checkTaskNameQuery(startTaskName, expectedTasks, WorkflowTaskState.COMPLETED); + checkActorIdQuery(USER1, expectedTasks, WorkflowTaskState.COMPLETED); + checkIsActiveQuery(expectedTasks, WorkflowTaskState.COMPLETED); + checkTaskPropsQuery(expectedTasks, WorkflowTaskState.COMPLETED); + } + + public void testGetWorkflows() throws Exception + { + String fakeDefId = getEngine() + "$9999999999999"; + List workflows = workflowService.getActiveWorkflows(fakeDefId); + assertTrue(workflows.isEmpty()); + workflows = workflowService.getCompletedWorkflows(fakeDefId); + assertTrue(workflows.isEmpty()); + workflows = workflowService.getWorkflows(fakeDefId); + assertTrue(workflows.isEmpty()); + + WorkflowDefinition definition = deployDefinition(getTestDefinitionPath()); + String defId = definition.getId(); + + workflows = workflowService.getActiveWorkflows(defId); + assertTrue(workflows.isEmpty()); + workflows = workflowService.getCompletedWorkflows(defId); + assertTrue(workflows.isEmpty()); + workflows = workflowService.getWorkflows(defId); + assertTrue(workflows.isEmpty()); + + // Create workflow parameters + Date dueDate = new Date(); + String description = "Some Description"; + NodeRef assignee = personManager.get(USER2); + + Serializable wfPackage1 = workflowService.createPackage(null); + Map params = new HashMap(); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage1); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); // task instance field + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); // task instance field + params.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, description); // task instance field + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); // task instance field + + // Start workflow 1 + WorkflowPath path1 = workflowService.startWorkflow(defId, params); + String instance1 = path1.getInstance().getId(); + + checkActiveWorkflows(defId, instance1); + checkCompletedWorkflows(defId); + checkWorkflows(defId, instance1); + + // Start workflow 2 + Serializable wfPackage2 = workflowService.createPackage(null); + params = new HashMap(); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage2); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); // task instance field + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); // task instance field + params.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, description); // task instance field + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); // task instance field + + WorkflowPath path2 = workflowService.startWorkflow(defId, params); + String instance2 = path2.getInstance().getId(); + + checkActiveWorkflows(defId, instance1, instance2); + checkCompletedWorkflows(defId); + checkWorkflows(defId, instance1, instance2); + + // End workflow 1 + WorkflowTask startTask1 = workflowService.getStartTask(instance1); + workflowService.endTask(startTask1.getId(), null); + List tasks = workflowService.getTasksForWorkflowPath(path1.getId()); + assertEquals(1, tasks.size()); + WorkflowTask task1 = tasks.get(0); + workflowService.endTask(task1.getId(), null); + + checkActiveWorkflows(defId, instance2); + checkCompletedWorkflows(defId, instance1); + checkWorkflows(defId, instance1, instance2); + + // End workflow 2 + WorkflowTask startTask2 = workflowService.getStartTask(instance2); + workflowService.endTask(startTask2.getId(), null); + tasks = workflowService.getTasksForWorkflowPath(path2.getId()); + assertEquals(1, tasks.size()); + WorkflowTask task2 = tasks.get(0); + workflowService.endTask(task2.getId(), null); + + checkActiveWorkflows(defId); + checkCompletedWorkflows(defId, instance1, instance2); + checkWorkflows(defId, instance1, instance2); + } + + public void checkWorkflows(String defId, String... expectedIds) + { + List workflows = workflowService.getWorkflows(defId); + checkWorkflows(workflows, expectedIds); + } + + public void checkCompletedWorkflows(String defId, String... expectedIds) + { + List workflows = workflowService.getCompletedWorkflows(defId); + checkWorkflows(workflows, expectedIds); + } + + private void checkActiveWorkflows(String defId, String... expectedIds) + { + List workflows = workflowService.getActiveWorkflows(defId); + checkWorkflows(workflows, expectedIds); + } + + private void checkWorkflows(List workflows, String... expectedIds) + { + assertEquals(expectedIds.length, workflows.size()); + List expIds = Arrays.asList(expectedIds); + for (WorkflowInstance workflow : workflows) + { + String workflowId = workflow.getId(); + assertTrue("The id: "+workflowId +" was not expected! Expected Ids: "+expIds, expIds.contains(workflowId)); + } + } + + protected void checkTaskNameQuery(QName taskName, List expectedTaskIds, WorkflowTaskState state) { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + taskQuery.setTaskName(taskName); + checkTasksFoundUsingQuery(expectedTaskIds, taskQuery); + + QName unexistingTaskName = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "unexistingTask"); + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setTaskName(unexistingTaskName); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void checkProcessIdQuery(String workflowInstanceId, List expectedTaskIds, WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + taskQuery.setProcessId(workflowInstanceId); + checkTasksFoundUsingQuery(expectedTaskIds, taskQuery); + + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setProcessId(BPMEngineRegistry.createGlobalId(getEngine(), "99999999999")); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void checkTaskIdQuery(String expectedTaskId, WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + taskQuery.setTaskId(expectedTaskId); + checkTasksFoundUsingQuery(Arrays.asList(expectedTaskId), taskQuery); + + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setTaskId(BPMEngineRegistry.createGlobalId(getEngine(), "99999999999")); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void checkIsActiveQuery(List expectedTaskIds, WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + taskQuery.setActive(true); + checkTasksFoundUsingQuery(expectedTaskIds, taskQuery); + + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setActive(false); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void checkActorIdQuery(String actorId, List expectedTaskIds, WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + taskQuery.setActorId(actorId); + checkTasksFoundUsingQuery(expectedTaskIds, taskQuery); + + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setActorId(USER3); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void checkTaskPropsQuery(List expectedTaskIds, WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + QName customStringProp = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "customStringProp"); + Map taskProps = new HashMap(); + + taskProps.put(customStringProp, "stringValue"); + + taskQuery.setTaskCustomProps(taskProps); + checkTasksFoundUsingQuery(expectedTaskIds, taskQuery); + + taskProps = new HashMap(); + taskProps.put(customStringProp, "otherValue"); + + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setTaskCustomProps(taskProps); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void checkProcessNameQuery(List expectedTaskIds, WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + + taskQuery.setProcessName(getAdhocProcessName()); + checkTasksFoundUsingQuery(expectedTaskIds, taskQuery); + + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setProcessName(QName.createQName("dummyProcessName")); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected void checkProcessPropsQuery(List expectedTaskIds, WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + + Map processProps = new HashMap(); + processProps.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "Test workflow description"); + taskQuery.setProcessCustomProps(processProps); + checkTasksFoundUsingQuery(expectedTaskIds, taskQuery); + + processProps = new HashMap(); + processProps.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "Wrong workflow description"); + taskQuery = createWorkflowTaskQuery(state); + taskQuery.setTaskCustomProps(processProps); + checkNoTasksFoundUsingQuery(taskQuery); + } + + protected WorkflowTaskQuery createWorkflowTaskQuery(WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = new WorkflowTaskQuery(); + taskQuery.setTaskState(state); + return taskQuery; + } + + protected void checkTasksFoundUsingQuery(List taskIds, WorkflowTaskQuery workflowTaskQuery) + { + List tasks = workflowService.queryTasks(workflowTaskQuery); + assertNotNull(tasks); + assertEquals(taskIds.size(), tasks.size()); + for(WorkflowTask task : tasks) + { + assertTrue(taskIds.contains(task.getId())); + } + } + + protected void checkNoTasksFoundUsingQuery(WorkflowTaskQuery workflowTaskQuery) + { + List tasks = workflowService.queryTasks(workflowTaskQuery); + assertNotNull(tasks); + assertEquals(0, tasks.size()); + } + + + protected WorkflowTask getNextTaskForWorkflow(String workflowInstanceId) + { + WorkflowTaskQuery taskQuery = new WorkflowTaskQuery(); + taskQuery.setProcessId(workflowInstanceId); + taskQuery.setTaskState(WorkflowTaskState.IN_PROGRESS); + + List workflowTasks = workflowService.queryTasks(taskQuery); + assertEquals(1, workflowTasks.size()); + return workflowTasks.get(0); + } + + protected WorkflowDefinition deployDefinition(String resource) + { + InputStream input = getInputStream(resource); + WorkflowDeployment deployment = workflowService.deployDefinition(getEngine(), input, XML); + WorkflowDefinition definition = deployment.getDefinition(); + return definition; + } + + protected abstract QName getAdhocProcessName(); + + + private InputStream getInputStream(String resource) + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input= classLoader.getResourceAsStream(resource); + return input; + } + + private void checkDefinitions(WorkflowDefinition expDefinition, boolean expContainsDef, WorkflowDefinition... definitions) + { + String id = expDefinition.getId(); + for (WorkflowDefinition definition : definitions) + { + if(expContainsDef) + { + if(definition.getId().equals(id)) + { + checkDefinition(expDefinition, definition); + return; + } + + } + else + { + if(definition.getId().equals(id)) + { + fail("The definitions unexpectedly contain id: "+id); + } + } + } + if(expContainsDef) + fail("The definitions did not contain expected id: "+id); + } + + private void checkDefinition(WorkflowDefinition expDef, WorkflowDefinition actualDef) + { + assertEquals(expDef.getId(), actualDef.getId()); + assertEquals(expDef.getName(), actualDef.getName()); + assertEquals(expDef.getDescription(), actualDef.getDescription()); + assertEquals(expDef.getTitle(), actualDef.getTitle()); + assertEquals(expDef.getVersion(), actualDef.getVersion()); + } + + + @SuppressWarnings("deprecation") + @Override + protected void onSetUpInTransaction() throws Exception + { + ServiceRegistry registry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + this.workflowService = registry.getWorkflowService(); + this.authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); + this.nodeService = registry.getNodeService(); + Repository repositoryHelper = (Repository) applicationContext.getBean("repositoryHelper"); + this.companyHome = repositoryHelper.getCompanyHome(); + + MutableAuthenticationService authenticationService = registry.getAuthenticationService(); + AuthorityService authorityService = registry.getAuthorityService(); + PersonService personService = registry.getPersonService(); + SearchService searchService = registry.getSearchService(); + + authenticationComponent.setSystemUserAsCurrentUser(); + + // create test users + this.personManager = new TestPersonManager(authenticationService, personService, nodeService); + this.groupManager = new TestGroupManager(authorityService, searchService); + + personManager.createPerson(USER1); + personManager.createPerson(USER2); + personManager.createPerson(USER3); + + // create test groups + groupManager.addGroupToParent(GROUP, SUB_GROUP); + + // add users to groups + groupManager.addUserToGroup(GROUP, USER1); + groupManager.addUserToGroup(SUB_GROUP, USER2); + + personManager.setUser(USER1); + } + + @SuppressWarnings("deprecation") + @Override + protected void onTearDownInTransaction() throws Exception + { + super.onTearDownInTransaction(); + + authenticationComponent.setSystemUserAsCurrentUser(); + groupManager.clearGroups(); + personManager.clearPeople(); + authenticationComponent.clearCurrentSecurityContext(); + } + + protected abstract String getEngine(); + + protected abstract String getTestDefinitionPath(); + + protected abstract String getAdhocDefinitionPath(); + + protected abstract String getPooledReviewDefinitionPath(); + + protected abstract String getTestTimerDefinitionPath(); +} diff --git a/source/java/org/alfresco/repo/workflow/AlfrescoBpmEngine.java b/source/java/org/alfresco/repo/workflow/AlfrescoBpmEngine.java new file mode 100644 index 0000000000..a871b3374f --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/AlfrescoBpmEngine.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.namespace.NamespaceService; + +/** + * @since 3.5 + * @author Nick Smith + * + */ +public abstract class AlfrescoBpmEngine extends BPMEngine +{ + protected TenantService tenantService; + protected MessageService messageService; + protected NamespaceService namespaceService; + protected DictionaryService dictionaryService; + protected WorkflowObjectFactory factory; + protected WorkflowAuthorityManager authorityManager; + + /** + * {@inheritDoc} + */ + @Override + public void afterPropertiesSet() throws Exception + { + super.afterPropertiesSet(); + if (tenantService ==null) + { + throw new WorkflowException("TenantService not specified"); + } + if (messageService ==null) + { + throw new WorkflowException("MessageService not specified"); + } + if (namespaceService ==null) + { + throw new WorkflowException("NamespaceService not specified"); + } + WorkflowQNameConverter qNameConverter = new WorkflowQNameConverter(namespaceService); + this.factory = new WorkflowObjectFactory(qNameConverter, tenantService, messageService, dictionaryService, getEngineId()); + } + + /** + * Sets the Tenant Service + * + * @param tenantService + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * Sets the Message Service + * + * @param messageService + */ + public void setMessageService(MessageService messageService) + { + this.messageService = messageService; + } + + /** + * Sets the Namespace Service + * + * @param namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param dictionaryService the dictionaryService to set + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param factory the factory to set + */ + public void setWorkflowObjectFactory(WorkflowObjectFactory factory) + { + this.factory = factory; + } + + /** + * @param authorityManager the authorityManager to set + */ + public void setWorkflowAuthorityManager(WorkflowAuthorityManager authorityManager) + { + this.authorityManager = authorityManager; + } +} diff --git a/source/java/org/alfresco/repo/workflow/BPMEngine.java b/source/java/org/alfresco/repo/workflow/BPMEngine.java index f8f47de15c..c247bbda66 100644 --- a/source/java/org/alfresco/repo/workflow/BPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/BPMEngine.java @@ -26,12 +26,12 @@ import org.springframework.beans.factory.InitializingBean; * Base functionality for a plug-in BPM Engine * * @author davidc + * @author Nick Smith */ public class BPMEngine implements InitializingBean { private BPMEngineRegistry registry; private String engineId; - /** * Sets the BPM Engine Registry @@ -53,9 +53,16 @@ public class BPMEngine implements InitializingBean this.engineId = engineId; } - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + /** + * @return the engineId + */ + protected String getEngineId() + { + return engineId; + } + + /** + * {@inheritDoc} */ public void afterPropertiesSet() throws Exception { @@ -63,7 +70,6 @@ public class BPMEngine implements InitializingBean { throw new WorkflowException("Engine Id not specified"); } - if (this instanceof WorkflowComponent) { registry.registerWorkflowComponent(engineId, (WorkflowComponent)this); @@ -80,7 +86,7 @@ public class BPMEngine implements InitializingBean * @param localId the local engine id * @return the global id */ - protected String createGlobalId(String localId) + public String createGlobalId(String localId) { return BPMEngineRegistry.createGlobalId(engineId, localId); } @@ -91,8 +97,9 @@ public class BPMEngine implements InitializingBean * @param globalId the global id * @return the local id */ - protected String createLocalId(String globalId) + public String createLocalId(String globalId) { return BPMEngineRegistry.getLocalId(globalId); } + } diff --git a/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java b/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java index e2f3c00d43..fe54d03fc1 100644 --- a/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java +++ b/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java @@ -49,7 +49,6 @@ public class BPMEngineRegistry private Map workflowComponents; private Map taskComponents; - /** * Construct */ @@ -190,6 +189,19 @@ public class BPMEngineRegistry public static String getLocalId(String globalId) { return getGlobalIdParts(globalId)[1]; - } - + } + + /** + * Returns true if the globalId parameter is a valid global Id + * for the given engineId. + * + * @param globalId + * @param engineId + * @return + */ + public static boolean isGlobalId(String globalId, String engineId) + { + return globalId.startsWith(engineId+ID_SEPERATOR); + } + } diff --git a/source/java/org/alfresco/repo/workflow/DefaultWorkflowPropertyHandler.java b/source/java/org/alfresco/repo/workflow/DefaultWorkflowPropertyHandler.java new file mode 100644 index 0000000000..6c36b67dc3 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/DefaultWorkflowPropertyHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.Serializable; + +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class DefaultWorkflowPropertyHandler extends AbstractWorkflowPropertyHandler +{ + /** + * {@inheritDoc} + */ + public Object handleProperty(QName key, Serializable value, TypeDefinition type, Object object, Class objectType) + { + return handleDefaultProperty(object, type, key, value); + } + + /** + * {@inheritDoc} + */ + @Override + protected QName getKey() + { + // Does not have a key! + return null; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/NodeConverter.java b/source/java/org/alfresco/repo/workflow/NodeConverter.java new file mode 100644 index 0000000000..29d902ae3b --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/NodeConverter.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.util.Collection; +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @since 3.5 + * @author Nick Smith + * + */ +public interface NodeConverter +{ + List convertNodes(Collection nodes); + + Object convertNode(NodeRef node); +} diff --git a/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuter.java b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuter.java index 85a8c59720..e0db189097 100644 --- a/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuter.java +++ b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuter.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; import org.alfresco.service.cmr.action.Action; @@ -144,7 +143,7 @@ public class StartWorkflowActionExecuter extends ActionExecuterAbstractBase } // start the workflow - WorkflowPath path = workflowService.startWorkflow(def.id, workflowParameters); + WorkflowPath path = workflowService.startWorkflow(def.getId(), workflowParameters); // determine whether to auto-end the start task Boolean endStartTask = (Boolean)ruleAction.getParameterValue(PARAM_END_START_TASK); @@ -154,10 +153,10 @@ public class StartWorkflowActionExecuter extends ActionExecuterAbstractBase // auto-end the start task with the provided transition (if one) if (endStartTask) { - List tasks = workflowService.getTasksForWorkflowPath(path.id); + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); for (WorkflowTask task : tasks) { - workflowService.endTask(task.id, startTaskTransition); + workflowService.endTask(task.getId(), startTaskTransition); } } } diff --git a/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuterTest.java b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuterTest.java index b0dc83465c..a1ac9e9c7d 100644 --- a/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuterTest.java +++ b/source/java/org/alfresco/repo/workflow/StartWorkflowActionExecuterTest.java @@ -51,6 +51,7 @@ public class StartWorkflowActionExecuterTest extends BaseSpringTest /** * Called at the begining of all tests */ + @SuppressWarnings("deprecation") @Override protected void onSetUpInTransaction() throws Exception { diff --git a/source/java/org/alfresco/repo/workflow/TaskComponent.java b/source/java/org/alfresco/repo/workflow/TaskComponent.java index 305b010ea9..bc0ffdda8b 100644 --- a/source/java/org/alfresco/repo/workflow/TaskComponent.java +++ b/source/java/org/alfresco/repo/workflow/TaskComponent.java @@ -108,6 +108,12 @@ public interface TaskComponent * @return the updated task */ public WorkflowTask endTask(String taskId, String transitionId); - + + /** + * Gets all active timers for the specified workflow + * + * @return the list of active timers + */ + public WorkflowTask getStartTask(String workflowInstanceId); } diff --git a/source/java/org/alfresco/repo/workflow/TaskUpdater.java b/source/java/org/alfresco/repo/workflow/TaskUpdater.java index 75a62a3a78..3051aadb3a 100644 --- a/source/java/org/alfresco/repo/workflow/TaskUpdater.java +++ b/source/java/org/alfresco/repo/workflow/TaskUpdater.java @@ -136,7 +136,7 @@ public class TaskUpdater public WorkflowTask update() { WorkflowTask task = workflowService.getTaskById(taskId); - NodeRef packageNode = task.path.instance.workflowPackage; + NodeRef packageNode = task.getPath().getInstance().getWorkflowPackage(); packageMgr.update(packageNode); WorkflowTask result = workflowService.updateTask(taskId, properties, add, remove); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowAuthorityManager.java b/source/java/org/alfresco/repo/workflow/WorkflowAuthorityManager.java new file mode 100644 index 0000000000..2d0afcd48c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowAuthorityManager.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AuthorityType; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class WorkflowAuthorityManager +{ + private final AuthorityDAO authorityDAO; + + + public WorkflowAuthorityManager(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + /** + * Convert Alfresco authority to user id + * + * @param authority + * @return actor id + */ + public String mapAuthorityToName(NodeRef authority) + { + return authorityDAO.getAuthorityName(authority); + } + + /** + * Convert authority name to an Alfresco Authority + * + * @param names + * the authority names to convert + * @return the Alfresco authorities + */ + public NodeRef mapNameToAuthority(String name) + { + NodeRef authority = null; + if (name != null) + { + authority = authorityDAO.getAuthorityNodeRefOrNull(name); + } + return authority; + } + + public boolean isUser(String authorityName) + { + AuthorityType type = AuthorityType.getAuthorityType(authorityName); + return type == AuthorityType.USER || + type == AuthorityType.ADMIN || + type == AuthorityType.GUEST; + } + + public String getAuthorityName(NodeRef authorityRef) + { + return authorityDAO.getAuthorityName(authorityRef); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java b/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java index 1ad0fc0d72..c5498700ac 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java @@ -88,9 +88,9 @@ public class WorkflowBuilder { NodeRef packageRef = packageMgr.create(packageNode); params.put(WorkflowModel.ASSOC_PACKAGE, packageRef); - WorkflowPath path = workflowService.startWorkflow(definition.id, params); + WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); signalStartTask(path); - return path.instance; + return path.getInstance(); } private void signalStartTask(WorkflowPath path) @@ -99,7 +99,7 @@ public class WorkflowBuilder if (tasks.size() == 1) { WorkflowTask startTask = tasks.get(0); - workflowService.endTask(startTask.id, null); + workflowService.endTask(startTask.getId(), null); } else { diff --git a/source/java/org/alfresco/repo/workflow/WorkflowConstants.java b/source/java/org/alfresco/repo/workflow/WorkflowConstants.java new file mode 100644 index 0000000000..0a0a27ac1d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowConstants.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +/** + * @author Frederik Heremans + */ +public interface WorkflowConstants +{ + public static final String PROP_INITIATOR = "initiator"; + public static final String PROP_INITIATOR_HOME = "initiatorhome"; + public static final String PROP_COMPANY_HOME = "companyhome"; + public static final String PROP_WORKFLOW_INSTANCE_ID = "workflowinstanceid"; + public static final String PROP_CANCELLED = "cancelled"; + + public static final String TASK_STATUS_COMPLETED = "Completed"; +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java b/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java index d6ab05b46b..aaae2e3f8b 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java @@ -113,8 +113,8 @@ public class WorkflowDefinitionType implements ContentServicePolicies.OnContentU Boolean beforeValue = (Boolean)before.get(WorkflowModel.PROP_WORKFLOW_DEF_DEPLOYED); Boolean afterValue = (Boolean)after.get(WorkflowModel.PROP_WORKFLOW_DEF_DEPLOYED); - if (afterValue != null && - (beforeValue == null || (beforeValue != null && afterValue != null && beforeValue.equals(afterValue) == false))) + // If the afterValue exists and is different from the beforeValue... + if (afterValue != null && (afterValue.equals(beforeValue) == false)) { if (afterValue.booleanValue() == true) { diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java index 57cd0daa5f..25f0513932 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java @@ -298,8 +298,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean else { WorkflowDeployment deployment = workflowService.deployDefinition(engineId, workflowResource.getInputStream(), mimetype); - if (logger.isInfoEnabled()) - logger.info("Workflow deployer: Deployed process definition '" + deployment.definition.title + "' (version " + deployment.definition.version + ") from '" + location + "' with " + deployment.problems.length + " problems"); + logDeployment(location, deployment); } } } @@ -326,7 +325,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean catch(Throwable e) { // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) { /* NOOP */ } throw new AlfrescoRuntimeException("Workflow deployment failed", e); } finally @@ -357,13 +356,10 @@ public class WorkflowDeployer extends AbstractLifecycleBean { // deploy / re-deploy WorkflowDeployment deployment = workflowService.deployDefinition(nodeRef); - if (logger.isInfoEnabled()) - { - logger.info("Workflow deployer: Deployed process definition '" + deployment.definition.title + "' (version " + deployment.definition.version + ") from '" + nodeRef + "' with " + deployment.problems.length + " problems"); - } + logDeployment(nodeRef, deployment); if (deployment != null) { - WorkflowDefinition def = deployment.definition; + WorkflowDefinition def = deployment.getDefinition(); // Update the meta data for the model Map props = nodeService.getProperties(nodeRef); @@ -371,9 +367,9 @@ public class WorkflowDeployer extends AbstractLifecycleBean props.put(WorkflowModel.PROP_WORKFLOW_DEF_NAME, def.getName()); // TODO - ability to return and handle deployment problems / warnings - if (deployment.problems.length > 0) + if (deployment.getProblems().length > 0) { - for (String problem : deployment.problems) + for (String problem : deployment.getProblems()) { logger.warn(problem); } @@ -392,6 +388,17 @@ public class WorkflowDeployer extends AbstractLifecycleBean } } } + + private void logDeployment(Object location, WorkflowDeployment deployment) + { + if (logger.isInfoEnabled()) + { + String title = deployment.getDefinition().getTitle(); + String version = deployment.getDefinition().getVersion(); + int problemLength = deployment.getProblems().length; + logger.info("Workflow deployer: Deployed process definition '" + title + "' (version " + version + ") from '" + location + "' with " + problemLength + " problems"); + } + } public void undeploy(NodeRef nodeRef) { diff --git a/source/java/org/alfresco/repo/workflow/WorkflowEngine.java b/source/java/org/alfresco/repo/workflow/WorkflowEngine.java new file mode 100644 index 0000000000..4e6d983bec --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowEngine.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public interface WorkflowEngine extends WorkflowComponent, TaskComponent +{ + // Empty +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java index 23f02a894e..89dda1aebe 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java @@ -87,7 +87,8 @@ public class WorkflowInterpreter extends BaseInterpreter */ private WorkflowDefinition currentWorkflowDef = null; private WorkflowPath currentPath = null; - private String currentDeploy = null; + private String currentDeployResource = null; + private String currentDeployEngine = null; /** @@ -99,15 +100,16 @@ public class WorkflowInterpreter extends BaseInterpreter /* (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent) + * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.getContext().ApplicationEvent) */ @Override protected void onBootstrap(ApplicationEvent event) { + //NOOP } /* (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent) + * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.getContext().ApplicationEvent) */ @Override protected void onShutdown(ApplicationEvent event) @@ -174,6 +176,7 @@ public class WorkflowInterpreter extends BaseInterpreter /** * @param transactionService transactionService */ + @Override public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; @@ -203,6 +206,7 @@ public class WorkflowInterpreter extends BaseInterpreter runMain("workflowInterpreter"); } + @Override protected boolean hasAuthority(String username) { // admin can change to any user (via worklow command "user ") @@ -217,6 +221,7 @@ public class WorkflowInterpreter extends BaseInterpreter * @param line The unparsed command * @return The textual output of the command. */ + @Override protected String executeCommand(String line) throws IOException { @@ -317,13 +322,13 @@ public class WorkflowInterpreter extends BaseInterpreter } for (WorkflowDefinition def : defs) { - out.println("id: " + def.id + " , name: " + def.name + " , title: " + def.title + " , version: " + def.version); + out.println("id: " + def.getId() + " , name: " + def.getName() + " , title: " + def.getTitle() + " , version: " + def.getVersion()); } } else if (command[1].equals("workflows")) { - String id = (currentWorkflowDef != null) ? currentWorkflowDef.id : null; + String id = (currentWorkflowDef != null) ? currentWorkflowDef.getId() : null; if (id == null && command.length == 2) { return "workflow definition not in use. Enter command 'show workflows all' or 'use '.\n"; @@ -340,14 +345,14 @@ public class WorkflowInterpreter extends BaseInterpreter } } - if (id.equals("all")) + if ("all".equals(id)) { for (WorkflowDefinition def : workflowService.getAllDefinitions()) { - List workflows = workflowService.getActiveWorkflows(def.id); + List workflows = workflowService.getActiveWorkflows(def.getId()); for (WorkflowInstance workflow : workflows) { - out.println("id: " + workflow.id + " , desc: " + workflow.description + " , start date: " + workflow.startDate + " , def: " + workflow.definition.name + " v" + workflow.definition.version); + out.println("id: " + workflow.getId() + " , desc: " + workflow.getDescription() + " , start date: " + workflow.getStartDate() + " , def: " + workflow.getDefinition().getName() + " v" + workflow.getDefinition().getVersion()); } } } @@ -356,14 +361,14 @@ public class WorkflowInterpreter extends BaseInterpreter List workflows = workflowService.getActiveWorkflows(id); for (WorkflowInstance workflow : workflows) { - out.println("id: " + workflow.id + " , desc: " + workflow.description + " , start date: " + workflow.startDate + " , def: " + workflow.definition.name); + out.println("id: " + workflow.getId() + " , desc: " + workflow.getDescription() + " , start date: " + workflow.getStartDate() + " , def: " + workflow.getDefinition().getName()); } } } else if (command[1].equals("paths")) { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); if (workflowId == null) { return "Syntax Error. Workflow Id not specified.\n"; @@ -371,13 +376,13 @@ public class WorkflowInterpreter extends BaseInterpreter List paths = workflowService.getWorkflowPaths(workflowId); for (WorkflowPath path : paths) { - out.println("path id: " + path.id + " , node: " + path.node.name); + out.println("path id: " + path.getId() + " , node: " + path.getNode().getName()); } } else if (command[1].equals("tasks")) { - String pathId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.id; + String pathId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getId(); if (pathId == null) { return "Syntax Error. Path Id not specified.\n"; @@ -385,13 +390,13 @@ public class WorkflowInterpreter extends BaseInterpreter List tasks = workflowService.getTasksForWorkflowPath(pathId); for (WorkflowTask task : tasks) { - out.println("task id: " + task.id + " , name: " + task.name + " , properties: " + task.properties.size()); + out.println("task id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size()); } } else if (command[1].equals("transitions")) { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); if (workflowId == null) { return "Syntax Error. Workflow Id not specified.\n"; @@ -403,22 +408,22 @@ public class WorkflowInterpreter extends BaseInterpreter } for (WorkflowPath path : paths) { - out.println("path: " + path.id + " , node: " + path.node.name + " , active: " + path.active); - List tasks = workflowService.getTasksForWorkflowPath(path.id); + out.println("path: " + path.getId() + " , node: " + path.getNode().getName() + " , active: " + path.isActive()); + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); for (WorkflowTask task : tasks) { - out.println(" task id: " + task.id + " , name: " + task.name + ", title: " + task.title + " , desc: " + task.description + " , properties: " + task.properties.size()); + out.println(" task id: " + task.getId() + " , name: " + task.getName() + ", title: " + task.getTitle() + " , desc: " + task.getDescription() + " , properties: " + task.getProperties().size()); } - for (WorkflowTransition transition : path.node.transitions) + for (WorkflowTransition transition : path.getNode().getTransitions()) { - out.println(" transition id: " + ((transition.id == null || transition.id.equals("")) ? "[default]" : transition.id) + " , title: " + transition.title); + out.println(" transition id: " + ((transition.getId() == null || transition.getId().equals("")) ? "[default]" : transition.getId()) + " , title: " + transition.getTitle()); } } } else if (command[1].equals("timers")) { - String id = (currentWorkflowDef != null) ? currentWorkflowDef.id : null; + String id = (currentWorkflowDef != null) ? currentWorkflowDef.getId() : null; if (id == null && command.length == 2) { return "workflow definition not in use. Enter command 'show timers all' or 'use '.\n"; @@ -437,14 +442,14 @@ public class WorkflowInterpreter extends BaseInterpreter List timers = new ArrayList(); - if (id.equals("all")) + if ("all".equals(id)) { for (WorkflowDefinition def : workflowService.getAllDefinitions()) { - List workflows = workflowService.getActiveWorkflows(def.id); + List workflows = workflowService.getActiveWorkflows(def.getId()); for (WorkflowInstance workflow : workflows) { - timers.addAll(workflowService.getTimers(workflow.id)); + timers.addAll(workflowService.getTimers(workflow.getId())); } } } @@ -453,22 +458,22 @@ public class WorkflowInterpreter extends BaseInterpreter List workflows = workflowService.getActiveWorkflows(id); for (WorkflowInstance workflow : workflows) { - timers.addAll(workflowService.getTimers(workflow.id)); + timers.addAll(workflowService.getTimers(workflow.getId())); } } for (WorkflowTimer timer : timers) { - out.print("id: " + timer.id + " , name: " + timer.name + " , due date: " + timer.dueDate + " , path: " + timer.path.id + " , node: " + timer.path.node.name + " , process: " + timer.path.instance.id); - if (timer.task != null) + out.print("id: " + timer.getId() + " , name: " + timer.getName() + " , due date: " + timer.getDueDate() + " , path: " + timer.getPath().getId() + " , node: " + timer.getPath().getNode().getName() + " , process: " + timer.getPath().getInstance().getId()); + if (timer.getTask() != null) { - out.print(" , task: " + timer.task.name + "(" + timer.task.id + ")"); + out.print(" , task: " + timer.getTask().getName() + "(" + timer.getTask().getId() + ")"); } out.println(); - if (timer.error != null) + if (timer.getError() != null) { - out.println("error executing timer id " + timer.id); - out.println(timer.error); + out.println("error executing timer id " + timer.getId()); + out.println(timer.getError()); } } } @@ -486,7 +491,7 @@ public class WorkflowInterpreter extends BaseInterpreter List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getRunAsUser(), WorkflowTaskState.IN_PROGRESS); for (WorkflowTask task : tasks) { - out.println("id: " + task.id + " , name: " + task.name + " , properties: " + task.properties.size() + " , workflow: " + task.path.instance.id + " , path: " + task.path.id); + out.println("id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); } } @@ -496,7 +501,7 @@ public class WorkflowInterpreter extends BaseInterpreter List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getRunAsUser(), WorkflowTaskState.COMPLETED); for (WorkflowTask task : tasks) { - out.println("id: " + task.id + " , name " + task.name + " , properties: " + task.properties.size() + " , workflow: " + task.path.instance.id + " , path: " + task.path.id); + out.println("id: " + task.getId() + " , name " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); } } @@ -506,7 +511,7 @@ public class WorkflowInterpreter extends BaseInterpreter List tasks = workflowService.getPooledTasks(AuthenticationUtil.getRunAsUser()); for (WorkflowTask task : tasks) { - out.println("id: " + task.id + " , name " + task.name + " , properties: " + task.properties.size() + " , workflow: " + task.path.instance.id + " , path: " + task.path.id); + out.println("id: " + task.getId() + " , name " + task.getName() + " , properties: " + task.getProperties().size() + " , workflow: " + task.getPath().getInstance().getId() + " , path: " + task.getPath().getId()); } } @@ -535,19 +540,19 @@ public class WorkflowInterpreter extends BaseInterpreter return "Syntax Error.\n"; } WorkflowTask task = workflowService.getTaskById(command[2]); - out.println("id: " + task.id); - out.println("name: " + task.name); - out.println("title: " + task.title); - out.println("description: " + task.description); - out.println("state: " + task.state); - out.println("path: " + task.path.id); - out.println("transitions: " + task.definition.node.transitions.length); - for (WorkflowTransition transition : task.definition.node.transitions) + out.println("id: " + task.getId()); + out.println("name: " + task.getName()); + out.println("title: " + task.getTitle()); + out.println("description: " + task.getDescription()); + out.println("state: " + task.getState()); + out.println("path: " + task.getPath().getId()); + out.println("transitions: " + task.getDefinition().getNode().getTransitions().length); + for (WorkflowTransition transition : task.getDefinition().getNode().getTransitions()) { - out.println(" transition: " + ((transition.id == null || transition.id.equals("")) ? "[default]" : transition.id) + " , title: " + transition.title + " , desc: " + transition.description); + out.println(" transition: " + ((transition.getId() == null || transition.getId().equals("")) ? "[default]" : transition.getId()) + " , title: " + transition.getTitle() + " , desc: " + transition.getDescription()); } - out.println("properties: " + task.properties.size()); - for (Map.Entry prop : task.properties.entrySet()) + out.println("properties: " + task.getProperties().size()); + for (Map.Entry prop : task.getProperties().entrySet()) { out.println(" " + prop.getKey() + " = " + prop.getValue()); } @@ -560,15 +565,15 @@ public class WorkflowInterpreter extends BaseInterpreter return "Syntax Error.\n"; } WorkflowInstance workflow = workflowService.getWorkflowById(command[2]); - out.println("definition: " + workflow.definition.name); - out.println("id: " + workflow.id); - out.println("description: " + workflow.description); - out.println("active: " + workflow.active); - out.println("start date: " + workflow.startDate); - out.println("end date: " + workflow.endDate); - out.println("initiator: " + workflow.initiator); - out.println("context: " + workflow.context); - out.println("package: " + workflow.workflowPackage); + out.println("definition: " + workflow.getDefinition().getName()); + out.println("id: " + workflow.getId()); + out.println("description: " + workflow.getDescription()); + out.println("active: " + workflow.isActive()); + out.println("start date: " + workflow.getStartDate()); + out.println("end date: " + workflow.getEndDate()); + out.println("initiator: " + workflow.getInitiator()); + out.println("context: " + workflow.getContext()); + out.println("package: " + workflow.getWorkflowPackage()); } else if (command[1].equals("path")) @@ -704,7 +709,7 @@ public class WorkflowInterpreter extends BaseInterpreter out.println("found " + tasks.size() + " tasks."); for (WorkflowTask task : tasks) { - out.println("task id: " + task.id + " , name: " + task.name + " , properties: " + task.properties.size() + ", process id: " + task.path.instance); + out.println("task id: " + task.getId() + " , name: " + task.getName() + " , properties: " + task.getProperties().size() + ", process id: " + task.getPath().getInstance()); } } @@ -716,29 +721,30 @@ public class WorkflowInterpreter extends BaseInterpreter else if (command[0].equals("deploy")) { - if (command.length != 2) + if (command.length != 3) { return "Syntax Error.\n"; } - ClassPathResource workflowDef = new ClassPathResource(command[1]); - WorkflowDeployment deployment = workflowService.deployDefinition("jbpm", workflowDef.getInputStream(), MimetypeMap.MIMETYPE_XML); - WorkflowDefinition def = deployment.definition; - for (String problem : deployment.problems) + ClassPathResource workflowDef = new ClassPathResource(command[2]); + WorkflowDeployment deployment = workflowService.deployDefinition(command[1], workflowDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + WorkflowDefinition def = deployment.getDefinition(); + for (String problem : deployment.getProblems()) { out.println(problem); } - out.println("deployed definition id: " + def.id + " , name: " + def.name + " , title: " + def.title + " , version: " + def.version); - currentDeploy = command[1]; - out.print(executeCommand("use definition " + def.id)); + out.println("deployed definition id: " + def.getId() + " , name: " + def.getName() + " , title: " + def.getTitle() + " , version: " + def.getVersion()); + currentDeployEngine = command[1]; + currentDeployResource = command[2]; + out.print(executeCommand("use definition " + def.getId())); } else if (command[0].equals("redeploy")) { - if (currentDeploy == null) + if (currentDeployResource == null) { return "nothing to redeploy\n"; } - out.print(executeCommand("deploy " + currentDeploy)); + out.print(executeCommand("deploy " + currentDeployEngine + " " + currentDeployResource)); } else if (command[0].equals("undeploy")) @@ -764,8 +770,8 @@ public class WorkflowInterpreter extends BaseInterpreter List defs = workflowService.getAllDefinitionsByName(command[3]); for (WorkflowDefinition def: defs) { - workflowService.undeployDefinition(def.id); - out.print(" v" + def.version); + workflowService.undeployDefinition(def.getId()); + out.print(" v" + def.getVersion()); } out.println(""); currentWorkflowDef = null; @@ -792,9 +798,9 @@ public class WorkflowInterpreter extends BaseInterpreter { if (command.length == 1) { - out.println("definition: " + ((currentWorkflowDef == null) ? "None" : currentWorkflowDef.id + " , name: " + currentWorkflowDef.title + " , version: " + currentWorkflowDef.version)); - out.println("workflow: " + ((currentPath == null) ? "None" : currentPath.instance.id + " , active: " + currentPath.instance.active)); - out.println("path: " + ((currentPath == null) ? "None" : currentPath.id + " , node: " + currentPath.node.title)); + out.println("definition: " + ((currentWorkflowDef == null) ? "None" : currentWorkflowDef.getId() + " , name: " + currentWorkflowDef.getTitle() + " , version: " + currentWorkflowDef.getVersion())); + out.println("workflow: " + ((currentPath == null) ? "None" : currentPath.getInstance().getId() + " , active: " + currentPath.getInstance().isActive())); + out.println("path: " + ((currentPath == null) ? "None" : currentPath.getId() + " , node: " + currentPath.getNode().getTitle())); } else if (command.length > 1) { @@ -821,8 +827,8 @@ public class WorkflowInterpreter extends BaseInterpreter return "Syntax Error.\n"; } WorkflowInstance instance = workflowService.getWorkflowById(command[2]); - currentWorkflowDef = instance.definition; - currentPath = workflowService.getWorkflowPaths(instance.id).get(0); + currentWorkflowDef = instance.getDefinition(); + currentPath = workflowService.getWorkflowPaths(instance.getId()).get(0); out.print(executeCommand("use")); } else @@ -873,8 +879,8 @@ public class WorkflowInterpreter extends BaseInterpreter { return "Workflow definition not selected.\n"; } - WorkflowPath path = workflowService.startWorkflow(currentWorkflowDef.id, params); - out.println("started workflow id: " + path.instance.id + " , def: " + path.instance.definition.title); + WorkflowPath path = workflowService.startWorkflow(currentWorkflowDef.getId(), params); + out.println("started workflow id: " + path.getInstance().getId() + " , def: " + path.getInstance().getDefinition().getTitle()); currentPath = path; out.print(interpretCommand("show transitions")); } @@ -915,7 +921,7 @@ public class WorkflowInterpreter extends BaseInterpreter } } WorkflowTask task = workflowService.updateTask(command[2], params, null, null); - out.println("updated task id: " + command[2] + ", properties: " + task.properties.size()); + out.println("updated task id: " + command[2] + ", properties: " + task.getProperties().size()); } else { @@ -930,7 +936,7 @@ public class WorkflowInterpreter extends BaseInterpreter return "Syntax Error.\n"; } WorkflowPath path = workflowService.signal(command[1], (command.length == 3) ? command[2] : null); - out.println("signal sent - path id: " + path.id); + out.println("signal sent - path id: " + path.getId()); out.print(interpretCommand("show transitions")); } @@ -941,7 +947,7 @@ public class WorkflowInterpreter extends BaseInterpreter return "Syntax Error.\n"; } WorkflowPath path = workflowService.fireEvent(command[1], command[2]); - out.println("event " + command[2] + " fired - path id: " + path.id); + out.println("event " + command[2] + " fired - path id: " + path.getId()); out.print(interpretCommand("show transitions")); } @@ -954,12 +960,12 @@ public class WorkflowInterpreter extends BaseInterpreter if (command[1].equals("task")) { WorkflowTask task = workflowService.endTask(command[2], (command.length == 4) ? command[3] : null); - out.println("signal sent - path id: " + task.path.id); + out.println("signal sent - path id: " + task.getPath().getId()); out.print(interpretCommand("show transitions")); } else if (command[1].equals("workflow")) { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); if (workflowId == null) { return "Syntax Error. Workflow Id not specified.\n"; @@ -981,7 +987,7 @@ public class WorkflowInterpreter extends BaseInterpreter } if (command[1].equals("workflow")) { - String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.getInstance().getId(); if (workflowId == null) { return "Syntax Error. Workflow Id not specified.\n"; @@ -1005,11 +1011,11 @@ public class WorkflowInterpreter extends BaseInterpreter { for (WorkflowDefinition def : workflowService.getAllDefinitions()) { - List workflows = workflowService.getActiveWorkflows(def.id); + List workflows = workflowService.getActiveWorkflows(def.getId()); for (WorkflowInstance workflow : workflows) { - workflowService.deleteWorkflow(workflow.id); - out.println("workflow " + workflow.id + " deleted."); + workflowService.deleteWorkflow(workflow.getId()); + out.println("workflow " + workflow.getId() + " deleted."); } } } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowModel.java b/source/java/org/alfresco/repo/workflow/WorkflowModel.java index 7ef6099b83..6ba67299fd 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowModel.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowModel.java @@ -60,7 +60,7 @@ public interface WorkflowModel 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 + // Start task contstants static final QName TYPE_START_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "startTask"); static final QName PROP_WORKFLOW_DESCRIPTION = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowDescription"); static final QName PROP_WORKFLOW_PRIORITY = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowPriority"); @@ -70,6 +70,10 @@ public interface WorkflowModel static final QName ASSOC_GROUP_ASSIGNEE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "groupAssignee"); static final QName ASSOC_GROUP_ASSIGNEES = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "groupAssignees"); + // Activiti Task Constants + static final QName TYPE_ACTIVTI_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "activitiOutcomeTask"); + static final QName PROP_OUTCOME_PROPERTY_NAME= QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "outcomePropertyName"); + // workflow package static final QName ASPECT_WORKFLOW_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowPackage"); static final QName PROP_IS_SYSTEM_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "isSystemPackage"); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowNodeConverter.java b/source/java/org/alfresco/repo/workflow/WorkflowNodeConverter.java new file mode 100644 index 0000000000..f016b82818 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowNodeConverter.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @since 4.0 + * @author Nick Smith + */ +public interface WorkflowNodeConverter +{ + /** + * Converts a {@link NodeRef} into the appropriate Node type. + * @param node + * @return + */ + Object convertNode(NodeRef node); + + /** + * Converts a {@link NodeRef}. The return type is + * dependent on the value of isMany. If true then + * a {@link List} of the appropriate Node type is returned. Otherwise a + * single instance of the appropriate Node type is returned. + * + * @param values + * @param isMany + * @return + */ + Object convertNode(NodeRef value, boolean isMany); + + /** + * Converts a {@link Collection} of {@link NodeRef}s into a {@link List} of the appropriate Node type. + * @param values + * @return + */ + List convertNodes(Collection values); + + /** + * Converts a {@link Collection} of {@link NodeRef}s. The return type is + * dependent on the value of isMany. If true then + * a {@link List} of the appropriate Node type is returned. Otherwise a + * single instance of the appropriate Node type is returned. + * + * @param values + * @param isMany + * @return + */ + Object convertNodes(Collection values, boolean isMany); + + /** + * Converts a {@link Collection} of {@link NodeRef}s or a single {@link NodeRef}. The return type is + * dependent on the value of isMany. If true then + * a {@link List} of the appropriate Node type is returned. Otherwise a + * single instance of the appropriate Node type is returned. + + * @param value + * @param isMany + * @return + */ + Object convertNodes(Object value, boolean isMany); + + /** + * + * @param toConvert + * @return + */ + NodeRef convertToNode(Object toConvert); + + List convertToNodes(Collection toConvert); + + List convertToNodes(Object value); + + boolean isSupported(Object object); + + /** + * Converts the object to a {@link NodeRef} or a {@link List} of + * {@link NodeRef}s. The return type is dependant on the type of the object + * parameter. If the object parameter is a {@link Collection} then a + * {@link List} of {@link NodeRef}s is returned. Otherwise a single + * {@link NodeRef} is returned. + * + * @param object + * @return + */ + Serializable convert(Object object); + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java new file mode 100644 index 0000000000..df72c74ab1 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowNode; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; +import org.alfresco.service.cmr.workflow.WorkflowTransition; +import org.alfresco.service.namespace.QName; + +/** + * @since 3.5 + * @author Nick Smith + * + */ +public class WorkflowObjectFactory +{ + private final static String TITLE_LABEL = "title"; + private final static String DESC_LABEL = "description"; + + private final WorkflowQNameConverter qNameConverter; + private final TenantService tenantService; + private final MessageService messageService; + private final DictionaryService dictionaryService; + private final String engineId; + + public WorkflowObjectFactory(WorkflowQNameConverter qNameConverter, + TenantService tenantService, + MessageService messageService, + DictionaryService dictionaryService, + String engineId) + { + this.tenantService = tenantService; + this.messageService = messageService; + this.dictionaryService = dictionaryService; + this.engineId = engineId; + this.qNameConverter = qNameConverter; + } + + public String buildGlobalId(String localId) + { + return BPMEngineRegistry.createGlobalId(engineId, localId); + } + + public String getLocalEngineId(String globalId) + { + return BPMEngineRegistry.getLocalId(globalId); + } + + public boolean isGlobalId(String globalId) + { + return BPMEngineRegistry.isGlobalId(globalId, globalId); + } + + /** + * Create a new {@link WorkflowDeployment}. + * @param wfDef + * @param problems + * @return + */ + public WorkflowDeployment createDeployment(WorkflowDefinition wfDef, String... problems) + { + WorkflowDeployment wfDeployment = new WorkflowDeployment(wfDef, problems); + return wfDeployment; + } + + /** + * Create a new {@link WorkflowDefinition}. + * @param defId + * @param defName + * @param version + * @param defaultTitle + * @param startTaskDef + * @param processDef + * @return + */ + public WorkflowDefinition createDefinition(String defId, + String defName,int version, + String defaultTitle, String defaultDescription, + WorkflowTaskDefinition startTaskDef) + { + checkDomain(defName); + String actualId = buildGlobalId(defId); + + String actualName = getWorkflowDefinitionName(defName); + String actualVersion = Integer.toString(version); + + String baseName= tenantService.getBaseName(defName); + String displayId = baseName + ".workflow."; + String title = getLabel(displayId, TITLE_LABEL, defaultTitle); + String description = getLabel(displayId, DESC_LABEL, defaultDescription, title); + return new WorkflowDefinition( + actualId, actualName, actualVersion, title, description, startTaskDef); + } + + public String getWorkflowDefinitionName(String defName) + { + String baseName= tenantService.getBaseName(defName); + String actualName = buildGlobalId(baseName); + return actualName; + } + + public WorkflowInstance createInstance(String id, + WorkflowDefinition definition, Map variables, + boolean isActive, Date startDate, Date endDate) + { + String actualId = buildGlobalId(id); + checkDomain(getLocalEngineId(definition.getName())); + + String description = (String) getVariable(variables, WorkflowModel.PROP_WORKFLOW_DESCRIPTION); + + NodeRef initiator = null; + ScriptNode initiatorSN= (ScriptNode) getVariable(variables, WorkflowConstants.PROP_INITIATOR); + if(initiatorSN != null) + { + initiator = initiatorSN.getNodeRef(); + } + + NodeRef context = getNodeVariable(variables, WorkflowModel.PROP_CONTEXT); + NodeRef workflowPackage= getNodeVariable(variables, WorkflowModel.ASSOC_PACKAGE); + + WorkflowInstance workflowInstance = new WorkflowInstance( + actualId, definition, + description, initiator, + workflowPackage, context, + isActive, startDate, endDate); + + workflowInstance.priority = (Integer) getVariable(variables, WorkflowModel.PROP_WORKFLOW_PRIORITY); + Date dueDate = (Date) getVariable(variables, WorkflowModel.PROP_WORKFLOW_DUE_DATE); + if(dueDate != null) + { + workflowInstance.dueDate = dueDate; + } + + return workflowInstance; + } + + public WorkflowPath createPath(String id, + WorkflowInstance wfInstance, WorkflowNode node, + boolean isActive) + { + String actualId = buildGlobalId(id); + return new WorkflowPath(actualId, wfInstance, node, isActive); + } + + public WorkflowNode createNode(String name, + String definitionName, String defaultTitle, + String defaultDescription, String type, + boolean isTaskNode, WorkflowTransition... transitions) + { + String displayId = definitionName + ".node."+name; + String title = getLabel(displayId, TITLE_LABEL, defaultTitle); + String description = getLabel(displayId, DESC_LABEL, defaultDescription, title); + return new WorkflowNode(name, + title, description, type, + isTaskNode, transitions); + } + + public WorkflowTaskDefinition createTaskDefinition(String id, WorkflowNode node, String typeName, boolean isStart) + { + TypeDefinition metaData = getTaskTypeDefinition(typeName, isStart); + if(id == null) + { + id = qNameConverter.mapQNameToName(metaData.getName()); + } + return new WorkflowTaskDefinition(id, node, metaData); + } + + public WorkflowTask createTask (String id, + WorkflowTaskDefinition taskDef, String name, + String defaultTitle, String defaultDescription, + WorkflowTaskState state, WorkflowPath path, + Map properties) + { + String defName = path.getInstance().getDefinition().getName(); + String processKey = getLocalEngineId(defName); + checkDomain(processKey); + + String actualId = buildGlobalId(id); + String displayId = processKey + ".task." + name; + TypeDefinition metadata = taskDef.getMetadata(); + String title = getLabel(displayId, TITLE_LABEL, defaultTitle, metadata.getTitle(), name); + String description = getLabel(displayId, DESC_LABEL, defaultDescription, metadata.getDescription(), title); + return new WorkflowTask(actualId, + taskDef, name, title, description, + state, path, properties); + } + + public WorkflowTimer createWorkflowTimer(String id, String name, String error, + Date dueDate, WorkflowPath workflowPath, WorkflowTask workflowTask) + { + String actualId = buildGlobalId(id); + return new WorkflowTimer(actualId, name, workflowPath, workflowTask, dueDate, error); + } + + public String getTaskTitle(TypeDefinition typeDefinition, String defName, String defaultTitle, String name) + { + String processKey = defName; + if(isGlobalId(defName)) + { + processKey = getLocalEngineId(defName); + } + String displayId = processKey + ".task." + name; + return getLabel(displayId, TITLE_LABEL, defaultTitle, typeDefinition.getTitle(), name); + } + + public String getTaskDescription(TypeDefinition typeDefinition, String defName, String defaultDescription, String title) + { + String processKey = defName; + if(isGlobalId(defName)) + { + processKey = getLocalEngineId(defName); + } + String displayId = processKey + ".task." + title; + return getLabel(displayId, DESC_LABEL, defaultDescription, typeDefinition.getTitle(), title); + } + + + /** + * Get an I18N Label for a workflow item + * + * @param displayId message resource id lookup + * @param labelKey label to lookup (title or description) + * @param defaultLabel default value if not found in message resource bundle + * @return the label + */ + private String getLabel(String displayId, String labelKey, String... defaults) + { + String keyBase = displayId.replace(":", "_"); + String key = keyBase+ "." + labelKey; + String label = messageService.getMessage(key); + int i = 0; + while(label==null && i variables, QName qName) + { + Object obj = getVariable(variables, qName); + if (obj==null) + { + return null; + } + if(obj instanceof ScriptNode) + { + ScriptNode scriptNode = (ScriptNode) obj; + return scriptNode.getNodeRef(); + } + String message = "Variable "+qName+" should be of type ScriptNode but was "+obj.getClass(); + throw new WorkflowException(message); + } + + private Object getVariable(Map variables, QName qName) + { + if(variables == null || qName == null) + return null; + String varName = qNameConverter.mapQNameToName(qName); + return variables.get(varName); + } + + private Object getVariable(Map variables, String key) + { + if(variables == null || key == null) + return null; + return variables.get(key); + } + + /** + * Throws exception if domain mismatch + * @param definitionKey + */ + private void checkDomain(String definitionKey) + { + if (tenantService.isEnabled()) + { + + tenantService.checkDomain(definitionKey); + } + } + + /** + * Returns an anonymous {@link TypeDefinition} for the given name with all + * the mandatory aspects applied. + * + * @param name + * the name of the task definition. + * @param isStart + * is theis a start task? + * @return the task {@link TypeDefinition}. + */ + public TypeDefinition getTaskFullTypeDefinition(String name, boolean isStart) + { + TypeDefinition typeDef = getTaskTypeDefinition(name, isStart); + return dictionaryService.getAnonymousType(typeDef.getName()); + } + + /** + * Gets the Task {@link TypeDefinition} for the given name. + * + * @param name the name of the task definition. + * @param isStart is theis a start task? + * @return the task {@link TypeDefinition}. + */ + public TypeDefinition getTaskTypeDefinition(String name, boolean isStart) + { + TypeDefinition typeDef = null; + if(name!=null) + { + QName typeName = qNameConverter.mapNameToQName(name); + typeDef = dictionaryService.getType(typeName); + } + if (typeDef == null) + { + QName defaultTypeName = isStart? WorkflowModel.TYPE_START_TASK : WorkflowModel.TYPE_WORKFLOW_TASK; + typeDef = dictionaryService.getType(defaultTypeName); + if (typeDef == null) + { + String msg = messageService.getMessage("workflow.get.task.definition.metadata.error", name); + throw new WorkflowException( msg); + } + } + return typeDef; + } + + /** + * Map QName to jBPM variable name + * + * @param name QName + * @return jBPM variable name + */ + public String mapQNameToName(QName name) + { + return qNameConverter.mapQNameToName(name); + } + + /** + * Map QName to jBPM variable name + * + * @param name QName + * @return jBPM variable name + */ + public QName mapNameToQName(String name) + { + return qNameConverter.mapNameToQName(name); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPackageComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowPackageComponent.java index c939bd0091..df7d18ef99 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowPackageComponent.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowPackageComponent.java @@ -21,6 +21,7 @@ package org.alfresco.repo.workflow; import java.util.List; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowInstance; /** @@ -61,5 +62,17 @@ public interface WorkflowPackageComponent * @return list of workflows which act upon the specified content */ public List getWorkflowIdsForContent(NodeRef packageItem); - + + /** + * Initialises the workflow package node on the {@link WorkflowInstance}, + * adding the appropriate aspect and setting the appropriate properties to + * mark it as a package for the given {@link WorkflowInstance}. + * + * @param instance + * the workflow instance to which the package belongs. + * + * @return true if the package node was modified. + */ + public boolean setWorkflowForPackage(WorkflowInstance instance); + } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java index 9cbc56effe..39bb1ae86f 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java @@ -19,10 +19,12 @@ package org.alfresco.repo.workflow; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.alfresco.model.ContentModel; +import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.importer.ImporterBootstrap; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -31,6 +33,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; @@ -45,6 +48,7 @@ import org.springframework.extensions.surf.util.ParameterCheck; public class WorkflowPackageImpl implements WorkflowPackageComponent { private final static String PACKAGE_FOLDER = "packages"; + private static final String ERR_PACKAGE_ALREADY_ASSOCIATED = "workflow.package.already.associated.error"; // service dependencies private ImporterBootstrap bootstrap; @@ -54,7 +58,8 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent private PermissionService permissionService; private NodeRef systemWorkflowContainer = null; private TenantService tenantService; - + private MessageService messageService; + /** * @param bootstrap the importer bootstrap for the store to place workflow * items into @@ -100,12 +105,17 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent { this.tenantService = tenantService; } + + /** + * @param messageService the messageService to set + */ + public void setMessageService(MessageService messageService) + { + this.messageService = messageService; + } - /* - * (non-Javadoc) - * @see - * org.alfresco.repo.workflow.WorkflowPackageComponent#createPackage(org - * .alfresco.service.cmr.repository.NodeRef) + /** + * {@inheritDoc} */ public NodeRef createPackage(NodeRef container) { @@ -173,11 +183,8 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent } } - /* - * (non-Javadoc) - * @see - * org.alfresco.repo.workflow.WorkflowPackageComponent#deletePackage(org - * .alfresco.service.cmr.repository.NodeRef) + /** + * {@inheritDoc} */ public void deletePackage(NodeRef container) { @@ -197,11 +204,8 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent } } - /* - * (non-Javadoc) - * @see - * org.alfresco.repo.workflow.WorkflowPackageComponent#getWorkflowIdsForContent - * (org.alfresco.service.cmr.repository.NodeRef, boolean) + /** + * {@inheritDoc} */ public List getWorkflowIdsForContent(NodeRef packageItem) { @@ -270,24 +274,24 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent if (path == null) { throw new WorkflowException( "Unable to locate workflow system container - path not specified"); } List nodeRefs = searchService.selectNodes(systemContainer, path, null, namespaceService, false); + NodeRef result = null; + if (nodeRefs != null && nodeRefs.size() > 0) + { + result = nodeRefs.get(0); + } - if (tenantService.isEnabled()) + if (tenantService.isEnabled() == false) { - NodeRef tenantSystemWorkflowContainer = null; - if (nodeRefs != null && nodeRefs.size() > 0) + if(result == null) { - tenantSystemWorkflowContainer = nodeRefs.get(0); + result = systemWorkflowContainer; } - return tenantSystemWorkflowContainer; - } - else - { - if (nodeRefs != null && nodeRefs.size() > 0) + else { - systemWorkflowContainer = nodeRefs.get(0); + systemWorkflowContainer = result; } - return systemWorkflowContainer; } + return result; } /** @@ -314,16 +318,51 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent public NodeRef createSystemWorkflowContainer() { NodeRef systemContainer = findSystemContainer(); - NodeRef systemWorkflowContainer = findSystemWorkflowContainer(systemContainer); - if (systemWorkflowContainer == null) + NodeRef systemWfContainer = findSystemWorkflowContainer(systemContainer); + if (systemWfContainer == null) { String name = bootstrap.getConfiguration().getProperty("system.workflow_container.childname"); QName qname = QName.createQName(name, namespaceService); ChildAssociationRef childRef = nodeService.createNode(systemContainer, ContentModel.ASSOC_CHILDREN, qname, ContentModel.TYPE_CONTAINER); - systemWorkflowContainer = childRef.getChildRef(); + systemWfContainer = childRef.getChildRef(); } - return systemWorkflowContainer; + return systemWfContainer; + } + + /** + * {@inheritDoc} + */ + public boolean setWorkflowForPackage(WorkflowInstance instance) + { + NodeRef packageNode = instance.getWorkflowPackage(); + if(packageNode==null) + return false; + + Serializable pckgInstanceId = nodeService.getProperty(packageNode, WorkflowModel.PROP_WORKFLOW_INSTANCE_ID); + if(pckgInstanceId != null) + { + if(pckgInstanceId.equals(instance.getId())) + { + return false; + } + String msg = messageService.getMessage(ERR_PACKAGE_ALREADY_ASSOCIATED, packageNode, + instance.getId(), pckgInstanceId); + throw new WorkflowException(msg); + } + + if (nodeService.hasAspect(packageNode, WorkflowModel.ASPECT_WORKFLOW_PACKAGE)==false) + { + createPackage(packageNode); + } + + String definitionId = instance.getDefinition().getId(); + String definitionName = instance.getDefinition().getName(); + String instanceId = instance.getId(); + nodeService.setProperty(packageNode, WorkflowModel.PROP_WORKFLOW_DEFINITION_ID, definitionId); + nodeService.setProperty(packageNode, WorkflowModel.PROP_WORKFLOW_DEFINITION_NAME, definitionName); + nodeService.setProperty(packageNode, WorkflowModel.PROP_WORKFLOW_INSTANCE_ID, instanceId); + return true; } } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPropertyConverter.java b/source/java/org/alfresco/repo/workflow/WorkflowPropertyConverter.java new file mode 100644 index 0000000000..79d8f38a42 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowPropertyConverter.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; + +/** + * @since 3.5 + * @author Nick Smith + * + */ +public class WorkflowPropertyConverter +{ + public static final Object SKIP = new Object(); + + private final WorkflowObjectFactory factory; + private final NodeConverter converter; + + + public WorkflowPropertyConverter(WorkflowObjectFactory factory, NodeConverter converter) + { + this.factory = factory; + this.converter = converter; + } + + /** + * Sets Properties of Task + * + * @param instance + * task instance + * @param properties + * properties to set + */ + public Map getStringProperties(TypeDefinition taskDef, Map properties) + { + if (properties == null) + { + return null; + } + + Map results = new HashMap(properties.size()); + + Map taskProperties = taskDef.getProperties(); + Map taskAssocs = taskDef.getAssociations(); + + // map each parameter to task + for (Entry entry : properties.entrySet()) + { + QName key = entry.getKey(); + Serializable value = entry.getValue(); + Object actualValue = getValue(key, value, taskProperties, taskAssocs); + + String name = factory.mapQNameToName(key); + results.put(name, actualValue); + } + return results; + } + + private Object getValue(QName key, Serializable value, + Map taskProperties, + Map taskAssocs) + { + + PropertyDefinition propDef = taskProperties.get(key); + if (propDef != null) + { + return getPropertyValue(key, value, propDef); + } + AssociationDefinition assocDef = taskAssocs.get(key); + if (assocDef != null) + { + return getAssociation(key, value, assocDef); + } + // untyped value, perform minimal conversion + if (value instanceof NodeRef) + { + return convertNodeRefs(false, value); + } + return value; + } + + private Object getAssociation(QName key, Serializable value, AssociationDefinition assocDef) + { + return convertNodeRefs(assocDef.isTargetMany(), value); + } + + private Object getPropertyValue(QName key, Serializable value, PropertyDefinition propDef) + { + if (propDef.isProtected()) + { + // NOTE: only write non-protected properties + return SKIP; + } + + Object result = null; + DataTypeDefinition dataTypeDef = propDef.getDataType(); + if (value instanceof Collection) + { + result = DefaultTypeConverter.INSTANCE.convert(dataTypeDef, (Collection) value); + } + else + { + result = DefaultTypeConverter.INSTANCE.convert(dataTypeDef, value); + } + // convert NodeRefs to JBPMNodes + if (dataTypeDef.getName().equals(DataTypeDefinition.NODE_REF)) + { + result = convertNodeRefs(propDef.isMultiValued(), result); + } + return result; + } + + /** + * Convert a Repository association to JBPMNodeList or JBPMNode + * + * @param isMany + * true => force conversion to list + * @param value + * value to convert + * @return JBPMNodeList or JBPMNode + */ + @SuppressWarnings("unchecked") + private Object convertNodeRefs(boolean isMany, Object value) + { + if(value instanceof NodeRef) + { + NodeRef node = (NodeRef) value; + if (isMany) + { + return converter.convertNodes(Collections.singletonList(node)); + } + else + { + return converter.convertNode(node); + } + } + if (value instanceof Collection) + { + Collection nodes = (Collection) value; + if(isMany) + { + converter.convertNodes(nodes); + } + else + { + NodeRef node = nodes.size()==0?null:nodes.iterator().next(); + converter.convertNode(node); + } + } + return value; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPropertyHandler.java b/source/java/org/alfresco/repo/workflow/WorkflowPropertyHandler.java new file mode 100644 index 0000000000..c7acb2e7e5 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowPropertyHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.Serializable; + +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public interface WorkflowPropertyHandler +{ + static final String DO_NOT_ADD = "*£$DO NOT ADD THIS PROPERTY $£*"; + + Object handleProperty(QName key, Serializable value, TypeDefinition type, Object object, Class objectType); +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPropertyHandlerRegistry.java b/source/java/org/alfresco/repo/workflow/WorkflowPropertyHandlerRegistry.java new file mode 100644 index 0000000000..4731e4fa60 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowPropertyHandlerRegistry.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class WorkflowPropertyHandlerRegistry +{ + private final Map handlers = new HashMap(); + + private final WorkflowPropertyHandler defaultHandler; + private final WorkflowQNameConverter qNameConverter; + + /** + * @param defaultHandler + * @param qNameConverter + */ + public WorkflowPropertyHandlerRegistry(WorkflowPropertyHandler defaultHandler, WorkflowQNameConverter qNameConverter) + { + this.defaultHandler = defaultHandler; + this.qNameConverter = qNameConverter; + } + + public void registerHandler(QName key, WorkflowPropertyHandler handler) + { + handlers.put(key, handler); + } + + public void clear() + { + handlers.clear(); + } + + public Map handleVariablesToSet(Map properties, + TypeDefinition type, + Object object, Class objectType) + { + Map variablesToSet = new HashMap(); + for (Entry entry : properties.entrySet()) + { + QName key = entry.getKey(); + Serializable value = entry.getValue(); + WorkflowPropertyHandler handler = handlers.get(key); + if(handler == null) + { + handler = defaultHandler; + } + Object result = handler.handleProperty(key, value, type, object, objectType); + if(WorkflowPropertyHandler.DO_NOT_ADD.equals(result)==false) + { + String keyStr = qNameConverter.mapQNameToName(key); + variablesToSet.put(keyStr, result); + } + } + return variablesToSet; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowQNameConverter.java b/source/java/org/alfresco/repo/workflow/WorkflowQNameConverter.java new file mode 100644 index 0000000000..9ce8c01a3f --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowQNameConverter.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow; + +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class WorkflowQNameConverter +{ + private final NamespacePrefixResolver prefixResolver; + + public WorkflowQNameConverter(NamespacePrefixResolver prefixResolver) + { + this.prefixResolver = prefixResolver; + } + + /** + * Map QName to jBPM variable name + * + * @param name QName + * @return jBPM variable name + */ + public String mapQNameToName(QName name) + { + // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as new scheme (i.e. } -> _) + // NOTE: Use new scheme + String nameStr = name.toPrefixString(prefixResolver); + if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':')) + { + return nameStr.replace(':', '}'); + } + return nameStr.replace(':', '_'); + } + + /** + * Map QName to jBPM variable name + * + * @param name QName + * @return jBPM variable name + */ + public QName mapNameToQName(String name) + { + if(name.indexOf(QName.NAMESPACE_BEGIN)==0) + { + return QName.createQName(name); + } + String qName = name; + if(name.indexOf(QName.NAMESPACE_PREFIX)==-1) + { + qName = name.replaceFirst("_", ":"); + } + return QName.createQName(qName, prefixResolver); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 1cdd594ec9..df9a4481a8 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -169,11 +169,11 @@ public class WorkflowServiceImpl implements WorkflowService WorkflowComponent component = getWorkflowComponent(engineId); WorkflowDeployment deployment = component.deployDefinition(workflowDefinition, mimetype); - if (logger.isDebugEnabled() && deployment.problems.length > 0) + if (logger.isDebugEnabled() && deployment.getProblems().length > 0) { - for (String problem : deployment.problems) + for (String problem : deployment.getProblems()) { - logger.debug("Workflow definition '" + deployment.definition.title + "' problem: " + problem); + logger.debug("Workflow definition '" + deployment.getDefinition().getTitle() + "' problem: " + problem); } } @@ -354,7 +354,13 @@ public class WorkflowServiceImpl implements WorkflowService { String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId); WorkflowComponent component = getWorkflowComponent(engineId); - return component.startWorkflow(workflowDefinitionId, parameters); + WorkflowPath path = component.startWorkflow(workflowDefinitionId, parameters); + if(parameters!=null && parameters.containsKey(WorkflowModel.ASSOC_PACKAGE)) + { + WorkflowInstance instance = path.getInstance(); + workflowPackageComponent.setWorkflowForPackage(instance); + } + return path; } /* @@ -461,7 +467,7 @@ public class WorkflowServiceImpl implements WorkflowService // NOTE: Delete workflow package after cancelling workflow, so it's // still available // in process-end events of workflow definition - workflowPackageComponent.deletePackage(instance.workflowPackage); + workflowPackageComponent.deletePackage(instance.getWorkflowPackage()); return instance; } @@ -479,7 +485,7 @@ public class WorkflowServiceImpl implements WorkflowService // NOTE: Delete workflow package after deleting workflow, so it's still // available // in process-end events of workflow definition - workflowPackageComponent.deletePackage(instance.workflowPackage); + workflowPackageComponent.deletePackage(instance.getWorkflowPackage()); return instance; } @@ -535,6 +541,18 @@ public class WorkflowServiceImpl implements WorkflowService return component.getTasksForWorkflowPath(pathId); } + /** + * {@inheritDoc} + */ + + @Override + public WorkflowTask getStartTask(String workflowInstanceId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowInstanceId); + TaskComponent component = getTaskComponent(engineId); + return component.getStartTask(workflowInstanceId); + } + /* * (non-Javadoc) * @see @@ -632,7 +650,13 @@ public class WorkflowServiceImpl implements WorkflowService { String engineId = BPMEngineRegistry.getEngineId(taskId); TaskComponent component = getTaskComponent(engineId); - return component.updateTask(taskId, properties, add, remove); + WorkflowTask task = component.updateTask(taskId, properties, add, remove); + if(add!=null && add.containsKey(WorkflowModel.ASSOC_PACKAGE)) + { + WorkflowInstance instance = task.getPath().getInstance(); + workflowPackageComponent.setWorkflowForPackage(instance); + } + return task; } /* @@ -817,7 +841,7 @@ public class WorkflowServiceImpl implements WorkflowService String engineId = BPMEngineRegistry.getEngineId(workflowId); WorkflowComponent component = getWorkflowComponent(engineId); WorkflowInstance instance = component.getWorkflowById(workflowId); - if (instance != null && instance.active == active) + if (instance != null && instance.isActive() == active) { workflowInstances.add(instance); } @@ -881,7 +905,7 @@ public class WorkflowServiceImpl implements WorkflowService private NodeRef getWorkflowPackageIfExists(String taskId) { WorkflowTask workflowTask = getTaskById(taskId); - if (workflowTask != null) { return (NodeRef) workflowTask.properties.get(WorkflowModel.ASSOC_PACKAGE); } + if (workflowTask != null) { return (NodeRef) workflowTask.getProperties().get(WorkflowModel.ASSOC_PACKAGE); } return null; } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java deleted file mode 100644 index 0d37e7a580..0000000000 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.workflow; - -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.person.TestPersonManager; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.MutableAuthenticationService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowInstance; -import org.alfresco.service.cmr.workflow.WorkflowPath; -import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.cmr.workflow.WorkflowTask; -import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; -import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; -import org.alfresco.service.cmr.workflow.WorkflowTaskState; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.BaseSpringTest; -import org.alfresco.util.GUID; - - -/** - * Workflow Service Implementation Tests - * - * @author davidc - */ -public class WorkflowServiceImplTest extends BaseSpringTest -{ - private final static String USER1 = "WFUser1" + GUID.generate(); - private final static String USER2 = "WFUser2" + GUID.generate(); - private final static String USER3 = "WFUser3" + GUID.generate(); - private final static String GROUP = "WFGroup" + GUID.generate(); - private final static String SUB_GROUP = "WFSubGroup" + GUID.generate(); - - private WorkflowService workflowService; - private NodeService nodeService; - private SearchService searchService; - private AuthenticationComponent authenticationComponent; - private MutableAuthenticationService authenticationService; - private AuthorityService authorityService; - private PersonService personService; - private TestPersonManager personManager; - - //@Override - @SuppressWarnings("deprecation") - protected void onSetUpInTransaction() throws Exception - { - workflowService = (WorkflowService)applicationContext.getBean(ServiceRegistry.WORKFLOW_SERVICE.getLocalName()); - nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); - searchService = (SearchService)applicationContext.getBean(ServiceRegistry.SEARCH_SERVICE.getLocalName()); - authenticationService = (MutableAuthenticationService) applicationContext.getBean(ServiceRegistry.AUTHENTICATION_SERVICE.getLocalName()); - authorityService = (AuthorityService) applicationContext.getBean(ServiceRegistry.AUTHORITY_SERVICE.getLocalName()); - personService = (PersonService) applicationContext.getBean(ServiceRegistry.PERSON_SERVICE.getLocalName()); - - // authenticate - authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); - authenticationComponent.setSystemUserAsCurrentUser(); - - // create test users - personManager = new TestPersonManager(authenticationService, personService, nodeService); - personManager.createPerson(USER1); - personManager.createPerson(USER2); - personManager.createPerson(USER3); - - // create test groups - createGroup(null, GROUP); - createGroup(GROUP, SUB_GROUP); - - // add users to groups - addUserToGroup(GROUP, USER1); - addUserToGroup(SUB_GROUP, USER2); - } - - @SuppressWarnings("deprecation") - @Override - protected void onTearDownInTransaction() throws Exception - { - super.onTearDownInTransaction(); - - authenticationComponent.setSystemUserAsCurrentUser(); - deleteGroup(SUB_GROUP); - deleteGroup(GROUP); - personManager.clearPeople(); - } - - protected void createGroup(String parentGroupShortName, String groupShortName) - { - if (parentGroupShortName != null) - { - String parentGroupFullName = authorityService.getName(AuthorityType.GROUP, parentGroupShortName); - if (authorityService.authorityExists(parentGroupFullName)) - { - authorityService.createAuthority(AuthorityType.GROUP, groupShortName, groupShortName, null); - - String groupFullName = authorityService.getName(AuthorityType.GROUP, groupShortName); - authorityService.addAuthority(parentGroupFullName, groupFullName); - } - } - else - { - authorityService.createAuthority(AuthorityType.GROUP, groupShortName, groupShortName, null); - } - } - - protected void addUserToGroup(String groupName, String userName) - { - // get the full name for the group - String fullGroupName = this.authorityService.getName(AuthorityType.GROUP, groupName); - - // create group if it does not exist - if (this.authorityService.authorityExists(fullGroupName) == false) - { - this.authorityService.createAuthority(AuthorityType.GROUP, fullGroupName); - } - - // add the user to the group - this.authorityService.addAuthority(fullGroupName, userName); - } - - protected void deleteGroup(String groupShortName) - { - String groupFullName = authorityService.getName(AuthorityType.GROUP, groupShortName); - if (authorityService.authorityExists(groupFullName) == true) - { - authorityService.deleteAuthority(groupFullName); - } - } - - protected NodeRef findGroup(String shortGroupName) - { - NodeRef group = null; - - String query = "+TYPE:\"cm:authorityContainer\" AND @cm\\:authorityName:*" + shortGroupName; - - ResultSet results = null; - try - { - results = this.searchService.query( - new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"), - SearchService.LANGUAGE_LUCENE, query); - - if (results.length() > 0) - { - group = results.getNodeRefs().get(0); - } - } - finally - { - if (results != null) - { - results.close(); - } - } - - return group; - } - - public void testGetWorkflowDefinitions() - { - List workflowDefs = workflowService.getDefinitions(); - assertNotNull(workflowDefs); - assertTrue(workflowDefs.size() > 0); - } - - public void testStartWorkflow() - { - List workflowDefs = workflowService.getDefinitions(); - assertNotNull(workflowDefs); - assertTrue(workflowDefs.size() > 0); - WorkflowDefinition workflowDef = workflowDefs.get(0); - WorkflowPath path = workflowService.startWorkflow(workflowDef.id, null); - assertNotNull(path); - assertTrue(path.active); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.id, path.instance.definition.id); - } - - public void testWorkflowPackage() - { - NodeRef nodeRef = workflowService.createPackage(null); - assertNotNull(nodeRef); - assertTrue(nodeService.hasAspect(nodeRef, WorkflowModel.ASPECT_WORKFLOW_PACKAGE)); - } - - public void testQueryTasks() - { - WorkflowTaskQuery filter = new WorkflowTaskQuery(); - filter.setTaskName(QName.createQName("{http://www.alfresco.org/model/wcmworkflow/1.0}submitpendingTask")); - filter.setTaskState(WorkflowTaskState.COMPLETED); - Map taskProps = new HashMap(); - taskProps.put(QName.createQName("{http://www.alfresco.org/model/bpm/1.0}workflowDescription"), "Test5"); - filter.setTaskCustomProps(taskProps); - filter.setProcessId("jbpm$48"); - filter.setProcessName(QName.createQName("{http://www.alfresco.org/model/wcmworkflow/1.0}submit")); - Map procProps = new HashMap(); - procProps.put(QName.createQName("{http://www.alfresco.org/model/bpm/1.0}workflowDescription"), "Test5"); - procProps.put(QName.createQName("companyhome"), new NodeRef("workspace://SpacesStore/3df8a9d0-ff04-11db-98da-a3c3f3149ea5")); - filter.setProcessCustomProps(procProps); - filter.setOrderBy(new WorkflowTaskQuery.OrderBy[] { WorkflowTaskQuery.OrderBy.TaskName_Asc, WorkflowTaskQuery.OrderBy.TaskState_Asc }); - List tasks = workflowService.queryTasks(filter); - System.out.println("Found " + tasks.size() + " tasks."); - for (WorkflowTask task : tasks) - { - System.out.println(task.toString()); - } - } - - public void testAssociateWorkflowPackage() - { - // create workflow package - NodeRef rootRef = nodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore")); - NodeRef nodeRef = workflowService.createPackage(null); - assertNotNull(nodeRef); - assertTrue(nodeService.hasAspect(nodeRef, WorkflowModel.ASPECT_WORKFLOW_PACKAGE)); - ChildAssociationRef childAssoc = nodeService.createNode(rootRef, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "test"), ContentModel.TYPE_CONTENT, null); - - List exisingInstances = workflowService.getWorkflowsForContent(childAssoc.getChildRef(), true); - int size = 0; - if (exisingInstances != null) - { - size = exisingInstances.size(); - } - - nodeService.addChild(nodeRef, childAssoc.getChildRef(), ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "test123")); - - // start workflow - List workflowDefs = workflowService.getDefinitions(); - assertNotNull(workflowDefs); - assertTrue(workflowDefs.size() > 0); - WorkflowDefinition workflowDef = workflowDefs.get(0); - Map parameters = new HashMap(); - parameters.put(WorkflowModel.ASSOC_PACKAGE, nodeRef); - WorkflowPath path = workflowService.startWorkflow(workflowDef.id, parameters); - assertNotNull(path); - assertTrue(path.active); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.id, path.instance.definition.id); - String workflowDefId = (String)nodeService.getProperty(nodeRef, WorkflowModel.PROP_WORKFLOW_DEFINITION_ID); - assertEquals(workflowDefId, workflowDef.id); - String workflowDefName = (String)nodeService.getProperty(nodeRef, WorkflowModel.PROP_WORKFLOW_DEFINITION_NAME); - assertEquals(workflowDefName, workflowDef.name); - String workflowInstanceId = (String)nodeService.getProperty(nodeRef, WorkflowModel.PROP_WORKFLOW_INSTANCE_ID); - assertEquals(workflowInstanceId, path.instance.id); - - // get workflows for content - List instances = workflowService.getWorkflowsForContent(childAssoc.getChildRef(), true); - assertNotNull(instances); - assertEquals(size + 1, instances.size()); - - for (WorkflowInstance instance : instances) - { - boolean fNew = true; - for (WorkflowInstance exisingInstance : exisingInstances) - { - if (instance.id.equals(exisingInstance.id)) - { - fNew = false; - break; - } - fNew = true; - break; - } - - if (fNew) - { - assertEquals(instance.id, path.instance.id); - } - - } - - List completedInstances = workflowService.getWorkflowsForContent(childAssoc.getChildRef(), false); - assertNotNull(completedInstances); - assertEquals(0, completedInstances.size()); - } - - public void testGetWorkflowTaskDefinitions() - { - List workflowDefs = workflowService.getDefinitions(); - assertNotNull(workflowDefs); - assertTrue(workflowDefs.size() > 0); - - for (WorkflowDefinition workflowDef : workflowDefs) - { - List workflowTaskDefs = workflowService.getTaskDefinitions(workflowDef.getId()); - assertNotNull(workflowTaskDefs); - assertTrue(workflowTaskDefs.size() > 0); - } - } - - public void testTaskCapabilities() - { - // start Adhoc workflow as USER1 and assign to USER2 - personManager.setUser(USER1); - - // Get the workflow definition. - WorkflowDefinition workflowDef = this.workflowService.getDefinitionByName("jbpm$wf:adhoc"); - assertNotNull(workflowDef); - - // Create workflow parameters - Map params = new HashMap(); - Serializable wfPackage = workflowService.createPackage(null); - params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); - Date dueDate = new Date(); - params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); - params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); - NodeRef assignee = personService.getPerson(USER2); - params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); - - // Start a workflow instance - WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); - assertNotNull(path); - assertTrue(path.isActive()); - String workflowInstanceId = path.getInstance().getId(); - - // End start task to progress workflow - List tasks = workflowService.getTasksForWorkflowPath(path.getId()); - assertEquals(1, tasks.size()); - WorkflowTask startTask = tasks.get(0); - String startTaskId = startTask.getId(); - workflowService.endTask(startTaskId, null); - - // Fetch start task and check capabilities - startTask = workflowService.getTaskById(startTaskId); - assertNotNull(startTask); - assertEquals(startTask.getState(), WorkflowTaskState.COMPLETED); - - // check nothing can be done to the task as its completed - assertFalse(workflowService.isTaskClaimable(startTask, USER1)); - assertFalse(workflowService.isTaskEditable(startTask, USER1)); - assertFalse(workflowService.isTaskReassignable(startTask, USER1)); - assertFalse(workflowService.isTaskReleasable(startTask, USER1)); - - // Fetch the current task in the workflow - List paths = workflowService.getWorkflowPaths(workflowInstanceId); - assertNotNull(paths); - assertEquals(1, paths.size()); - tasks = workflowService.getTasksForWorkflowPath(path.getId()); - assertEquals(1, tasks.size()); - WorkflowTask currentTask = tasks.get(0); - assertEquals(currentTask.getState(), WorkflowTaskState.IN_PROGRESS); - assertEquals(currentTask.getProperties().get(ContentModel.PROP_OWNER), USER2); - - // check the task is not claimable or releasable by any user as it is not a pooled task - assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); - assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); - assertFalse(workflowService.isTaskClaimable(currentTask, USER3)); - assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); - assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); - assertFalse(workflowService.isTaskReleasable(currentTask, USER3)); - - // user1 (initiator) and user2 (owner) should be able to edit and reassign task - assertTrue(workflowService.isTaskEditable(currentTask, USER1)); - assertTrue(workflowService.isTaskEditable(currentTask, USER2)); - assertTrue(workflowService.isTaskReassignable(currentTask, USER1)); - assertTrue(workflowService.isTaskReassignable(currentTask, USER2)); - - // user3 should not be able to edit or reassign task - assertFalse(workflowService.isTaskEditable(currentTask, USER3)); - assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); - - // cancel the workflow - workflowService.cancelWorkflow(workflowInstanceId); - assertNull(workflowService.getWorkflowById(workflowInstanceId)); - } - - public void testPooledTaskCapabilities() - { - // make admin current user - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - - // start pooled review and approve workflow - WorkflowDefinition workflowDef = this.workflowService.getDefinitionByName("jbpm$wf:reviewpooled"); - assertNotNull(workflowDef); - - // Create workflow parameters - Map params = new HashMap(); - Serializable wfPackage = workflowService.createPackage(null); - params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); - Date dueDate = new Date(); - params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); - params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); - NodeRef group = findGroup(GROUP); - assertNotNull(group); - params.put(WorkflowModel.ASSOC_GROUP_ASSIGNEE, group); - - // Start a workflow instance - WorkflowPath path = workflowService.startWorkflow(workflowDef.getId(), params); - assertNotNull(path); - assertTrue(path.isActive()); - String workflowInstanceId = path.getInstance().getId(); - - // End start task to progress workflow - List tasks = workflowService.getTasksForWorkflowPath(path.getId()); - assertEquals(1, tasks.size()); - WorkflowTask startTask = tasks.get(0); - String startTaskId = startTask.getId(); - workflowService.endTask(startTaskId, null); - - // Fetch the current task in the workflow - List paths = workflowService.getWorkflowPaths(workflowInstanceId); - assertNotNull(paths); - assertEquals(1, paths.size()); - tasks = workflowService.getTasksForWorkflowPath(path.getId()); - assertEquals(1, tasks.size()); - WorkflowTask currentTask = tasks.get(0); - assertEquals(currentTask.getState(), WorkflowTaskState.IN_PROGRESS); - assertNull(currentTask.getProperties().get(ContentModel.PROP_OWNER)); - - // ensure the task is not reassignable by any user - assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); - assertFalse(workflowService.isTaskReassignable(currentTask, USER2)); - assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); - - // ensure the task is not releasable by any user - assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); - assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); - assertFalse(workflowService.isTaskReleasable(currentTask, USER3)); - - // ensure the task is claimable by the members of the group and sub group - assertTrue(workflowService.isTaskClaimable(currentTask, USER1)); - assertTrue(workflowService.isTaskClaimable(currentTask, USER2)); - - // ensure the task is not claimable by members outside of the group - assertFalse(workflowService.isTaskClaimable(currentTask, USER3)); - - // ensure the task can be edited - assertTrue(workflowService.isTaskEditable(currentTask, USER1)); - assertTrue(workflowService.isTaskEditable(currentTask, USER2)); - assertFalse(workflowService.isTaskEditable(currentTask, USER3)); - - // claim the task for USER1 - Map properties = new HashMap(8); - properties.put(ContentModel.PROP_OWNER, USER1); - workflowService.updateTask(currentTask.getId(), properties, null, null); - currentTask = workflowService.getTaskById(currentTask.getId()); - - // check flags are correct now USER1 is the owner - assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); - assertTrue(workflowService.isTaskReleasable(currentTask, USER1)); - assertTrue(workflowService.isTaskEditable(currentTask, USER1)); - assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); - assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); - assertFalse(workflowService.isTaskEditable(currentTask, USER2)); - - // cancel the workflow - workflowService.cancelWorkflow(workflowInstanceId); - assertNull(workflowService.getWorkflowById(workflowInstanceId)); - } -} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java b/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java index 5e21e62294..17e043e731 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java @@ -18,18 +18,22 @@ */ package org.alfresco.repo.workflow; -import org.alfresco.repo.workflow.jbpm.JBPMDeleteProcessTest; -import org.alfresco.repo.workflow.jbpm.JBPMEngineTest; -import org.alfresco.repo.workflow.jbpm.JBPMEngineUnitTest; -import org.alfresco.repo.workflow.jbpm.JBPMSpringTest; -import org.alfresco.repo.workflow.jbpm.NodeListConverterTest; -import org.alfresco.repo.workflow.jbpm.ReviewAndApproveTest; -import org.alfresco.util.ApplicationContextHelper; - import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.alfresco.repo.workflow.activiti.ActivitiSpringTransactionTest; +import org.alfresco.repo.workflow.activiti.ActivitiTimerExecutionTest; +import org.alfresco.repo.workflow.activiti.ActivitiWorkflowServiceIntegrationTest; +import org.alfresco.repo.workflow.jbpm.AlfrescoJavaScriptIntegrationTest; +import org.alfresco.repo.workflow.jbpm.JBPMDeleteProcessTest; +import org.alfresco.repo.workflow.jbpm.JBPMEngineTest; +import org.alfresco.repo.workflow.jbpm.JBPMSpringTest; +import org.alfresco.repo.workflow.jbpm.JbpmWorkflowServiceIntegrationTest; +import org.alfresco.repo.workflow.jbpm.NodeListConverterTest; +import org.alfresco.repo.workflow.jbpm.ReviewAndApproveTest; +import org.alfresco.util.ApplicationContextHelper; + /** * Workflow test suite */ @@ -47,30 +51,38 @@ public class WorkflowTestSuite extends TestSuite // Ensure that the default context is available ApplicationContextHelper.getApplicationContext(); - // Add the tests to be run + // Add the JBPM tests to be run suite.addTestSuite( StartWorkflowActionExecuterTest.class ); - suite.addTestSuite( WorkflowServiceImplTest.class ); + suite.addTestSuite( JbpmWorkflowServiceIntegrationTest.class ); suite.addTestSuite( ReviewAndApproveTest.class ); suite.addTestSuite( NodeListConverterTest.class ); suite.addTestSuite( JBPMDeleteProcessTest.class ); suite.addTestSuite( JBPMSpringTest.class ); suite.addTestSuite( JBPMEngineTest.class ); + suite.addTestSuite( AlfrescoJavaScriptIntegrationTest.class ); - // This should go last, as its uses a different - // context to the other tests - suite.addTestSuite( JBPMEngineUnitTest.class ); - - // This one will force the suite to shut down the context - // properly, which avoids periodic wierd build failures + // Add the Activiti tests to be run + suite.addTestSuite( ActivitiWorkflowServiceIntegrationTest.class ); + suite.addTestSuite( ActivitiSpringTransactionTest.class ); + suite.addTestSuite( ActivitiTimerExecutionTest.class ); + + // This test will force the application context properly, which avoids + // periodic wierd build failures suite.addTestSuite( WorkflowSuiteContextShutdownTest.class ); - + + // Note the following workflow tests are not included in this sutie: + // ActivitiTaskComponentTest + // ActivitiWorkflowComponentTest + // ActivitiWorkflowRestApiTest + // JbpmWorkflowRestApiTest return suite; } public static class WorkflowSuiteContextShutdownTest extends TestCase { - public void testDummy() {} + public void testDummy() { /*Do Nothing */ } - protected void tearDown() throws Exception { + @Override + protected void tearDown() throws Exception { System.err.println("Workflow test suite has completed, shutting down the ApplicationContext..."); ApplicationContextHelper.closeApplicationContext(); diff --git a/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java b/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java new file mode 100644 index 0000000000..e902d7f403 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import javax.annotation.Resource; + +import org.activiti.engine.HistoryService; +import org.activiti.engine.ManagementService; +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.TaskService; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.ProcessInstance; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.activiti.variable.ScriptNodeVariableType; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.junit.After; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.TransactionConfiguration; +import org.springframework.transaction.annotation.Transactional; + +/** + * @since 4.0 + * @author Nick Smith + * @author Frederik Heremans + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations={"classpath:test-database-context.xml", + "classpath:activiti/test-activiti-component-context.xml", + "classpath:alfresco/activiti-context.xml"}) +@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true) +@Transactional +public class AbstractActivitiComponentTest +{ + protected static final String TEST_GROUP = "GROUP_testGroup"; + protected static final String TEST_USER = "testUser"; + protected static final String TEST_TASK_DEF = "activiti/testTransaction.bpmn20.xml"; + protected static final String TEST_TASK_KEY = "testTask"; + protected static final String TEST_ADHOC_DEF = "activiti/testAdhoc.bpmn20.xml"; + protected static final String TEST_SIGNALLING_DEF = "activiti/testSignalling.bpmn20.xml"; + protected static final String TEST_REVIEW_DEF = "activiti/testReview.bpmn20.xml"; + protected static final String TEST_ADHOC_KEY = "testAdhoc"; + protected static final String TEST_JOB_KEY = "testAdhoc"; + protected static final String TEST_JOB_DEF = "activiti/testJob.bpmn20.xml"; + + protected static final String XML = MimetypeMap.MIMETYPE_XML; + + @Autowired + protected ProcessEngine processEngine; + + @Resource(name="activitiWorkflowEngine") + protected ActivitiWorkflowEngine workflowEngine; + @Resource(name="activitiRuntimeService") + protected RuntimeService runtime; + @Resource(name="activitiRepositoryService") + protected RepositoryService repo; + @Resource(name="activitiTaskService") + protected TaskService taskService; + @Resource(name="activitiHistoryService") + protected HistoryService historyService; + @Resource(name="activitiManagementService") + protected ManagementService managementService; + + @Resource + protected MessageService messageService; + + @Resource + protected TenantService tenantService; + + @Resource(name="NamespaceService") + protected NamespaceService namespaceService; + + @Resource(name="DictionaryService") + protected DictionaryService dictionaryService; + + @Resource(name="NodeService") + protected NodeService nodeService; + + @Resource(name="searchService") + protected SearchService unprotectedSearchService; + + @Resource + protected PermissionService permissionService; + + @Resource(name="PersonService") + protected PersonService personService; + + @Resource + protected AuthorityDAO authorityDAO; + + @Resource + protected ServiceRegistry serviceRegistry; + + protected static final NodeRef rootNode = new NodeRef("workspace://root/"); + protected static final NodeRef companyHomeNode = new NodeRef("workspace://companyHome/"); + protected static final NodeRef adminPersonNode = new NodeRef("workspace://admin/"); + protected static final NodeRef adminHomeNode = new NodeRef("workspace://admin-home/"); + protected static final NodeRef testUserNode = new NodeRef("workspace://testUser/"); + protected static final NodeRef testGroupNode = new NodeRef("workspace://testGroup/"); + protected static final NodeRef testWorkflowPackage = new NodeRef("workspace://testPackage/"); + protected static final NodeRef testWorkflowContext = new NodeRef("workspace://testContext/"); + + protected WorkflowDefinition deployTestTaskDefinition() + { + return deployDefinition(TEST_TASK_DEF); + } + + protected WorkflowDefinition deployTestAdhocDefinition() + { + return deployDefinition(TEST_ADHOC_DEF); + } + + protected WorkflowDefinition deployTestSignallingDefinition() + { + return deployDefinition(TEST_SIGNALLING_DEF); + } + + protected WorkflowDefinition deployTestJobDefinition() + { + return deployDefinition(TEST_JOB_DEF); + } + + protected WorkflowDefinition deployDefinition(String resource) + { + InputStream input = getInputStream(resource); + WorkflowDeployment deployment = workflowEngine.deployDefinition(input, XML); + WorkflowDefinition definition = deployment.getDefinition(); + return definition; + } + + protected InputStream getInputStream(String resource) + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input= classLoader.getResourceAsStream(resource); + return input; + } + + + @Before + public void setUp() throws Exception + { + mockTenantService(); + mockNamespaceService(); + mockDictionaryService(); + mockNodeService(); + mockSearchService(); + mockPermissionService(); + mockPersonService(); + mockAuthorityDAO(); + mockServiceRegistry(); + + workflowEngine.setCompanyHomeStore("workspace://SpacesStore"); + workflowEngine.setCompanyHomePath("spaces.company_home.childname"); + + // Also add custom type + // TODO: Should come from configuration + ScriptNodeVariableType variableType = new ScriptNodeVariableType(); + variableType.setServiceRegistry(serviceRegistry); + +// ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getVariableTypes().addType(variableType, 1); + + // Use util to set current user to admin + AuthenticationUtil.setFullyAuthenticatedUser("admin"); + + workflowEngine.afterPropertiesSet(); + } + + /** + * + */ + private void mockServiceRegistry() + { + // Set all services on the mocked Serviceregistry, injected by spring + when(serviceRegistry.getNodeService()).thenReturn(nodeService); + when(serviceRegistry.getDictionaryService()).thenReturn(dictionaryService); + when(serviceRegistry.getPermissionService()).thenReturn(permissionService); + } + + private void mockAuthorityDAO() + { + when(authorityDAO.authorityExists(TEST_USER)).thenReturn(true); + when(authorityDAO.authorityExists(TEST_GROUP)).thenReturn(true); + + // We will return dummy node refs to authorities testUser and testGroup + when(authorityDAO.getAuthorityNodeRefOrNull(TEST_USER)).thenReturn(testUserNode); + when(authorityDAO.getAuthorityNodeRefOrNull(TEST_GROUP)).thenReturn(testGroupNode); + } + + private void mockPersonService() + { + // Checking if admin exists + when(personService.personExists("admin")).thenReturn(true); + + // Return reference to Admin person + when(personService.getPerson("admin")).thenReturn(adminPersonNode); + + // Check if test-user exists + when(personService.personExists(TEST_USER)).thenReturn(true); + when(personService.getPerson(TEST_USER)).thenReturn(testUserNode); + } + + private void mockPermissionService() + { + // Allow permission on all nodes + when(permissionService.hasPermission((NodeRef) any(), anyString())).thenReturn(AccessStatus.ALLOWED); + } + + private void mockNodeService() + { + // Return root store + when(nodeService.getRootNode(new StoreRef("workspace://SpacesStore"))).thenReturn(rootNode); + + // Return company home and it's type + when(nodeService.exists(companyHomeNode)).thenReturn(true); + when(nodeService.getType((NodeRef) any())).thenReturn(QName.createQName("cm:folder")); + + // Return admin's home property + when(nodeService.getProperty(adminPersonNode, ContentModel.PROP_HOMEFOLDER)).thenReturn(adminHomeNode); + + // Return testUser and testGroup types + when(nodeService.getType(testUserNode)).thenReturn(ContentModel.TYPE_PERSON); + when(nodeService.getType(testGroupNode)).thenReturn(ContentModel.TYPE_AUTHORITY); + } + + private void mockSearchService() + { + // When searching for company home, return single node + when(unprotectedSearchService.selectNodes(rootNode, "spaces.company_home.childname", null, namespaceService, false)).thenReturn(Arrays.asList(companyHomeNode)); + } + + /** + * @return + */ + private void mockDictionaryService() + { + Mockito.reset(dictionaryService); + when(dictionaryService.getType((QName)any())).thenAnswer(new Answer() + { + @Override + public TypeDefinition answer(InvocationOnMock invocation) throws Throwable + { + QName name = (QName) invocation.getArguments()[0]; + TypeDefinition type = mock(TypeDefinition.class); + + when(type.getName()).thenReturn(name); + return type; + } + }); + + when(dictionaryService.getAnonymousType((QName)any())).thenAnswer(new Answer() + { + @Override + public TypeDefinition answer(InvocationOnMock invocation) throws Throwable + { + QName name = (QName) invocation.getArguments()[0]; + TypeDefinition type = mock(TypeDefinition.class); + + when(type.getName()).thenReturn(name); + + // Add a default value + Map props = new HashMap(); + QName qname = QName.createQName("http://test", "myProp"); + + + DataTypeDefinition qNameDef = mock(DataTypeDefinition.class); + when(qNameDef.getName()).thenReturn(DataTypeDefinition.QNAME); + when(qNameDef.getJavaClassName()).thenReturn(QName.class.getName()); + + // Create dummy property type + DataTypeDefinition def = mock(DataTypeDefinition.class); + when(def.getName()).thenReturn(DataTypeDefinition.TEXT); + when(def.getJavaClassName()).thenReturn(String.class.getName()); + + // Create dummy property definition + PropertyDefinition prop = mock(PropertyDefinition.class); + when(prop.getName()).thenReturn(qname); + when(prop.getDefaultValue()).thenReturn("Default value"); + when(prop.getDataType()).thenReturn(def); + + // Also add description + PropertyDefinition description = mock(PropertyDefinition.class); + when(description.getName()).thenReturn(WorkflowModel.PROP_DESCRIPTION); + when(description.getDataType()).thenReturn(def); + + // Add outcome property name + PropertyDefinition outcomePropertyName = mock(PropertyDefinition.class); + when(outcomePropertyName.getName()).thenReturn(WorkflowModel.PROP_OUTCOME_PROPERTY_NAME); + when(outcomePropertyName.getDataType()).thenReturn(qNameDef); + when(outcomePropertyName.getDefaultValue()).thenReturn("{http://test}testOutcome"); + + // Add outcome property + PropertyDefinition outcomeProperty = mock(PropertyDefinition.class); + when(outcomeProperty.getName()).thenReturn(QName.createQName("http://test", "testOutcome")); + when(outcomeProperty.getDataType()).thenReturn(def); + + props.put(qname, prop); + props.put(WorkflowModel.PROP_DESCRIPTION, description); + props.put(WorkflowModel.PROP_OUTCOME_PROPERTY_NAME, outcomePropertyName); + props.put(QName.createQName("http://test", "testOutcome"), outcomeProperty); + + when(type.getProperties()).thenReturn(props); + return type; + } + }); + + // Mock type inheritance for person nodes + when(dictionaryService.isSubClass(ContentModel.TYPE_PERSON, ContentModel.TYPE_PERSON)).thenReturn(true); + } + + private void mockNamespaceService() + { + namespaceService.registerNamespace(NamespaceService.BPM_MODEL_PREFIX, NamespaceService.BPM_MODEL_1_0_URI); + namespaceService.registerNamespace(NamespaceService.DEFAULT_PREFIX, NamespaceService.DEFAULT_URI); + namespaceService.registerNamespace(NamespaceService.WORKFLOW_MODEL_PREFIX, NamespaceService.WORKFLOW_MODEL_1_0_URI); + namespaceService.registerNamespace("test", "http://test"); + } + + private void mockTenantService() + { + when(tenantService.getBaseName(anyString())).thenAnswer(new Answer() + { + public String answer(InvocationOnMock invocation) throws Throwable + { + Object arg= invocation.getArguments()[0]; + return (String) arg; + } + }); + } + + @After + public void tearDown() + { + List defs = repo.createProcessDefinitionQuery() + .processDefinitionKey(TEST_TASK_KEY) + .list(); + HashSet deployments = new HashSet(defs.size()); + for (ProcessDefinition def : defs) + { + deployments.add(def.getDeploymentId()); + } + for (String deployment : deployments) + { + List definitions = repo.createProcessDefinitionQuery() + .deploymentId(deployment) + .list(); + for (ProcessDefinition def : definitions) + { + killInstances(def); + } + repo.deleteDeployment(deployment); + } + } + + public String mapQNameToName(QName name) + { + // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as + // new scheme (i.e. } -> _) + // NOTE: Use new scheme + String nameStr = name.toPrefixString(this.namespaceService); + if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':')) + { + return nameStr.replace(':', '}'); + } + return nameStr.replace(':', '_'); + } + + + private void killInstances(ProcessDefinition def) + { + List instances = runtime.createProcessInstanceQuery() + .processDefinitionId(def.getId()) + .list(); + for (ProcessInstance instance : instances) + { + runtime.deleteProcessInstance(instance.getId(), "For test"); + } + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java new file mode 100644 index 0000000000..eb07afee47 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +/** + * @since 3.5 + * @author Nick Smith + * + */ +public interface ActivitiConstants +{ + public static final String ENGINE_ID = "activiti"; + public static final String NODE_NAME = "name"; + public static final String NODE_DESCRIPTION = "documentation"; + public static final String NODE_TYPE= "type"; + + public static final String PROP_START_TASK_END_DATE = "_startTaskCompleted"; + public static final String START_TASK_PREFIX = "start"; + + public static final String DEFAULT_TRANSITION_NAME = "Next"; + public static final String START_TASK_PROPERTY_PREFIX = "_start_"; + + public static final String USER_TASK_NODE_TYPE = "userTask"; + public static final String PROP_TASK_FORM_KEY = "taskFormKey"; + public static final String PROP_POOLED_ACTORS_HISTORY = "pooledActorsHistory"; + public static final String DELETE_REASON_DELETED = "deleted"; + public static final String DELETE_REASON_CANCELLED = "cancelled"; + + public static final String SERVICE_REGISTRY_BEAN_KEY = "services"; + + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiNodeConverter.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiNodeConverter.java new file mode 100644 index 0000000000..147297ebb8 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiNodeConverter.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.util.Collection; +import java.util.List; + +import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.workflow.AbstractWorkflowNodeConverter; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author Nick Smith + * + */ +public class ActivitiNodeConverter extends AbstractWorkflowNodeConverter +{ + private final ServiceRegistry serviceRegistry; + + public ActivitiNodeConverter(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * {@inheritDoc} + */ + @Override + public Object convertNode(NodeRef node) + { + return new ActivitiScriptNode(node, serviceRegistry); + } + + /** + * {@inheritDoc} + */ + @Override + public List convertNodes(Collection values) + { + ActivitiScriptNodeList results = new ActivitiScriptNodeList(); + for (NodeRef node : values) + { + results.add(new ActivitiScriptNode(node, serviceRegistry)); + } + return results; + } + + /** + * {@inheritDoc} + */ + @Override + public NodeRef convertToNode(Object toConvert) + { + return ((ScriptNode)toConvert).getNodeRef(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSupported(Object object) + { + if(object == null) + { + return false; + } + if(object instanceof ActivitiScriptNode) + { + return true; + } + if(object instanceof ActivitiScriptNodeList) + { + return true; + } + return false; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiScriptNode.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiScriptNode.java new file mode 100644 index 0000000000..7049e7d54f --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiScriptNode.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.workflow.activiti; + +import java.io.Serializable; +import java.util.Date; + +import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.workflow.jbpm.JBPMNode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.mozilla.javascript.Scriptable; + +/** + * Scriptable Node suitable for Activti Beanshell access + * + * @author Frederik Heremans + */ +public class ActivitiScriptNode extends ScriptNode +{ + private static final long serialVersionUID = -826970280203254365L; + + /** + * Construct + * + * @param nodeRef node reference + * @param services services + */ + public ActivitiScriptNode(NodeRef nodeRef, ServiceRegistry services) + { + super(nodeRef, services, null); + } + + /** + * {@inheritDoc} + */ + @Override + protected NodeValueConverter createValueConverter() + { + return new JBPMNodeConverter(); + } + + /** + * Value converter for beanshell. Dates should be handled differenty since + * default conversion uses top-level scope which is sometimes missing. + */ + private class JBPMNodeConverter extends NodeValueConverter + { + @Override + public Serializable convertValueForRepo(Serializable value) + { + if (value instanceof Date) + { + return value; + } + else + { + return super.convertValueForRepo(value); + } + } + + @Override + public Serializable convertValueForScript(ServiceRegistry serviceRegistry, Scriptable theScope, QName qname, Serializable value) + { + if (value instanceof NodeRef) + { + return new JBPMNode(((NodeRef)value), serviceRegistry); + } + else if (value instanceof Date) + { + return value; + } + else + { + return super.convertValueForScript(serviceRegistry, theScope, qname, value); + } + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiScriptNodeList.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiScriptNodeList.java new file mode 100644 index 0000000000..9e6c9ff927 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiScriptNodeList.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * List of {@link ActivitiScriptNode}s. + * + * @author Frederik Heremans + */ +public class ActivitiScriptNodeList extends ArrayList +{ + private static final long serialVersionUID = 5177463364573735290L; + + public List getNodeReferences() + { + // Extract all node references + List nodeRefs = new ArrayList(); + for(ActivitiScriptNode scriptNode : this) + { + nodeRefs.add(scriptNode.getNodeRef()); + } + return nodeRefs; + } + + @Override + public int size() { + return super.size(); + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiSmokeTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiSmokeTest.java new file mode 100644 index 0000000000..16162dc8b7 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiSmokeTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.IOException; + +import junit.framework.TestCase; + +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.ProcessEngineConfiguration; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.repository.Deployment; +import org.activiti.engine.runtime.ProcessInstance; +import org.springframework.core.io.ClassPathResource; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiSmokeTest extends TestCase +{ + public void testDeploy() throws Exception + { + ProcessEngine engine = buildProcessEngine(); + + RepositoryService repoService = engine.getRepositoryService(); + + Deployment deployment = deployDefinition(repoService); + + assertNotNull(deployment); + + RuntimeService runtimeService = engine.getRuntimeService(); + try + { + ProcessInstance instance = runtimeService.startProcessInstanceByKey("testTask"); + assertNotNull(instance); + + String instanceId = instance.getId(); + ProcessInstance instanceInDb = findProcessInstance(runtimeService, instanceId); + assertNotNull(instanceInDb); + runtimeService.deleteProcessInstance(instanceId, ""); + } + finally + { + +// List deployments = repoService.createDeploymentQuery().list(); +// for (Deployment deployment2 : deployments) +// { +// repoService.deleteDeployment(deployment2.getId()); +// } + + repoService.deleteDeployment(deployment.getId()); + } + } + + private Deployment deployDefinition(RepositoryService repoService) throws IOException + { + ClassPathResource resource = new ClassPathResource("org/alfresco/repo/workflow/activiti/testTransaction.bpmn20.xml"); + Deployment deployment = repoService.createDeployment() + .addInputStream(resource.getFilename(), resource.getInputStream()) + .deploy(); + return deployment; + } + + private ProcessEngine buildProcessEngine() + { + String properties = "org/alfresco/repo/workflow/activiti/activiti.cfg.xml"; + ProcessEngine engine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(properties).buildProcessEngine(); + return engine; + } + + private ProcessInstance findProcessInstance(RuntimeService runtimeService, String instanceId) + { + ProcessInstance instanceInDb = runtimeService.createProcessInstanceQuery() + .processInstanceId(instanceId) + .singleResult(); + return instanceInDb; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiSpringTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiSpringTest.java new file mode 100644 index 0000000000..7b74bec3fe --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiSpringTest.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.activiti.engine.ActivitiException; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.repository.Deployment; +import org.activiti.engine.runtime.ProcessInstance; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.lang.ArrayUtils; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @since 3.4 + * @author Nick Smith + * + */ +public class ActivitiSpringTest extends TestCase +{ + private static final QName PROP_CHECK_VALUE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "check_value"); + private static final String PROC_DEF_KEY = "testTask"; + private static final String ACTIVITI_CONTEXT = "classpath:alfresco/activiti-context.xml"; + private static final QName ASPECT = ContentModel.ASPECT_ATTACHABLE; + + private RuntimeService runtime; + private RepositoryService repo; + private Deployment deployment; + private AuthenticationComponent authenticationComponent; + private NodeService nodeService; + private RetryingTransactionHelper txnHelper; + private NodeRef workingNodeRef; + + public void testSmoke() throws Exception + { + assertNotNull(runtime); + + ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY); + assertNotNull(instance); + + String instanceId = instance.getId(); + ProcessInstance instanceInDb = findProcessInstance(instanceId); + assertNotNull(instanceInDb); + runtime.deleteProcessInstance(instance.getId(), ""); + assertNotNull(instance); + } + + /** + * Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService. + * Check that the process instance was rolled back. + */ + public void testRollbackFromAlfresco() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public String execute() throws Throwable + { + ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY); + String id = instance.getId(); + try + { + blowUp(); + } + catch (InvalidNodeRefException e) + { + // Expected, but absorbed + } + return id; + } + }; + String id = txnHelper.doInTransaction(callback); + ProcessInstance instance = findProcessInstance(id); + if(instance!=null) + { + runtime.deleteProcessInstance(id, "For test"); + fail("The process instance creation should have been rolled back!"); + } + } + + /** + * Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService. + * Check that the process instance was rolled back. + */ + public void testRollbackFromActiviti() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + nodeService.addAspect(workingNodeRef, ASPECT, null); + assertTrue("The node should have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT)); + try + { + runtime.signal("Fake Id"); + fail("Should throw an Exception here!"); + } + catch (ActivitiException e) + { + // Expected, but absorbed + } + return null; + } + }; + txnHelper.doInTransaction(callback); + assertFalse("The node should not have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT)); + } + + /** + * Checks nesting of two transactions with requiresNew == true + */ + public void testNestedWithoutPropogation() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY); + final String id = instance.getId(); + + ProcessInstance instanceInDb = findProcessInstance(id); + assertNotNull("Can't read process instance in same transaction!", instanceInDb); + RetryingTransactionCallback callbackInner = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ProcessInstance instanceInDb2 = findProcessInstance(id); + assertNull("Should not be able to read process instance in inner transaction!", instanceInDb2); + return null; + } + }; + try + { + txnHelper.doInTransaction(callbackInner, false, true); + return null; + } + finally + { + runtime.deleteProcessInstance(id, "FOr test"); + } + } + }; + txnHelper.doInTransaction(callback); + } + + private Long blowUp() + { + NodeRef invalidNodeRef = new NodeRef(workingNodeRef.getStoreRef(), "BOGUS"); + nodeService.setProperty(invalidNodeRef, PROP_CHECK_VALUE, null); + fail("Expected to generate an InvalidNodeRefException"); + return null; + } + + private ProcessInstance findProcessInstance(String instanceId) + { + return runtime.createProcessInstanceQuery() + .processInstanceId(instanceId) + .singleResult(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setUp() throws Exception + { + ConfigurableApplicationContext appContext = loadContext(); + this.repo = (RepositoryService) appContext.getBean("activitiRepositoryService"); + this.runtime = (RuntimeService) appContext.getBean("activitiRuntimeService"); + + ServiceRegistry serviceRegistry = (ServiceRegistry) appContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + authenticationComponent = (AuthenticationComponent) appContext.getBean("authenticationComponent"); + TransactionService transactionService = serviceRegistry.getTransactionService(); + nodeService = serviceRegistry.getNodeService(); + txnHelper = transactionService.getRetryingTransactionHelper(); + + // authenticate + authenticationComponent.setSystemUserAsCurrentUser(); + + StoreRef storeRef = nodeService.createStore( + StoreRef.PROTOCOL_WORKSPACE, + "test-" + getName() + "-" + System.currentTimeMillis()); + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + // Create a node to work on + workingNodeRef = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()), + ContentModel.TYPE_CMOBJECT).getChildRef(); + + String resource = "org/alfresco/repo/workflow/activiti/testTransaction.bpmn20.xml"; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input= classLoader.getResourceAsStream(resource); + this.deployment = repo.createDeployment() + .addInputStream(resource, input) + .deploy(); + } + + private String[] getConfigLocations() + { + String[] defaultLocations = ApplicationContextHelper.CONFIG_LOCATIONS; + Object[] locations = ArrayUtils.add(defaultLocations, ACTIVITI_CONTEXT); + return (String[]) locations; + } + + private ConfigurableApplicationContext loadContext() throws Exception + { + String[] locations = getConfigLocations(); + return new ClassPathXmlApplicationContext(locations); + } + + /** + * {@inheritDoc} + */ + @Override + protected void tearDown() throws Exception + { + try{ + repo.deleteDeployment(deployment.getId()); + authenticationComponent.clearCurrentSecurityContext(); + } + catch (Exception e) + { + // Do Nothing } + } + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiSpringTransactionTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiSpringTransactionTest.java new file mode 100644 index 0000000000..2aa632221c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiSpringTransactionTest.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.activiti.engine.ActivitiException; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.repository.Deployment; +import org.activiti.engine.runtime.ProcessInstance; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @since 3.4 + * @author Nick Smith + * + */ +public class ActivitiSpringTransactionTest extends TestCase +{ + private static final QName PROP_CHECK_VALUE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "check_value"); + private static final String PROC_DEF_KEY = "testTask"; + private static final QName ASPECT = ContentModel.ASPECT_ATTACHABLE; + + private RuntimeService runtime; + private RepositoryService repo; + private Deployment deployment; + private AuthenticationComponent authenticationComponent; + private NodeService nodeService; + private RetryingTransactionHelper txnHelper; + private NodeRef workingNodeRef; + + public void testSmoke() throws Exception + { + assertNotNull(runtime); + + ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY); + assertNotNull(instance); + + String instanceId = instance.getId(); + ProcessInstance instanceInDb = findProcessInstance(instanceId); + assertNotNull(instanceInDb); + runtime.deleteProcessInstance(instance.getId(), ""); + assertNotNull(instance); + } + + /** + * Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService. + * Check that the process instance was rolled back. + */ + public void testRollbackFromAlfresco() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public String execute() throws Throwable + { + ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY); + String id = instance.getId(); + try + { + blowUp(); + } + catch (InvalidNodeRefException e) + { + // Expected, but absorbed + } + return id; + } + }; + String id = txnHelper.doInTransaction(callback); + ProcessInstance instance = findProcessInstance(id); + if(instance!=null) + { + runtime.deleteProcessInstance(id, "For test"); + fail("The process instance creation should have been rolled back!"); + } + } + + /** + * Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService. + * Check that the process instance was rolled back. + */ + public void testRollbackFromActiviti() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + nodeService.addAspect(workingNodeRef, ASPECT, null); + assertTrue("The node should have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT)); + try + { + runtime.signal("Fake Id"); + fail("Should throw an Exception here!"); + } + catch (ActivitiException e) + { + // Expected, but absorbed + } + return null; + } + }; + txnHelper.doInTransaction(callback); + assertFalse("The node should not have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT)); + } + + /** + * Checks nesting of two transactions with requiresNew == true + */ + public void testNestedWithoutPropogation() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY); + final String id = instance.getId(); + + ProcessInstance instanceInDb = findProcessInstance(id); + assertNotNull("Can't read process instance in same transaction!", instanceInDb); + RetryingTransactionCallback callbackInner = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ProcessInstance instanceInDb2 = findProcessInstance(id); + assertNull("Should not be able to read process instance in inner transaction!", instanceInDb2); + return null; + } + }; + try + { + txnHelper.doInTransaction(callbackInner, false, true); + return null; + } + finally + { + runtime.deleteProcessInstance(id, "FOr test"); + } + } + }; + txnHelper.doInTransaction(callback); + } + + private Long blowUp() + { + NodeRef invalidNodeRef = new NodeRef(workingNodeRef.getStoreRef(), "BOGUS"); + nodeService.setProperty(invalidNodeRef, PROP_CHECK_VALUE, null); + fail("Expected to generate an InvalidNodeRefException"); + return null; + } + + private ProcessInstance findProcessInstance(String instanceId) + { + return runtime.createProcessInstanceQuery() + .processInstanceId(instanceId) + .singleResult(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setUp() throws Exception + { + ApplicationContext appContext = ApplicationContextHelper.getApplicationContext(); + this.repo = (RepositoryService) appContext.getBean("activitiRepositoryService"); + this.runtime = (RuntimeService) appContext.getBean("activitiRuntimeService"); + + ServiceRegistry serviceRegistry = (ServiceRegistry) appContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + authenticationComponent = (AuthenticationComponent) appContext.getBean("authenticationComponent"); + TransactionService transactionService = serviceRegistry.getTransactionService(); + nodeService = serviceRegistry.getNodeService(); + txnHelper = transactionService.getRetryingTransactionHelper(); + + // authenticate + authenticationComponent.setSystemUserAsCurrentUser(); + + StoreRef storeRef = nodeService.createStore( + StoreRef.PROTOCOL_WORKSPACE, + "test-" + getName() + "-" + System.currentTimeMillis()); + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + // Create a node to work on + workingNodeRef = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()), + ContentModel.TYPE_CMOBJECT).getChildRef(); + + String resource = "activiti/testTransaction.bpmn20.xml"; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input= classLoader.getResourceAsStream(resource); + this.deployment = repo.createDeployment() + .addInputStream(resource, input) + .deploy(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void tearDown() throws Exception + { + try{ + repo.deleteDeployment(deployment.getId()); + authenticationComponent.clearCurrentSecurityContext(); + } + catch (Exception e) + { + // Do Nothing } + } + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskComponentTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskComponentTest.java new file mode 100644 index 0000000000..3bbf5b1a59 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskComponentTest.java @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.activiti.engine.history.HistoricDetail; +import org.activiti.engine.history.HistoricVariableUpdate; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.QName; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @since 4.0 + * @author Nick Smith + * @author Frederik Heremans + */ +public class ActivitiTaskComponentTest extends AbstractActivitiComponentTest +{ + private WorkflowDefinition workflowDef; + + @Test + public void testGetStartTask() + { + try + { + workflowEngine.getStartTask("Foo"); + fail("Should blow up if Id is wrong format!"); + } + catch(WorkflowException e) + { + // Do Nothing + } + + WorkflowTask result = workflowEngine.getStartTask(ActivitiConstants.ENGINE_ID + "$Foo"); + assertNull("Should not find any result for fake (but valid) Id.", result); + + Map params = new HashMap(); + String comment = "Start task description"; + params.put(WorkflowModel.PROP_COMMENT, comment); + params.put(WorkflowModel.PROP_PRIORITY, 1 ); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_DUE_DATE, dueDate ); + + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), params); + String instanceId = path.getInstance().getId(); + WorkflowTask task = workflowEngine.getStartTask(instanceId); + assertNotNull("Task shoudl exist!", task); + + String localId = ActivitiConstants.START_TASK_PREFIX+BPMEngineRegistry.getLocalId(instanceId); + String taskId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, localId); + assertEquals("Start Task Id is wrong", taskId, task.getId()); + + assertEquals("The start task path is wrong!", path.getId(), task.getPath().getId()); + TypeDefinition definition = task.getDefinition().getMetadata(); + assertNotNull(definition); + String name = definition.getName().toPrefixString(namespaceService).replace(':', '_'); + assertEquals("bpm_foo", name); + + assertEquals(name, task.getName()); + assertEquals(name, task.getTitle()); + assertEquals(name, task.getDescription()); + assertEquals(WorkflowTaskState.IN_PROGRESS, task.getState()); + assertEquals(name, task.getDescription()); + + // Check start task properties populated. + Map properties = task.getProperties(); + assertEquals(comment, properties.get(WorkflowModel.PROP_COMMENT)); + assertEquals(1, properties.get(WorkflowModel.PROP_PRIORITY)); + assertEquals(dueDate, properties.get(WorkflowModel.PROP_DUE_DATE)); + + // Check start task after task is completed. + task = workflowEngine.endTask(task.getId(), null); + + assertEquals("Start Task Id is wrong", taskId, task.getId()); + + assertEquals("The start task path is wrong!", path.getId(), task.getPath().getId()); + definition = task.getDefinition().getMetadata(); + assertNotNull(definition); + name = definition.getName().toPrefixString(namespaceService).replace(':', '_'); + assertEquals("bpm_foo", name); + + assertEquals(name, task.getName()); + assertEquals(name, task.getTitle()); + assertEquals(name, task.getDescription()); + assertEquals(WorkflowTaskState.COMPLETED, task.getState()); + assertEquals(name, task.getDescription()); + + // Check start task properties populated. + properties = task.getProperties(); + assertEquals(comment, properties.get(WorkflowModel.PROP_COMMENT)); + assertEquals(1, properties.get(WorkflowModel.PROP_PRIORITY)); + assertEquals(dueDate, properties.get(WorkflowModel.PROP_DUE_DATE)); + + // Check start task for historic process. + workflowEngine.cancelWorkflow(instanceId); + task = workflowEngine.getStartTask(instanceId); + + assertNull(task); + + +// assertEquals("Start Task Id is wrong", taskId, task.getId()); +// +// assertEquals("The start task path is wrong!", path.getId(), task.getPath().getId()); +// definition = task.getDefinition().getMetadata(); +// assertNotNull(definition); +// name = definition.getName().toPrefixString(namespaceService).replace(':', '_'); +// assertEquals("bpm_foo", name); +// +// assertEquals(name, task.getName()); +// assertEquals(name, task.getTitle()); +// assertEquals(name, task.getDescription()); +// assertEquals(WorkflowTaskState.COMPLETED, task.getState()); +// assertEquals(name, task.getDescription()); +// +// // Check start task properties populated. +// properties = task.getProperties(); +// assertEquals(comment, properties.get(WorkflowModel.PROP_COMMENT)); +// assertEquals(1, properties.get(WorkflowModel.PROP_PRIORITY)); +// assertEquals(dueDate, properties.get(WorkflowModel.PROP_DUE_DATE)); + } + + @Test + public void testGetTaskById() throws Exception + { + try + { + workflowEngine.getTaskById("Foo"); + fail("Should blow up if Id is wrong format!"); + } + catch(WorkflowException e) + { + // Do Nothing + } + + WorkflowTask result = workflowEngine.getTaskById(ActivitiConstants.ENGINE_ID + "$Foo"); + assertNull("Should not find any result for fake (but valid) Id.", result); + + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap()); + Task task = taskService.createTaskQuery() + .executionId(BPMEngineRegistry.getLocalId(path.getId())) + .singleResult(); + assertNotNull("Task shoudl exist!", task); + + String taskId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, task.getId()); + WorkflowTask wfTask = workflowEngine.getTaskById(taskId); + assertNotNull(wfTask); + } + + @Test + public void testGetStartTaskById() throws Exception + { + + WorkflowTask result = workflowEngine.getTaskById(ActivitiConstants.ENGINE_ID + "$Foo"); + assertNull("Should not find any result for fake (but valid) Id.", result); + + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap()); + + Task task = taskService.createTaskQuery() + .executionId(BPMEngineRegistry.getLocalId(path.getId())) + .singleResult(); + + // A start task should be available for the process instance + String startTaskId = ActivitiConstants.START_TASK_PREFIX + task.getProcessInstanceId(); + + String taskId = createGlobalId(startTaskId); + WorkflowTask wfTask = workflowEngine.getTaskById(taskId); + assertNotNull(wfTask); + assertEquals(createGlobalId(task.getProcessInstanceId()), wfTask.getPath().getId()); + } + + @SuppressWarnings("unchecked") + @Test + public void testGetFinishedTaskById() throws Exception + { + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap()); + + // Finish the start-task + WorkflowTask startTask = workflowEngine.getStartTask(path.getInstance().getId()); + workflowEngine.endTask(startTask.getId(), null); + + // Set some task properties on the first task, different types + List tasks = workflowEngine.getTasksForWorkflowPath(path.getId()); + String finishedTaskId = tasks.get(0).getId(); + + Map props = new HashMap(); + props.put(WorkflowModel.PROP_DESCRIPTION, "Task description"); + props.put(WorkflowModel.PROP_PRIORITY, 1234); + props.put(QName.createQName("myprop1"), "Property value"); + props.put(QName.createQName("myprop2"), Boolean.TRUE); + props.put(QName.createQName("myprop3"), 12345); + props.put(QName.createQName("myprop4"), 45678L); + + workflowEngine.updateTask(finishedTaskId, props, null, null); + + // Finish the first task, this task will be used in this test + workflowEngine.endTask(finishedTaskId, null); + + // Get the finished task + WorkflowTask finishedTask = workflowEngine.getTaskById(finishedTaskId); + assertNotNull(finishedTask); + + // TODO: revive once http://jira.codehaus.org/browse/ACT-485 is fixed + // Assert.assertEquals("Task description", finishedTask.getDescription()); + + Assert.assertEquals(finishedTaskId, finishedTask.getId()); + Assert.assertEquals("bpm_foo_task", finishedTask.getName()); + Assert.assertEquals("Task", finishedTask.getTitle()); + Assert.assertEquals(WorkflowTaskState.COMPLETED, finishedTask.getState()); + + // Check if typeDefinition (formKey) is preserved on finished tasks + Assert.assertEquals("task name", finishedTask.getDefinition().getId(), "bpm_foo_task"); + + // Check workflowpath + Assert.assertEquals(path.getId(), finishedTask.getPath().getId()); + Assert.assertEquals(path.getInstance().getId(), finishedTask.getPath().getInstance().getId()); + + // Check variables + Assert.assertEquals("Property value", finishedTask.getProperties().get(QName.createQName("myprop1"))); + Assert.assertEquals(Boolean.TRUE, finishedTask.getProperties().get(QName.createQName("myprop2"))); + Assert.assertEquals(12345, finishedTask.getProperties().get(QName.createQName("myprop3"))); + Assert.assertEquals(45678L, finishedTask.getProperties().get(QName.createQName("myprop4"))); + + // Check pooled actors, should be one user and one group + List pooledActors = (List) finishedTask.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS); + Assert.assertNotNull(pooledActors); + Assert.assertEquals(2, pooledActors.size()); + Assert.assertTrue(pooledActors.contains(testGroupNode)); + Assert.assertTrue(pooledActors.contains(testUserNode)); + } + + @Test + public void testEndTask() throws Exception + { + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap()); + + Task task = taskService.createTaskQuery() + .executionId(BPMEngineRegistry.getLocalId(path.getId())) + .singleResult(); + + assertNotNull("Task should exist!", task); + + String globalTaskId = createGlobalId(task.getId()); + // Set a custom property on the task, this will be flushed + // to process-instance once task is completed + Map props = new HashMap(); + props.put(QName.createQName("http://test", "myVar"), "test123"); + + workflowEngine.updateTask(globalTaskId, props, null, null); + + // Now end the task + workflowEngine.endTask(globalTaskId, null); + + // Check if process-instance now contains the variable of the finished task + List updates = historyService.createHistoricDetailQuery() + .variableUpdates() + .processInstanceId(task.getProcessInstanceId()) + .list(); + + boolean found = false; + for(HistoricDetail detail : updates) + { + HistoricVariableUpdate update = (HistoricVariableUpdate) detail; + if(update.getVariableName().equals("test_myVar")) + { + Assert.assertEquals("test123", update.getValue()); + found = true; + } + } + + Assert.assertTrue("Task variables are not flushed to process-instance", found); + } + + @SuppressWarnings("unchecked") + @Test + public void testGetPooledTasks() throws Exception + { + // The first task in the TestTaskDefinition has candidate group 'testGroup' + // and candidate-user 'testUser' + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap()); + + // Get start task + WorkflowTask startTask = workflowEngine.getStartTask(path.getInstance().getId()); + assertNotNull(startTask); + + // Finish the start task + workflowEngine.endTask(startTask.getId(), null); + + List tasks = workflowEngine.getTasksForWorkflowPath(path.getId()); + assertNotNull(tasks); + Assert.assertEquals(1, tasks.size()); + + // Check if the ASSOC_POOLED_ACTORS is set on the task, to be sure + // pooled actors are used on task + WorkflowTask theTask = tasks.get(0); + + Serializable pooledActors = theTask.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS); + assertNotNull(pooledActors); + + // Group and user should be present + List pooledActorNodes = (List) pooledActors; + Assert.assertEquals(2, pooledActorNodes.size()); + Assert.assertTrue(pooledActorNodes.contains(testUserNode)); + Assert.assertTrue(pooledActorNodes.contains(testGroupNode)); + + // The task should be found when pooled tasks are requested + List pooledUserTasks = workflowEngine.getPooledTasks(Arrays.asList(TEST_USER)); + assertNotNull(pooledUserTasks); + Assert.assertEquals(1, pooledUserTasks.size()); + Assert.assertEquals(theTask.getId(), pooledUserTasks.get(0).getId()); + + // The task should be found when pooled taskes are requested + List pooledGroupTasks = workflowEngine.getPooledTasks(Arrays.asList(TEST_GROUP)); + assertNotNull(pooledGroupTasks); + Assert.assertEquals(1, pooledGroupTasks.size()); + Assert.assertEquals(theTask.getId(), pooledGroupTasks.get(0).getId()); + + // Only a single task should be found when task is both pooled for testUser and testGroup + List pooledTasks = workflowEngine.getPooledTasks(Arrays.asList(TEST_USER, TEST_GROUP)); + assertNotNull(pooledTasks); + Assert.assertEquals(1, pooledTasks.size()); + Assert.assertEquals(theTask.getId(), pooledTasks.get(0).getId()); + + // No tasks should be found + List unexistingPooledTasks = workflowEngine.getPooledTasks(Arrays.asList("unexisting")); + assertNotNull(unexistingPooledTasks); + Assert.assertEquals(0, unexistingPooledTasks.size()); + + // If one authority matches, task should be returned + pooledGroupTasks = workflowEngine.getPooledTasks(Arrays.asList("unexistinggroup",TEST_GROUP)); + assertNotNull(pooledGroupTasks); + Assert.assertEquals(1, pooledGroupTasks.size()); + Assert.assertEquals(theTask.getId(), pooledGroupTasks.get(0).getId()); + } + + @Test + public void testQueryTasksInProgress() throws Exception { + // Testing all query functionality for WorkflowTaskState.IN_PROGRESS + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap()); + + Task task = taskService.createTaskQuery() + .executionId(BPMEngineRegistry.getLocalId(path.getId())) + .singleResult(); + assertNotNull("Task should exist!", task); + + String globalTaskId = createGlobalId(task.getId()); + + // Test query by taskId + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS); + taskQuery.setTaskId(globalTaskId); + + List tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(1, tasks.size()); + Assert.assertEquals(globalTaskId, tasks.get(0).getId()); + + // Test query by nonexistent taskId + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS); + taskQuery.setTaskId(createGlobalId("nonexistentTask")); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + + // Test query by process ID + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS); + taskQuery.setProcessId(createGlobalId(task.getProcessInstanceId())); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(1, tasks.size()); + Assert.assertEquals(globalTaskId, tasks.get(0).getId()); + + // Test query by nonexistent processId + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS); + taskQuery.setProcessId(createGlobalId("nonexistentProcess")); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + + // Test query by actor ID + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS); + taskQuery.setActorId(TEST_USER); + tasks = workflowEngine.queryTasks(taskQuery); + + // No tasks should be assigned to testUser + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + + // Assign the task + taskService.setAssignee(task.getId(), TEST_USER); + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS); + taskQuery.setActorId(TEST_USER); + tasks = workflowEngine.queryTasks(taskQuery); + // Task is assigned to testUser + Assert.assertNotNull(tasks); + Assert.assertEquals(1, tasks.size()); + Assert.assertEquals(globalTaskId, tasks.get(0).getId()); + + // Test by nonexistent actor ID + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS); + taskQuery.setActorId("nonexistentUser"); + tasks = workflowEngine.queryTasks(taskQuery); + + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + + // TODO: test using process-name, not yet implemented now + // TODO: test using task-name + + // Test querying task variables, using all possible (and allowed) types of variables + Map variables = new HashMap(); + variables.put("longVar", 928374L); + variables.put("shortVar", (short) 123); + variables.put("integerVar", 1234); + variables.put("stringVar", "stringValue"); + variables.put("booleanVar", true); + Date date = Calendar.getInstance().getTime(); + variables.put("dateVar", date); + variables.put("nullVar", null); + ActivitiScriptNode scriptNode = new ActivitiScriptNode(testGroupNode, serviceRegistry); + variables.put("scriptNodeVar", scriptNode); + + taskService.setVariablesLocal(task.getId(), variables); + + // Query long variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("longVar"), 928374L, globalTaskId); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("longVar"), 444444L); + + // Query short variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("shortVar"), (short) 123, globalTaskId); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("shortVar"), (short) 456); + + // Query integer variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("integerVar"), 1234, globalTaskId); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("integerVar"), 5678); + + // Query string variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("stringVar"), "stringValue", globalTaskId); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("stringVar"), "noMatchString"); + + // Query string variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("booleanVar"), true, globalTaskId); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("booleanVar"), false); + + // Query date variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("dateVar"), date, globalTaskId); + Calendar otherDate = Calendar.getInstance(); + otherDate.add(Calendar.YEAR, 1); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("dateVar"), otherDate.getTime()); + + // Query null variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("nullVar"), null, globalTaskId); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("nullVar"), "notNull"); + + // Query script-node variable + checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("scriptNodeVar"), scriptNode, globalTaskId); + ActivitiScriptNode otherNode = new ActivitiScriptNode(testUserNode, serviceRegistry); + checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("scriptNodeVar"), otherNode); + + // TODO: test using process variables + } + + + @Test + public void testQueryTasksCompleted() throws Exception { + // Testing all query functionality for WorkflowTaskState.IN_PROGRESS + WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap()); + + Task task = taskService.createTaskQuery() + .executionId(BPMEngineRegistry.getLocalId(path.getId())) + .singleResult(); + assertNotNull("Task should exist!", task); + String globalTaskId = createGlobalId(task.getId()); + + // Set the actor + taskService.setAssignee(task.getId(), TEST_USER); + + // End the task + workflowEngine.endTask(globalTaskId, null); + + // Test query by taskId + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setTaskId(globalTaskId); + + List tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(1, tasks.size()); + Assert.assertEquals(globalTaskId, tasks.get(0).getId()); + + // Test query by nonexistent task ID + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setTaskId(createGlobalId("nonexistantTask")); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + + // Test query by process ID, this should also return the start-task + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setProcessId(createGlobalId(task.getProcessInstanceId())); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(2, tasks.size()); + + boolean taskFound = false; + boolean startTaskFound = false; + for(WorkflowTask wfTask : tasks) + { + if(wfTask.getId().equals(globalTaskId)) + { + taskFound = true; + } + if(wfTask.getId().contains(ActivitiConstants.START_TASK_PREFIX)) + { + startTaskFound = true; + } + } + Assert.assertTrue("Task should have been returned", taskFound); + Assert.assertTrue("Start-task should have been returned", startTaskFound); + + // Test query by nonexistent process ID + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setProcessId(createGlobalId("nonexistantProcess")); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + + // Test query by actor + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setActorId(TEST_USER); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(1, tasks.size()); + + // Test by nonexistent actor + taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); + taskQuery.setActorId("unexistingUser"); + + tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + + // TODO: test using process-name, not yet implemented now + // TODO: test using task-name + + // TODO: test using task variables + // TODO: test using process variables + + } + + private void checkTaskVariableTaskPresent(WorkflowTaskState state, + QName varName, Object varValue, String expectedTask) { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + Map customProperties = new HashMap(); + customProperties.put(varName, varValue); + taskQuery.setTaskCustomProps(customProperties); + + assertTaskPresent(taskQuery, expectedTask); + } + + private void checkTaskVariableNoMatch(WorkflowTaskState state, + QName varName, Object varValue) { + WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state); + Map customProperties = new HashMap(); + customProperties.put(varName, varValue); + taskQuery.setTaskCustomProps(customProperties); + + assertNoTaskPresent(taskQuery); + } + + private WorkflowTaskQuery createWorkflowTaskQuery(WorkflowTaskState state) + { + WorkflowTaskQuery taskQuery = new WorkflowTaskQuery(); + taskQuery.setTaskState(state); + return taskQuery; + } + + + private void assertTaskPresent(WorkflowTaskQuery taskQuery, + String taskId) + { + List tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(1, tasks.size()); + Assert.assertEquals(taskId, tasks.get(0).getId()); + } + + private void assertNoTaskPresent(WorkflowTaskQuery taskQuery) + { + List tasks = workflowEngine.queryTasks(taskQuery); + Assert.assertNotNull(tasks); + Assert.assertEquals(0, tasks.size()); + } + + private String createGlobalId(String id) + { + return BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, id); + } + + @Override + @Before + public void setUp() throws Exception + { + super.setUp(); + this.workflowDef = deployTestTaskDefinition(); + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskPropertyHandler.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskPropertyHandler.java new file mode 100644 index 0000000000..951cd8430c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskPropertyHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.Serializable; + +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.AbstractWorkflowPropertyHandler; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public abstract class ActivitiTaskPropertyHandler extends AbstractWorkflowPropertyHandler +{ + + /** + * {@inheritDoc} + */ + public Object handleProperty(QName key, Serializable value, TypeDefinition type, Object object, Class objectType) + { + if(DelegateTask.class.equals(objectType)) + { + return handleDelegateTaskProperty((DelegateTask)object, type, key, value); + } + else if(Task.class.equals(objectType)) + { + return handleTaskProperty((Task)object, type, key, value); + } + return handleProcessPropert(null, type, key, value); + } + + /** + * @param type + * @param key + * @param value + * @return + */ + private Object handleProcessPropert(Object process, TypeDefinition type, QName key, Serializable value) + { + return handleDefaultProperty(process, type, key, value); + } + + /** + * Handles the property for a {@link Task}. + * @param task + * @param type + * @param key + * @param value + * @return + */ + protected abstract Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value); + + /** + * Handles the property for a {@link DelegateTask}. + * @param task + * @param value + * @param key + * @param type + * @return + */ + protected abstract Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value); + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskTypeManager.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskTypeManager.java new file mode 100644 index 0000000000..eebcb004da --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTaskTypeManager.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import org.activiti.engine.FormService; +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.form.FormData; +import org.activiti.engine.form.TaskFormData; +import org.activiti.engine.impl.form.TaskFormHandler; +import org.activiti.engine.impl.task.TaskEntity; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.WorkflowObjectFactory; +import org.alfresco.service.cmr.dictionary.TypeDefinition; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiTaskTypeManager +{ + private final WorkflowObjectFactory factory; + private final FormService formService; + + public ActivitiTaskTypeManager(WorkflowObjectFactory factory, FormService formService) + { + this.factory = factory; + this.formService = formService; + } + + public TypeDefinition getStartTaskDefinition(String taskTypeName) + { + return factory.getTaskFullTypeDefinition(taskTypeName, true); + } + + public TypeDefinition getFullTaskDefinition(Task task) + { + TaskFormData taskFormData = formService.getTaskFormData(task.getId()); + return getFullTaskDefinition(task.getId(), taskFormData); + } + + public TypeDefinition getFullTaskDefinition(DelegateTask delegateTask) + { + FormData formData = null; + TaskEntity taskEntity = (TaskEntity) delegateTask; + TaskFormHandler taskFormHandler = taskEntity.getTaskDefinition().getTaskFormHandler(); + if (taskFormHandler != null) + { + formData = taskFormHandler.createTaskForm(taskEntity); + } + return getFullTaskDefinition(delegateTask.getId(), formData); + } + + public TypeDefinition getFullTaskDefinition(String typeName) + { + return getFullTaskDefinition(typeName, null); + } + + private TypeDefinition getFullTaskDefinition(String taskDefinitionKey, FormData taskFormData) + { + String formKey = null; + if(taskFormData != null) + { + formKey = taskFormData.getFormKey(); + } + else + { + // Revert to task definition key + formKey = taskDefinitionKey; + } + // Since Task instances are never the start-task, it's safe to always be false + return factory.getTaskFullTypeDefinition(formKey, false); + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTimerExecutionTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTimerExecutionTest.java new file mode 100644 index 0000000000..00ed4d44db --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTimerExecutionTest.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.impl.runtime.TimerEntity; +import org.activiti.engine.repository.ProcessDefinition; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.person.TestPersonManager; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTimer; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; + +/** + * Test to verify timer execution autentication and transaction behaviour. + * + * @author Frederik Heremans + */ +public class ActivitiTimerExecutionTest extends BaseSpringTest { + + private static final String USER1 = "User1" + GUID.generate(); + + private RetryingTransactionHelper transactionHelper; + + private WorkflowService workflowService; + + private AuthenticationComponent authenticationComponent; + + private NodeService nodeService; + + private ProcessEngine activitiProcessEngine; + + private TestPersonManager personManager; + + @SuppressWarnings("deprecation") + public void testTimerExecutionAuthentication() throws Exception + { + this.setComplete(); + this.endTransaction(); + + try + { + WorkflowInstance taskAssigneeWorkflowInstance = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public WorkflowInstance execute() throws Throwable + { + // Create test person + personManager.createPerson(USER1); + + WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml"); + + // Start the test timer transaction process, with 'error' = false, expecting a timer job + // to be executed without an error, with task timer is assigned to assigned to USER1 + Map params = new HashMap(); + params.put(QName.createQName("error"), Boolean.FALSE); + params.put(QName.createQName("theTaskAssignee"), USER1); + + WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); + // End start-task + workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); + + return path.getInstance(); + } + }); + + final String definitionId = taskAssigneeWorkflowInstance.getDefinition().getId(); + WorkflowInstance unassignedWorkflowInstance = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public WorkflowInstance execute() throws Throwable + { + // Start the test timer transaction process, with 'error' = false, expecting a timer job + // to be executed without an error, with task timer is unassigned + Map params = new HashMap(); + params.put(QName.createQName("error"), Boolean.FALSE); + params.put(QName.createQName("theTaskAssignee"), null); + + WorkflowPath path = workflowService.startWorkflow(definitionId, params); + // End start-task + workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); + + return path.getInstance(); + } + }); + + // No timers should be available after a while they should have been executed, otherwise test fails + waitForTimersToBeExecuted(taskAssigneeWorkflowInstance.getId()); + waitForTimersToBeExecuted(unassignedWorkflowInstance.getId()); + + // Test assigned task + WorkflowPath path = workflowService.getWorkflowPaths(taskAssigneeWorkflowInstance.getId()).get(0); + + // Check if job executed without exception, process should be waiting in "waitTask" + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertNotNull(tasks); + assertEquals(1, tasks.size()); + assertEquals("waitTask", tasks.get(0).getDefinition().getNode().getName()); + + // Check if timer was executed as task assignee, was set while executing timer + Map pathProps = workflowService.getPathProperties(path.getId()); + assertEquals(USER1, pathProps.get(QName.createQName("timerExecutedAs"))); + + // Test unassigned task, should be executed as admin-user + path = workflowService.getWorkflowPaths(unassignedWorkflowInstance.getId()).get(0); + + // Check if job did executed without exception, process should be waiting in "waitTask" + tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertNotNull(tasks); + assertEquals(1, tasks.size()); + assertEquals("waitTask", tasks.get(0).getDefinition().getNode().getName()); + + // Check if timer was executed as system + pathProps = workflowService.getPathProperties(path.getId()); + assertEquals(AuthenticationUtil.getSystemUserName(), pathProps.get(QName.createQName("timerExecutedAs"))); + } + finally + { + cleanUp(); + + } + } + + @SuppressWarnings("deprecation") + public void testTimerExecutionTransactionRollback() throws Exception + { + this.setComplete(); + this.endTransaction(); + + try + { + WorkflowInstance workflowInstance = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public WorkflowInstance execute() throws Throwable + { + // Create test person + personManager.createPerson(USER1); + + WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml"); + + // Start the test timer transaction process, with 'error' = false, expecting a timer job + // to be executed without an error, with task timer is assigned to assigned to USER1 + Map params = new HashMap(); + params.put(QName.createQName("error"), Boolean.TRUE); + params.put(QName.createQName("theTaskAssignee"), USER1); + + WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); + // End start-task + workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); + + return path.getInstance(); + } + }); + + String processInstanceId = BPMEngineRegistry.getLocalId(workflowInstance.getId()); + + // Check the timer, should have "error" set in it + TimerEntity timer = (TimerEntity) activitiProcessEngine.getManagementService() + .createJobQuery().timers() + .processInstanceId(processInstanceId).singleResult(); + + int numberOfRetries = 5; + for(int i = 0; i < numberOfRetries; i++) + { + if(timer.getExceptionMessage() != null && timer.getRetries() == 0) + { + break; + } + Thread.sleep(1000); + timer = (TimerEntity) activitiProcessEngine.getManagementService() + .createJobQuery().timers() + .processInstanceId(processInstanceId).singleResult(); + } + assertNotNull("Job should have exception message set", timer.getExceptionMessage()); + assertEquals(0, timer.getRetries()); + + // Check if exception is the one we deliberately caused + String fullExceptionStacktrace = activitiProcessEngine.getManagementService().getJobExceptionStacktrace(timer.getId()); + assertTrue(fullExceptionStacktrace.contains("Activiti engine rocks!")); + + // Check if alfresco-changes that were performed are rolled back + NodeRef personNode = personManager.get(USER1); + NodeRef userHomeNode = (NodeRef)nodeService.getProperty(personNode, ContentModel.PROP_HOMEFOLDER); + + String homeFolderName = (String) nodeService.getProperty(userHomeNode, ContentModel.PROP_NAME); + assertNotSame("User home changed", homeFolderName); + } + finally + { + cleanUp(); + } + } + + /** + * Delete the deployment, cascading all related processes/history + */ + private void cleanUp() + { + transactionHelper .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + try + { + personManager.clearPeople(); + } + finally + { + // Make sure process-definition is still deleted, even when clearing people fails. + ProcessDefinition procDef = activitiProcessEngine.getRepositoryService() + .createProcessDefinitionQuery() + .processDefinitionKey("testTimerTransaction") + .singleResult(); + + if(procDef != null) + { + activitiProcessEngine.getRepositoryService().deleteDeployment(procDef.getDeploymentId(), true); + } + } + return null; + } + }); + } + + @SuppressWarnings("deprecation") + @Override + protected void onSetUpInTransaction() throws Exception + { + ServiceRegistry registry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + this.workflowService = registry.getWorkflowService(); + this.authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); + this.nodeService = registry.getNodeService(); + + this.transactionHelper = (RetryingTransactionHelper) this.applicationContext + .getBean("retryingTransactionHelper"); + + this.activitiProcessEngine = (ProcessEngine) this.applicationContext.getBean("activitiProcessEngine"); + + MutableAuthenticationService authenticationService = registry.getAuthenticationService(); + PersonService personService = registry.getPersonService(); + + this.personManager = new TestPersonManager(authenticationService, personService, nodeService); + + authenticationComponent.setSystemUserAsCurrentUser(); + } + + private void waitForTimersToBeExecuted(String workflowInstanceId) throws Exception + { + // Job-executor should finish the job, no timers should be available for WF + List timers = workflowService.getTimers(workflowInstanceId); + + int numberOfRetries = 5; + for(int i=0; i< numberOfRetries; i++) + { + if(timers.size() == 0) + { + break; + } + Thread.sleep(1000); + timers = workflowService.getTimers(workflowInstanceId); + } + } + + protected WorkflowDefinition deployDefinition(String resource) + { + InputStream input = getInputStream(resource); + WorkflowDeployment deployment = workflowService.deployDefinition(ActivitiConstants.ENGINE_ID, input, MimetypeMap.MIMETYPE_XML); + WorkflowDefinition definition = deployment.getDefinition(); + return definition; + } + + private InputStream getInputStream(String resource) + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input= classLoader.getResourceAsStream(resource); + return input; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java new file mode 100644 index 0000000000..9fb7a6bdef --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java @@ -0,0 +1,557 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.activiti.engine.FormService; +import org.activiti.engine.HistoryService; +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.form.StartFormData; +import org.activiti.engine.form.TaskFormData; +import org.activiti.engine.history.HistoricProcessInstance; +import org.activiti.engine.history.HistoricTaskInstance; +import org.activiti.engine.impl.pvm.PvmActivity; +import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; +import org.activiti.engine.impl.pvm.process.ActivityImpl; +import org.activiti.engine.repository.Deployment; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.Execution; +import org.activiti.engine.runtime.ProcessInstance; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.WorkflowObjectFactory; +import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowNode; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTransition; +import org.alfresco.service.namespace.QName; + +/** + * @since 3.5 + * @author Nick Smith + * @author Frederik Heremans + * + */ +public class ActivitiTypeConverter +{ + /** + * Default transition provided for all Nodes when using Activiti engine. + */ + private static final WorkflowTransition NEXT_TRANSITION = new WorkflowTransition(ActivitiConstants.DEFAULT_TRANSITION_NAME, + ActivitiConstants.DEFAULT_TRANSITION_NAME, "Default Transition", true); + + private final RepositoryService repoService; + private final RuntimeService runtimeService; + private final FormService formService; + private final HistoryService historyService; + private final ActivitiPropertyConverter propertyConverter; + private final WorkflowObjectFactory factory; + + private final ActivitiUtil activitiUtil; + + public ActivitiTypeConverter(ProcessEngine processEngine, + WorkflowObjectFactory factory, + ActivitiPropertyConverter propertyConverter) + { + this.repoService = processEngine.getRepositoryService(); + this.runtimeService = processEngine.getRuntimeService(); + this.formService = processEngine.getFormService(); + this.historyService = processEngine.getHistoryService(); + this.factory = factory; + this.propertyConverter =propertyConverter; + this.activitiUtil = new ActivitiUtil(processEngine); + } + + /** + * Convert a {@link Deployment} into a {@link WorkflowDeployment}. + * @param deployment + * @return + */ + public WorkflowDeployment convert(Deployment deployment) + { + if(deployment == null) + return null; + + List processDefs = repoService.createProcessDefinitionQuery() + .deploymentId(deployment.getId()) + .list(); + ProcessDefinition processDef = processDefs.get(0); + WorkflowDefinition wfDef = convert(processDef); + return factory.createDeployment(wfDef); + } + + /** + * Convert a {@link ProcessDefinition} into a {@link WorkflowDefinition}. + * @param processDef + * @return + */ + public WorkflowDefinition convert(ProcessDefinition definition) + { + if(definition==null) + return null; + + String defId = definition.getId(); + String defName = definition.getKey(); + int version = definition.getVersion(); + String defaultTitle = definition.getName(); + + String startTaskName = null; + StartFormData startFormData = formService.getStartFormData(definition.getId()); + if(startFormData != null) + { + startTaskName = startFormData.getFormKey(); + } + + ReadOnlyProcessDefinition def = activitiUtil.getDeployedProcessDefinition(defId); + PvmActivity startEvent = def.getInitial(); + WorkflowTaskDefinition startTask = getTaskDefinition(startEvent, startTaskName, definition.getKey()); + + return factory.createDefinition(defId, + defName, version, defaultTitle, + null, startTask); + } + + public WorkflowTaskDefinition getTaskDefinition(PvmActivity activity, String taskFormKey, String processDefinitionName) + { + String startId = activity.getId(); + String startTitle = (String) activity.getProperty(ActivitiConstants.NODE_NAME); + String startDescription= (String) activity.getProperty(ActivitiConstants.NODE_DESCRIPTION); + String startType = (String) activity.getProperty(ActivitiConstants.NODE_TYPE); + if(taskFormKey == null) + { + taskFormKey = startId; + } + WorkflowNode node = factory.createNode(startId, processDefinitionName, startTitle, startDescription, startType, true, NEXT_TRANSITION); + WorkflowTaskDefinition startTask = factory.createTaskDefinition(taskFormKey, node, taskFormKey, true); + return startTask; + } + + public WorkflowInstance convert(ProcessInstance instance) + { + return convertAndSetVariables(instance, (Map) null); + } + + public WorkflowInstance convertAndSetVariables(ProcessInstance instance, Map collectedvariables) + { + if(instance == null) + return null; + + HistoricProcessInstance historicInstance = historyService + .createHistoricProcessInstanceQuery() + .processInstanceId(instance.getId()) + .singleResult(); + + return convertToInstanceAndSetVariables(historicInstance, collectedvariables); + } + + public WorkflowInstance convert(HistoricProcessInstance instance, Map collectedvariables) + { + if(instance == null) + return null; + + HistoricProcessInstance historicInstance = historyService + .createHistoricProcessInstanceQuery() + .processInstanceId(instance.getId()) + .singleResult(); + + return convertToInstanceAndSetVariables(historicInstance, collectedvariables); + } + + public WorkflowPath convert(Execution execution) + { + String instanceId = execution.getProcessInstanceId(); + ProcessInstance instance = activitiUtil.getProcessInstance(instanceId); + return convert(execution, instance); + } + + public WorkflowPath convert(Execution execution, ProcessInstance instance) + { + if(execution == null) + return null; + + boolean isActive = !execution.isEnded(); + + // Convert workflow and collect variables + Map workflowInstanceVariables = new HashMap(); + WorkflowInstance wfInstance = convertAndSetVariables(instance, workflowInstanceVariables); + + WorkflowNode node = null; + // Get active node on execution + List nodeIds = runtimeService.getActiveActivityIds(execution.getId()); + + if (nodeIds != null && nodeIds.size() >= 1) + { + ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(instance.getProcessDefinitionId()); + PvmActivity activity = procDef.findActivity(nodeIds.get(0)); + node = convert(activity); + } + + return factory.createPath(execution.getId(), wfInstance, node, isActive); + } + + public WorkflowNode convert(PvmActivity activity, boolean forceIsTaskNode) + { + String procDefId = activity.getProcessDefinition().getId(); + String key = activitiUtil.getProcessDefinition(procDefId).getKey(); + String name = activity.getId(); + String defaultTitle = (String) activity.getProperty(ActivitiConstants.NODE_NAME); + String defaultDescription = (String) activity.getProperty(ActivitiConstants.NODE_DESCRIPTION); + String type = (String) activity.getProperty(ActivitiConstants.NODE_TYPE); + boolean isTaskNode = forceIsTaskNode || ActivitiConstants.USER_TASK_NODE_TYPE.equals(type); + + if(defaultTitle == null) + { + defaultTitle = name; + } + if(defaultDescription == null) + { + defaultDescription = name; + } + + return factory.createNode(name, key, defaultTitle, defaultDescription, type, isTaskNode, + NEXT_TRANSITION); + } + + public WorkflowNode convert(PvmActivity activity) + { + return convert(activity, false); + } + + public List convertExecution(List executions) + { + ArrayList results = new ArrayList(executions.size()); + for (Execution execution : executions) + { + results.add(convert(execution)); + } + return results; + } + + @SuppressWarnings("unchecked") + public List convert(List inputs) + { + ArrayList results = new ArrayList(inputs.size()); + for (Object in : inputs) + { + T out = (T) convert(in); + if(out != null) + { + results.add(out); + } + } + return results; + } + + /** + * Converts an Activiti object to an Alresco Workflow object. + * Determines the exact conversion method to use by checking the class of object. + * @param obj The object to be converted. + * @return the converted object. + */ + private Object convert(Object obj) + { + if(obj == null) + return null; + + if (obj instanceof Deployment) + { + return convert( (Deployment) obj); + } + if (obj instanceof ProcessDefinition) + { + return convert( (ProcessDefinition) obj); + } + if (obj instanceof ProcessInstance) + { + return convert( (ProcessInstance) obj); + } + if (obj instanceof Execution) + { + return convert( (Execution) obj); + } + if (obj instanceof ActivityImpl) + { + return convert( (ActivityImpl) obj); + } + if (obj instanceof Task) + { + return convert( (Task) obj); + } + if(obj instanceof HistoricTaskInstance) + { + return convert((HistoricTaskInstance) obj); + } + if(obj instanceof HistoricProcessInstance) + { + return convert((HistoricProcessInstance) obj); + } + throw new WorkflowException("Cannot convert object: " + obj + " of type: " + obj.getClass()); + } + + + public WorkflowTask convert(Task task) + { + if(task == null) + return null; + String id = task.getId(); + String defaultTitle = task.getName(); + String defaultDescription = task.getDescription(); + + WorkflowTaskState state = WorkflowTaskState.IN_PROGRESS; + Execution execution = activitiUtil.getExecution(task.getExecutionId()); + WorkflowPath path = convert(execution); + + // Since the task is active, it's safe to use the active node on + // the execution path + WorkflowNode node = path.getNode(); + + + TaskFormData taskFormData =formService.getTaskFormData(task.getId()); + String taskDefId = null; + if(taskFormData != null) + { + taskDefId = taskFormData.getFormKey(); + } + WorkflowTaskDefinition taskDef = factory.createTaskDefinition(taskDefId, node, taskDefId, false); + + // All task-properties should be fetched, not only local + Map properties = propertyConverter.getTaskProperties(task, false); + + return factory.createTask(id, + taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties); + } + + public WorkflowTask getVirtualStartTask(String executionId, boolean inProgress) + { + Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult(); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(execution.getProcessInstanceId()) + .singleResult(); + + String id = ActivitiConstants.START_TASK_PREFIX + execution.getProcessInstanceId(); + + WorkflowTaskState state = null; + if(inProgress) + { + state = WorkflowTaskState.IN_PROGRESS; + } + else + { + state = WorkflowTaskState.COMPLETED; + } + + WorkflowPath path = convert(execution); + + // Convert start-event to start-task Node + ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(processInstance.getProcessDefinitionId()); + WorkflowNode startNode = convert(procDef.getInitial(), true); + + StartFormData startFormData = formService.getStartFormData(processInstance.getProcessDefinitionId()); + String taskDefId = null; + if(startFormData != null) + { + taskDefId = startFormData.getFormKey(); + } + WorkflowTaskDefinition taskDef = factory.createTaskDefinition(taskDefId, startNode, taskDefId, true); + + // Add properties based on HistoricProcessInstance + HistoricProcessInstance historicProcessInstance = historyService + .createHistoricProcessInstanceQuery() + .processInstanceId(execution.getProcessInstanceId()) + .singleResult(); + + Map properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, !inProgress); + + // TODO: Figure out what name/description should be used for the start-task, start event's name? + String defaultTitle = null; + String defaultDescription = null; + + return factory.createTask(id, + taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties); + } + + public WorkflowTask getVirtualStartTask(HistoricProcessInstance historicProcessInstance) + { + if(historicProcessInstance == null) + { + return null; + } + + String processInstanceId = historicProcessInstance.getId(); + String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId; + + WorkflowTaskState state = null; + + boolean completed = historicProcessInstance.getEndTime() != null; + if(completed) + { + state = WorkflowTaskState.COMPLETED; + } + else + { + state = WorkflowTaskState.IN_PROGRESS; + } + + // We use the process-instance ID as execution-id. It's ended anyway + WorkflowPath path = buildCompletedPath(processInstanceId, processInstanceId); + if(path == null) + { + return null; + } + + // Convert start-event to start-task Node + ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId()); + WorkflowNode startNode = convert(procDef.getInitial(), true); + + String taskDefId = activitiUtil.getStartFormKey(historicProcessInstance.getProcessDefinitionId()); + WorkflowTaskDefinition taskDef = factory.createTaskDefinition(taskDefId, startNode, taskDefId, true); + + Map properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, completed); + + // TODO: Figure out what name/description should be used for the start-task, start event's name? + String defaultTitle = null; + String defaultDescription = null; + + return factory.createTask(id, + taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties); + } + + public WorkflowTask convert(HistoricTaskInstance historicTaskInstance) { + if(historicTaskInstance == null) + { + return null; + } + WorkflowPath path = null; + // Check to see if the instance is still running + Execution execution = activitiUtil.getExecution(historicTaskInstance.getExecutionId()); + + if(execution != null) + { + // Process execution still running + path = convert(execution); + } + else + { + // Process execution is historic + path = buildCompletedPath(historicTaskInstance.getExecutionId(), historicTaskInstance.getProcessInstanceId()); + } + + if(path == null) + { + // When path is null, workflow is deleted or cancelled. Task should + // not be used + return null; + } + + // Extract node from historic task + WorkflowNode node = buildHistoricTaskWorkflowNode(historicTaskInstance); + + WorkflowTaskState state= WorkflowTaskState.COMPLETED; + + String taskId = historicTaskInstance.getId(); + // Get the local task variables from the history + Map variables = propertyConverter.getHistoricTaskVariables(taskId); + Map historicTaskProperties = propertyConverter.getTaskProperties(historicTaskInstance, variables); + + // Get task definition from historic variable + String formKey = (String) variables.get(ActivitiConstants.PROP_TASK_FORM_KEY); + WorkflowTaskDefinition taskDef = factory.createTaskDefinition(formKey, node, formKey, false); + String title = historicTaskInstance.getName(); + String description = historicTaskInstance.getDescription(); + String taskName = taskDef.getId(); + + return factory.createTask(taskId, taskDef, taskName, + title, description, state, path, historicTaskProperties); + } + + private WorkflowNode buildHistoricTaskWorkflowNode(HistoricTaskInstance historicTaskInstance) + { + ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(historicTaskInstance.getProcessDefinitionId()); + PvmActivity taskActivity = procDef.findActivity(historicTaskInstance.getTaskDefinitionKey()); + return convert(taskActivity); + } + + public WorkflowPath buildCompletedPath(String executionId, String processInstanceId) + { + WorkflowInstance wfInstance = null; + ProcessInstance processInstance = activitiUtil.getProcessInstance(processInstanceId); + if(processInstance != null) + { + wfInstance = convert(processInstance); + } + else + { + HistoricProcessInstance historicProcessInstance = activitiUtil.getHistoricProcessInstance(processInstanceId); + if(historicProcessInstance!= null) + wfInstance = convert(historicProcessInstance); + } + if(wfInstance == null) + { + // When workflow is cancelled or deleted, WorkflowPath should not be returned + return null; + } + WorkflowNode node = null; + return factory.createPath(executionId, wfInstance, node, false); + } + + public WorkflowInstance convertToInstanceAndSetVariables(HistoricProcessInstance historicProcessInstance, Map collectedVariables) + { + String processInstanceId = historicProcessInstance.getId(); + String id = processInstanceId; + ProcessDefinition procDef = activitiUtil.getProcessDefinition(historicProcessInstance.getProcessDefinitionId()); + WorkflowDefinition definition = convert(procDef); + + // Set process variables based on historic detail query + Map variables = propertyConverter.getHistoricProcessVariables(processInstanceId); + + Date startDate = historicProcessInstance.getStartTime(); + Date endDate = historicProcessInstance.getEndTime(); + + // Copy all variables to map, if not null + if(collectedVariables != null) + { + collectedVariables.putAll(variables); + } + + boolean isActive = historicProcessInstance.getEndTime() == null; + return factory.createInstance( + id, definition, variables, isActive, startDate, endDate); + } + + public WorkflowInstance convert(HistoricProcessInstance historicProcessInstance) + { + return convertToInstanceAndSetVariables(historicProcessInstance, null); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java new file mode 100644 index 0000000000..946bf6a5eb --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.util.Map; + +import org.activiti.engine.FormService; +import org.activiti.engine.HistoryService; +import org.activiti.engine.ManagementService; +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.TaskService; +import org.activiti.engine.form.StartFormData; +import org.activiti.engine.history.HistoricProcessInstance; +import org.activiti.engine.history.HistoricTaskInstance; +import org.activiti.engine.impl.RepositoryServiceImpl; +import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; +import org.activiti.engine.impl.repository.ProcessDefinitionEntity; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.Execution; +import org.activiti.engine.runtime.ProcessInstance; +import org.activiti.engine.task.Task; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiUtil +{ + private final RepositoryService repoService; + private final RuntimeService runtimeService; + private final HistoryService historyService; + private final TaskService taskService; + private final FormService formService; + private final ManagementService managementService; + + public ActivitiUtil(ProcessEngine engine) + { + this.repoService = engine.getRepositoryService(); + this.runtimeService = engine.getRuntimeService(); + this.taskService = engine.getTaskService(); + this.historyService = engine.getHistoryService(); + this.formService = engine.getFormService(); + this.managementService = engine.getManagementService(); + } + + public ProcessDefinition getProcessDefinition(String definitionId) + { + ProcessDefinition procDef = repoService.createProcessDefinitionQuery() + .processDefinitionId(definitionId) + .singleResult(); + return procDef; + } + + public ProcessInstance getProcessInstance(String id) + { + return runtimeService.createProcessInstanceQuery() + .processInstanceId(id) + .singleResult(); + } + + public Task getTaskInstance(String taskId) + { + return taskService.createTaskQuery().taskId(taskId).singleResult(); + } + + public HistoricProcessInstance getHistoricProcessInstance(String id) + { + return historyService.createHistoricProcessInstanceQuery() + .processInstanceId(id) + .singleResult(); + } + + public Execution getExecution(String id) + { + return runtimeService.createExecutionQuery() + .executionId(id) + .singleResult(); + } + + public ReadOnlyProcessDefinition getDeployedProcessDefinition(String processDefinitionId) + { + // Currently, getDeployedProcessDefinition is still experimental and not exposed on + // RepositoryService interface + return ((RepositoryServiceImpl)repoService).getDeployedProcessDefinition(processDefinitionId); + } + + public String getStartFormKey(String processDefinitionId) + { + ProcessDefinitionEntity procDef = (ProcessDefinitionEntity) getDeployedProcessDefinition(processDefinitionId); + if(procDef.getStartFormHandler() == null) { + return null; + } + return procDef.getStartFormHandler().createStartFormData(procDef).getFormKey(); + } + + public String getStartTaskTypeName(String processDefinitionId) + { + String startTaskName = null; + StartFormData startFormData = formService.getStartFormData(processDefinitionId); + if(startFormData != null) + { + startTaskName = startFormData.getFormKey(); + } + return startTaskName; + } + + public Map getExecutionVariables(String executionId) + { + return runtimeService.getVariables(executionId); + } + + /** + * @return the formService + */ + public FormService getFormService() + { + return formService; + } + + /** + * @return the historyService + */ + public HistoryService getHistoryService() + { + return historyService; + } + + /** + * @return the repoService + */ + public RepositoryService getRepositoryService() + { + return repoService; + } + + /** + * @return the runtimeService + */ + public RuntimeService getRuntimeService() + { + return runtimeService; + } + + /** + * @return the taskService + */ + public TaskService getTaskService() + { + return taskService; + } + + /** + * @return + */ + public ManagementService getManagementService() + { + return managementService; + } + + /** + * @param localId + * @return + */ + public HistoricTaskInstance getHistoricTaskInstance(String localId) + { + return historyService.createHistoricTaskInstanceQuery().taskId(localId).singleResult(); + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowComponentTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowComponentTest.java new file mode 100644 index 0000000000..8d4bbf1f7a --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowComponentTest.java @@ -0,0 +1,712 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import java.io.InputStream; +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.activiti.engine.history.HistoricProcessInstance; +import org.activiti.engine.impl.util.ClockUtil; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.Execution; +import org.activiti.engine.runtime.Job; +import org.activiti.engine.runtime.ProcessInstance; +import org.activiti.engine.task.Task; +import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.WorkflowConstants; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowNode; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTimer; +import org.alfresco.service.namespace.QName; +import org.junit.Test; + +/** + * Spring-configured JUnit 4 test case. + * Uses Spring to load up a test context and runs each test case in a transaction which gets rolled back. + * Loads up the activiti-context.xml and test-database-context.xml. + * @since 3.5 + * @author Nick Smith + * + */ +public class ActivitiWorkflowComponentTest extends AbstractActivitiComponentTest +{ + @Test + public void testDeployDefinition() throws Exception + { + ProcessDefinition defInDB = repo.createProcessDefinitionQuery() + .processDefinitionKey(TEST_TASK_KEY) + .singleResult(); + assertNull("The definition is already deployed!", defInDB); + + WorkflowDefinition definition = deployTestTaskDefinition(); + String localDefId = BPMEngineRegistry.getLocalId(definition.getId()); + ProcessDefinition processDef = repo.createProcessDefinitionQuery() + .processDefinitionId(localDefId) + .singleResult(); + assertNotNull("Process Definition should have been deployed!", processDef); + + ProcessDefinition def2InDB = repo.createProcessDefinitionQuery() + .processDefinitionKey(TEST_ADHOC_KEY) + .singleResult(); + assertNull("The definition is already deployed!", def2InDB); + + WorkflowDefinition definition2 = deployTestTaskDefinition(); + String localDef2Id = BPMEngineRegistry.getLocalId(definition2.getId()); + ProcessDefinition processDef2 = repo.createProcessDefinitionQuery() + .processDefinitionId(localDef2Id) + .singleResult(); + assertNotNull("Process Definition should have been deployed!", processDef2); + } + + @Test + public void testIsDefinitionDeployed() throws Exception + { + InputStream input = getInputStream(TEST_TASK_DEF); + + boolean result = workflowEngine.isDefinitionDeployed(input, XML); + assertFalse("Should return false before process def deployed.", result); + + deployTestTaskDefinition(); + + input = getInputStream(TEST_TASK_DEF); + result = workflowEngine.isDefinitionDeployed(input, XML); + assertTrue("Should return true after process def deployed.", result); + + // Check doesn't find Adhoc definition. + input = getInputStream(TEST_ADHOC_DEF); + result = workflowEngine.isDefinitionDeployed(input, XML); + assertFalse("Should not find Adhoc definition.", result); + } + + @Test + public void testUndeployDefinition() throws Exception + { + WorkflowDefinition definition = deployTestTaskDefinition(); + String localId = BPMEngineRegistry.getLocalId(definition.getId()); + + long defCount = repo.createProcessDefinitionQuery() + .processDefinitionId(localId) + .count(); + assertEquals("The deployed process definition should exist!", 1, defCount); + + workflowEngine.undeployDefinition(definition.getId()); + + defCount = repo.createProcessDefinitionQuery() + .processDefinitionId(localId) + .count(); + assertEquals("The undeployed process definition should not exist!", 0, defCount); + + } + + @Test + public void testGetDefinitionById() throws Exception + { + WorkflowDefinition definition = deployTestTaskDefinition(); + WorkflowDefinition result = workflowEngine.getDefinitionById(definition.getId()); + + assertNotNull("The workflow definition was not found!", result); + + assertEquals(definition.getId(), result.getId()); + assertEquals(definition.getDescription(), result.getDescription()); + assertEquals(definition.getName(), result.getName()); + assertEquals(definition.getTitle(), result.getTitle()); + assertEquals(definition.getVersion(), result.getVersion()); + + WorkflowTaskDefinition resultStartDef = result.getStartTaskDefinition(); + assertNotNull("Start task is null!", resultStartDef); + + WorkflowTaskDefinition originalStartDef = definition.getStartTaskDefinition(); + assertEquals("Start task Id does not match!", originalStartDef.getId(), resultStartDef.getId()); + + WorkflowNode resultNode = resultStartDef.getNode(); + assertNotNull("Start Task Node is null!", resultNode); + assertEquals("Start Task Node Name does not match!", originalStartDef.getNode().getName(), resultNode.getName()); + + TypeDefinition metaData = resultStartDef.getMetadata(); + assertNotNull("Start Task Metadata is null!", metaData); + assertEquals("Start Task Metadata name does not match!", originalStartDef.getMetadata().getName(), metaData.getName()); + + workflowEngine.undeployDefinition(definition.getId()); + WorkflowDefinition nullResult = workflowEngine.getDefinitionById(definition.getId()); + assertNull("The workflow definition was found but should be null!", nullResult); + } + + @Test + public void testGetDefinitionByName() throws Exception + { + WorkflowDefinition definition = deployTestTaskDefinition(); + WorkflowDefinition result = workflowEngine.getDefinitionByName(definition.getName()); + + assertNotNull("The workflow definition was not found!", result); + + assertEquals(definition.getId(), result.getId()); + assertEquals(definition.getDescription(), result.getDescription()); + assertEquals(definition.getName(), result.getName()); + assertEquals(definition.getTitle(), result.getTitle()); + assertEquals(definition.getVersion(), result.getVersion()); + + workflowEngine.undeployDefinition(definition.getId()); + WorkflowDefinition nullResult = workflowEngine.getDefinitionByName(definition.getName()); + assertNull("The workflow definition was found but should be null!", nullResult); + } + + @Test + public void testGetDefinitions() throws Exception + { + List startDefs = workflowEngine.getDefinitions(); + + WorkflowDefinition defV1 = deployTestTaskDefinition(); + List definitions = workflowEngine.getDefinitions(); + checkDefinitions(definitions, startDefs, defV1); + + // Deploy version 2 of testTask def. + WorkflowDefinition defV2 = deployTestTaskDefinition(); + // Check new version replaces old version. + definitions = workflowEngine.getDefinitions(); + checkDefinitions(definitions, startDefs, defV2); + + // Deploy new type of definition. + WorkflowDefinition adhocDef = deployTestAdhocDefinition(); + // Check that definitions of a different type are picked up. + definitions = workflowEngine.getDefinitions(); + checkDefinitions(definitions, startDefs, defV2, adhocDef); + } + + + @Test + public void testGetAllDefinitions() throws Exception + { + List startDefs = workflowEngine.getAllDefinitions(); + + WorkflowDefinition defV1 = deployTestTaskDefinition(); + List definitions = workflowEngine.getAllDefinitions(); + checkDefinitions(definitions, startDefs, defV1); + + // Deploy version 2 of testTask def. + WorkflowDefinition defV2 = deployTestTaskDefinition(); + // Check new version replaces old version. + definitions = workflowEngine.getAllDefinitions(); + checkDefinitions(definitions, startDefs, defV1, defV2); + + // Deploy new type of definition. + WorkflowDefinition adhocDef = deployTestAdhocDefinition(); + // Check that definitions of a different type are picked up. + definitions = workflowEngine.getAllDefinitions(); + checkDefinitions(definitions, startDefs, defV1, defV2, adhocDef); + } + + @Test + public void testStartWorkflow() throws Exception + { + WorkflowDefinition def = deployTestTaskDefinition(); + + // Fill a map of default properties to start the workflow with + Map properties = new HashMap(); + Date dueDate = Calendar.getInstance().getTime(); + properties.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "description123"); + properties.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + properties.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 2); + // properties.put(WorkflowModel.ASSOC_PACKAGE, null); + // properties.put(WorkflowModel.PROP_CONTEXT, null); + + + // Call the start method + WorkflowPath path = workflowEngine.startWorkflow(def.getId(), properties); + assertNotNull("The workflow path is null!", path); + String executionId = BPMEngineRegistry.getLocalId(path.getId()); + Execution execution = runtime.createExecutionQuery() + .executionId(executionId) + .singleResult(); + assertNotNull("No execution was created int he DB!", execution); + + WorkflowInstance instance = path.getInstance(); + assertNotNull("The workflow instance is null!",instance); + String procInstanceId = BPMEngineRegistry.getLocalId(instance.getId()); + ProcessInstance procInstance = runtime.createProcessInstanceQuery() + .processInstanceId(procInstanceId) + .singleResult(); + assertNotNull("No process instance was created!", procInstance); + + WorkflowNode node = path.getNode(); + assertNotNull("The workflow node is null!", node); + String nodeName = node.getName(); + + assertEquals("task", nodeName); + + // Check if company home is added as variable and can be fetched + ScriptNode companyHome = (ScriptNode) runtime.getVariable(procInstanceId, "companyhome"); + assertNotNull(companyHome); + assertEquals("companyHome", companyHome.getNodeRef().getStoreRef().getIdentifier()); + + // Check if the initiator is added as variable + ScriptNode initiator = (ScriptNode) runtime.getVariable(procInstanceId, "initiator"); + assertNotNull(initiator); + assertEquals("admin", initiator.getNodeRef().getStoreRef().getIdentifier()); + + // Check if the initiator home is also set as variable + ScriptNode initiatorHome = (ScriptNode) runtime.getVariable(procInstanceId, "initiatorhome"); + assertNotNull(initiatorHome); + assertEquals("admin-home", initiatorHome.getNodeRef().getStoreRef().getIdentifier()); + + // Check if start-date is set and no end-date is set + assertNotNull(path.getInstance().getStartDate()); + assertNull(path.getInstance().getEndDate()); + + // Also check if the task that is created, has all default properties initialised + Task task = taskService.createTaskQuery().processInstanceId(procInstanceId).singleResult(); + assertNotNull("Task should have been created", task); + assertEquals("task", task.getTaskDefinitionKey()); + String defaultSetVariable = (String) taskService.getVariableLocal(task.getId(), "test_myProp"); + assertEquals("Default value", defaultSetVariable); + + // Also check default value of task description is taken from WF-porps + assertEquals("description123", task.getDescription()); + } + + @Test + public void testSignal() throws Exception + { + WorkflowDefinition def = deployTestSignallingDefinition(); + ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId())); + + String procId = processInstance.getId(); + List nodeIds = runtime.getActiveActivityIds(procId); + assertEquals(1, nodeIds.size()); + assertEquals("task1", nodeIds.get(0)); + + String pathId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, procId); + WorkflowPath path = workflowEngine.signal(pathId, null); + assertEquals(pathId, path.getId()); + assertEquals("task2", path.getNode().getName()); + assertEquals(pathId, path.getInstance().getId()); + assertTrue(path.isActive()); + + nodeIds = runtime.getActiveActivityIds(procId); + assertEquals(1, nodeIds.size()); + assertEquals("task2", nodeIds.get(0)); + + // Should end the WorkflowInstance + path = workflowEngine.signal(pathId, null); + assertEquals(pathId, path.getId()); + assertNull(path.getNode()); + assertEquals(pathId, path.getInstance().getId()); + assertFalse(path.isActive()); + } + + @Test + public void testCancelWorkflow() throws Exception + { + WorkflowDefinition def = deployTestAdhocDefinition(); + + ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId())); + + // Validate if a workflow exists + List instances = workflowEngine.getActiveWorkflows(def.getId()); + assertNotNull(instances); + assertEquals(1, instances.size()); + assertEquals(processInstance.getId(), BPMEngineRegistry.getLocalId(instances.get(0).getId())); + + // Call cancel method on component + WorkflowInstance cancelledWorkflow = workflowEngine.cancelWorkflow(instances.get(0).getId()); + assertFalse(cancelledWorkflow.isActive()); + + instances = workflowEngine.getActiveWorkflows(def.getId()); + assertNotNull(instances); + assertEquals(0, instances.size()); + + // Histrotic process instance shouldn't be present + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstance.getProcessInstanceId()) + .singleResult(); + + assertNull(historicProcessInstance); + } + + @Test + public void testCancelUnexistingWorkflow() throws Exception + { + try + { + String globalId = workflowEngine.createGlobalId("unexistingWorkflowId"); + workflowEngine.cancelWorkflow(globalId); + fail("Exception expected"); + } + catch(WorkflowException e) + { + // Inore this + } + } + + @Test + public void testDeleteWorkflow() throws Exception + { + WorkflowDefinition def = deployTestAdhocDefinition(); + + ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId())); + + // Validate if a workflow exists + List instances = workflowEngine.getActiveWorkflows(def.getId()); + assertNotNull(instances); + assertEquals(1, instances.size()); + assertEquals(processInstance.getId(), BPMEngineRegistry.getLocalId(instances.get(0).getId())); + + // Call delete method on component + workflowEngine.deleteWorkflow(instances.get(0).getId()); + + instances = workflowEngine.getActiveWorkflows(def.getId()); + assertNotNull(instances); + assertEquals(0, instances.size()); + + // Historic process instance shouldn't be present + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstance.getProcessInstanceId()) + .singleResult(); + + assertNull(historicProcessInstance); + } + + @Test + public void testDeleteUnexistingWorkflow() throws Exception + { + try + { + String globalId = workflowEngine.createGlobalId("unexistingWorkflowId"); + workflowEngine.deleteWorkflow(globalId); + fail("Exception expected"); + } + catch(WorkflowException e) + { + // Inore this + } + } + + @Test + public void testGetActiveWorkflows() throws Exception + { + WorkflowDefinition def = deployTestAdhocDefinition(); + String activitiProcessDefinitionId = BPMEngineRegistry.getLocalId(def.getId()); + + ProcessInstance activeInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance completedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance cancelledInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance deletedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + + // Complete completedProcessInstance. + String completedId = completedInstance.getId(); + boolean isActive = true; + while (isActive) + { + Execution execution = runtime.createExecutionQuery() + .processInstanceId(completedId) + .singleResult(); + runtime.signal(execution.getId()); + ProcessInstance instance = runtime.createProcessInstanceQuery() + .processInstanceId(completedId) + .singleResult(); + isActive = instance != null; + } + + // Deleted and canceled instances shouldn't be returned + workflowEngine.cancelWorkflow(workflowEngine.createGlobalId(cancelledInstance.getId())); + workflowEngine.deleteWorkflow(workflowEngine.createGlobalId(deletedInstance.getId())); + + // Validate if a workflow exists + List instances = workflowEngine.getActiveWorkflows(def.getId()); + assertNotNull(instances); + assertEquals(1, instances.size()); + String instanceId = instances.get(0).getId(); + assertEquals(activeInstance.getId(), BPMEngineRegistry.getLocalId(instanceId)); + } + + @Test + public void testGetCompletedWorkflows() throws Exception + { + WorkflowDefinition def = deployTestAdhocDefinition(); + String activitiProcessDefinitionId = BPMEngineRegistry.getLocalId(def.getId()); + + runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance completedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance cancelledInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance deletedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + + // Complete completedProcessInstance. + String completedId = completedInstance.getId(); + boolean isActive = true; + while (isActive) + { + Execution execution = runtime.createExecutionQuery() + .processInstanceId(completedId) + .singleResult(); + runtime.signal(execution.getId()); + ProcessInstance instance = runtime.createProcessInstanceQuery() + .processInstanceId(completedId) + .singleResult(); + isActive = instance != null; + } + + // Deleted and canceled instances shouldn't be returned + workflowEngine.cancelWorkflow(workflowEngine.createGlobalId(cancelledInstance.getId())); + workflowEngine.deleteWorkflow(workflowEngine.createGlobalId(deletedInstance.getId())); + + // Validate if a workflow exists + List instances = workflowEngine.getCompletedWorkflows(def.getId()); + assertNotNull(instances); + assertEquals(1, instances.size()); + String instanceId = instances.get(0).getId(); + assertEquals(completedId, BPMEngineRegistry.getLocalId(instanceId)); + } + + @Test + public void testGetWorkflows() throws Exception + { + WorkflowDefinition def = deployTestAdhocDefinition(); + String activitiProcessDefinitionId = BPMEngineRegistry.getLocalId(def.getId()); + + ProcessInstance activeInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance completedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance cancelledInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + ProcessInstance deletedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId); + + // Complete completedProcessInstance. + String completedId = completedInstance.getId(); + boolean isActive = true; + while (isActive) + { + Execution execution = runtime.createExecutionQuery() + .processInstanceId(completedId) + .singleResult(); + runtime.signal(execution.getId()); + ProcessInstance instance = runtime.createProcessInstanceQuery() + .processInstanceId(completedId) + .singleResult(); + isActive = instance != null; + } + + // Deleted and canceled instances shouldn't be returned + workflowEngine.cancelWorkflow(workflowEngine.createGlobalId(cancelledInstance.getId())); + workflowEngine.deleteWorkflow(workflowEngine.createGlobalId(deletedInstance.getId())); + + // Validate if a workflow exists + List instances = workflowEngine.getWorkflows(def.getId()); + assertNotNull(instances); + assertEquals(2, instances.size()); + String instanceId = instances.get(0).getId(); + assertEquals(activeInstance.getId(), BPMEngineRegistry.getLocalId(instanceId)); + instanceId = instances.get(1).getId(); + assertEquals(completedId, BPMEngineRegistry.getLocalId(instanceId)); + } + + @Test + public void testGetWorkflowById() throws Exception + { + WorkflowDefinition def = deployTestAdhocDefinition(); + + Date startTime = new SimpleDateFormat("dd-MM-yyy hh:mm:ss").parse("01-01-2011 12:11:10"); + ClockUtil.setCurrentTime(startTime); + + // Add some variables which should be used in the WorkflowInstance + Map variables = new HashMap(); + + Date dueDate = Calendar.getInstance().getTime(); + putVariable(variables, WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + putVariable(variables, WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "I'm the description"); + putVariable(variables, WorkflowModel.PROP_CONTEXT, new ActivitiScriptNode(testWorkflowContext, serviceRegistry)); + putVariable(variables, WorkflowModel.ASSOC_PACKAGE, new ActivitiScriptNode(testWorkflowPackage, serviceRegistry)); + putVariable(variables, WorkflowModel.PROP_WORKFLOW_PRIORITY, 3); + variables.put(WorkflowConstants.PROP_INITIATOR, new ActivitiScriptNode(adminHomeNode, serviceRegistry)); + + ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()), variables); + String globalProcessInstanceId = BPMEngineRegistry.createGlobalId( + ActivitiConstants.ENGINE_ID, processInstance.getProcessInstanceId()); + + WorkflowInstance workflowInstance = workflowEngine.getWorkflowById(globalProcessInstanceId); + assertNotNull(workflowInstance); + + assertEquals(globalProcessInstanceId, workflowInstance.getId()); + assertNull(workflowInstance.getEndDate()); + assertTrue(workflowInstance.isActive()); + assertEquals("I'm the description", workflowInstance.getDescription()); + assertEquals(dueDate, workflowInstance.getDueDate()); + assertEquals(def.getId(), workflowInstance.getDefinition().getId()); + + assertEquals(adminHomeNode, workflowInstance.getInitiator()); + assertEquals(testWorkflowContext, workflowInstance.getContext()); + assertEquals(testWorkflowPackage, workflowInstance.getWorkflowPackage()); + + assertNotNull(workflowInstance.getPriority()); + assertEquals(3, workflowInstance.getPriority().intValue()); + + assertEquals(startTime, workflowInstance.getStartDate()); + + // Reset current time used in activiti + ClockUtil.setCurrentTime(null); + } + + @Test + public void testGetCompletedWorkflowById() throws Exception + { + WorkflowDefinition def = deployTestAdhocDefinition(); + + Date startTime = new SimpleDateFormat("dd-MM-yyy hh:mm:ss").parse("01-01-2011 01:02:03"); + ClockUtil.setCurrentTime(startTime); + + // Add some variables which should be used in the WorkflowInstance + Map variables = new HashMap(); + + Date dueDate = Calendar.getInstance().getTime(); + putVariable(variables, WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + putVariable(variables, WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "I'm the description"); + putVariable(variables, WorkflowModel.PROP_CONTEXT, new ActivitiScriptNode(testWorkflowContext, serviceRegistry)); + putVariable(variables, WorkflowModel.ASSOC_PACKAGE, new ActivitiScriptNode(testWorkflowPackage, serviceRegistry)); + putVariable(variables, WorkflowModel.PROP_WORKFLOW_PRIORITY, 3); + variables.put(WorkflowConstants.PROP_INITIATOR, new ActivitiScriptNode(adminHomeNode, serviceRegistry)); + + ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()), variables); + String globalProcessInstanceId = BPMEngineRegistry.createGlobalId( + ActivitiConstants.ENGINE_ID, processInstance.getProcessInstanceId()); + + Date endTime = new SimpleDateFormat("dd-MM-yyy hh:mm:ss").parse("01-01-2011 02:03:04"); + ClockUtil.setCurrentTime(endTime); + + // Finish the task + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + taskService.complete(task.getId()); + + WorkflowInstance workflowInstance = workflowEngine.getWorkflowById(globalProcessInstanceId); + assertNotNull(workflowInstance); + + assertEquals(globalProcessInstanceId, workflowInstance.getId()); + assertEquals(endTime, workflowInstance.getEndDate()); + assertFalse(workflowInstance.isActive()); + assertEquals("I'm the description", workflowInstance.getDescription()); + assertEquals(dueDate, workflowInstance.getDueDate()); + assertEquals(def.getId(), workflowInstance.getDefinition().getId()); + + assertEquals(adminHomeNode, workflowInstance.getInitiator()); + assertEquals(testWorkflowContext, workflowInstance.getContext()); + assertEquals(testWorkflowPackage, workflowInstance.getWorkflowPackage()); + + assertNotNull(workflowInstance.getPriority()); + assertEquals(3, workflowInstance.getPriority().intValue()); + assertEquals(startTime, workflowInstance.getStartDate()); + + // Reset current time used in activiti + ClockUtil.setCurrentTime(null); + } + + @Test + public void testGetTimers() throws Exception + { + WorkflowDefinition def = deployTestJobDefinition(); + + ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId())); + + // One timer should be active on workflow + String workflowInstanceId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, + processInstance.getProcessInstanceId()); + + // Query the timer in activity to have reference + Job timerJob = managementService.createJobQuery().timers().processInstanceId(processInstance.getId()).singleResult(); + String globalJobId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, timerJob.getId()); + + // Ask workflowEngine for timers + List timers = workflowEngine.getTimers(workflowInstanceId); + assertNotNull(timers); + assertEquals(1, timers.size()); + + WorkflowTimer timer = timers.get(0); + assertEquals(globalJobId, timer.getId()); + assertEquals(timerJob.getDuedate(), timer.getDueDate()); + + // Check the path of the timer + String expectedTimerPathId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, timerJob.getExecutionId()); + assertNotNull(timer.getPath()); + assertEquals(expectedTimerPathId, timer.getPath().getId()); + + // Check the workflow-instance associated with the path + assertEquals(workflowInstanceId, timer.getPath().getInstance().getId()); + + // Check the task returned by the timer + Task waitingTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + + assertNotNull(timer.getTask()); + assertEquals(BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, waitingTask.getId()), timer.getTask().getId()); + + // When task with boundry-timer on it is finished, no timers should be available + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + taskService.complete(task.getId()); + + timers = workflowEngine.getTimers(workflowInstanceId); + + assertNotNull(timers); + assertEquals(0, timers.size()); + } + + private void putVariable(Map variables, QName varName, Object value) + { + String variableName = mapQNameToName(varName); + variables.put(variableName, value); + } + + private void checkDefinitions(List actual,List startDefs, WorkflowDefinition... expected) + { + assertEquals("The number of process definitions expected does not match the actual number!", startDefs.size() + expected.length, actual.size()); + ArrayList ids = new ArrayList(actual.size()); + for (WorkflowDefinition def : actual) + { + ids.add(def.getId()); + } + for (WorkflowDefinition exp: expected) + { + assertTrue("Results did not contain expected definition: "+exp, ids.contains(exp.getId())); + } + List startIds = new ArrayList(startDefs.size()); + for (WorkflowDefinition def : startDefs) + { + startIds.add(def.getId()); + } + for (WorkflowDefinition exp: expected) + { + assertFalse("Starting Definitions should not contain expected definition: "+exp, startIds.contains(exp.getId())); + } + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java new file mode 100644 index 0000000000..22c645a798 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java @@ -0,0 +1,1957 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.activiti.engine.ActivitiException; +import org.activiti.engine.FormService; +import org.activiti.engine.HistoryService; +import org.activiti.engine.ManagementService; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.TaskService; +import org.activiti.engine.form.StartFormData; +import org.activiti.engine.history.HistoricProcessInstance; +import org.activiti.engine.history.HistoricTaskInstance; +import org.activiti.engine.history.HistoricTaskInstanceQuery; +import org.activiti.engine.impl.RepositoryServiceImpl; +import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior; +import org.activiti.engine.impl.bpmn.deployer.BpmnDeployer; +import org.activiti.engine.impl.form.DefaultTaskFormHandler; +import org.activiti.engine.impl.form.TaskFormHandler; +import org.activiti.engine.impl.identity.Authentication; +import org.activiti.engine.impl.pvm.PvmActivity; +import org.activiti.engine.impl.pvm.PvmTransition; +import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; +import org.activiti.engine.impl.pvm.process.ActivityImpl; +import org.activiti.engine.impl.runtime.TimerEntity; +import org.activiti.engine.repository.Deployment; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.Execution; +import org.activiti.engine.runtime.Job; +import org.activiti.engine.runtime.ProcessInstance; +import org.activiti.engine.task.Task; +import org.activiti.engine.task.TaskQuery; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.workflow.BPMEngine; +import org.alfresco.repo.workflow.WorkflowAuthorityManager; +import org.alfresco.repo.workflow.WorkflowConstants; +import org.alfresco.repo.workflow.WorkflowEngine; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.WorkflowNodeConverter; +import org.alfresco.repo.workflow.WorkflowObjectFactory; +import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery.OrderBy; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.xerces.parsers.DOMParser; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +/** + * @since 4.0 + * @author Nick Smith + * @author Frederik Heremans + * + */ +public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine +{ + // Workflow Component Messages + private static final String ERR_DEPLOY_WORKFLOW = "activiti.engine.deploy.workflow.error"; + private static final String ERR_IS_WORKFLOW_DEPLOYED = "activiti.engine.is.workflow.deployed.error"; + private static final String ERR_UNDEPLOY_WORKFLOW = "activiti.engine.undeploy.workflow.error"; + private static final String ERR_UNDEPLOY_WORKFLOW_UNEXISTING = "activiti.engine.undeploy.workflow.unexisting.error"; + private static final String ERR_GET_WORKFLOW_DEF = "activiti.engine.get.workflow.definition.error"; + private static final String ERR_GET_WORKFLOW_DEF_BY_ID = "activiti.engine.get.workflow.definition.by.id.error"; + private static final String ERR_GET_WORKFLOW_DEF_BY_NAME = "activiti.engine.get.workflow.definition.by.name.error"; + private static final String ERR_GET_ALL_DEFS_BY_NAME = "activiti.engine.get.all.workflow.definitions.by.name.error"; + private static final String ERR_GET_DEF_IMAGE = "activiti.engine.get.workflow.definition.image.error"; + private static final String ERR_GET_DEF_UNEXISTING_IMAGE = "activiti.engine.get.workflow.definition.unexisting.image.error"; +// private static final String ERR_GET_TASK_DEFS = "activiti.engine.get.task.definitions.error"; +// private static final String ERR_GET_PROCESS_DEF = "activiti.engine.get.process.definition.error"; + private static final String ERR_START_WORKFLOW = "activiti.engine.start.workflow.error"; + private static final String ERR_GET_WORKFLOW_INSTS = "activiti.engine.get.workflows.error"; + private static final String ERR_GET_ACTIVE_WORKFLOW_INSTS = "activiti.engine.get.active.workflows.error"; + private static final String ERR_GET_COMPLETED_WORKFLOW_INSTS = "activiti.engine.get.completed.workflows.error"; +// private static final String ERR_GET_WORKFLOW_INST_BY_ID = "activiti.engine.get.workflow.instance.by.id.error"; +// private static final String ERR_GET_PROCESS_INSTANCE = "activiti.engine.get.process.instance.error"; + private static final String ERR_GET_WORKFLOW_PATHS = "activiti.engine.get.workflow.paths.error"; +// private static final String ERR_GET_PATH_PROPERTIES = "activiti.engine.get.path.properties.error"; + private static final String ERR_CANCEL_WORKFLOW = "activiti.engine.cancel.workflow.error"; + private static final String ERR_CANCEL_UNEXISTING_WORKFLOW = "activiti.engine.cancel.unexisting.workflow.error"; + private static final String ERR_DELETE_WORKFLOW = "activiti.engine.delete.workflow.error"; + private static final String ERR_DELETE_UNEXISTING_WORKFLOW = "activiti.engine.delete.unexisting.workflow.error"; +// private static final String ERR_SIGNAL_TRANSITION = "activiti.engine.signal.transition.error"; + protected static final String ERR_FIRE_EVENT_NOT_SUPPORTED = "activiti.engine.event.unsupported"; +// private static final String ERR_FIRE_EVENT = "activiti.engine.fire.event.error"; + private static final String ERR_GET_TASKS_FOR_PATH = "activiti.engine.get.tasks.for.path.error"; +// private static final String ERR_GET_TASKS_FOR_UNEXISTING_PATH = "activiti.engine.get.tasks.for.unexisting.path.error"; + private static final String ERR_GET_TIMERS = "activiti.engine.get.timers.error"; + protected static final String ERR_FIND_COMPLETED_TASK_INSTS = "activiti.engine.find.completed.task.instances.error"; +// private static final String ERR_COMPILE_PROCESS_DEF_zip = "activiti.engine.compile.process.definition.zip.error"; +// private static final String ERR_COMPILE_PROCESS_DEF_XML = "activiti.engine.compile.process.definition.xml.error"; +// private static final String ERR_COMPILE_PROCESS_DEF_UNSUPPORTED = "activiti.engine.compile.process.definition.unsupported.error"; +// private static final String ERR_GET_activiti_ID = "activiti.engine.get.activiti.id.error"; + private static final String ERR_GET_WORKFLOW_TOKEN_INVALID = "activiti.engine.get.workflow.token.invalid"; + private static final String ERR_GET_WORKFLOW_TOKEN_NULL = "activiti.engine.get.workflow.token.is.null"; + private static final String ERR_GET_COMPANY_HOME_INVALID = "activiti.engine.get.company.home.invalid"; + private static final String ERR_GET_COMPANY_HOME_MULTIPLE = "activiti.engine.get.company.home.multiple"; + + // Task Component Messages + private static final String ERR_GET_ASSIGNED_TASKS = "activiti.engine.get.assigned.tasks.error"; + private static final String ERR_GET_POOLED_TASKS = "activiti.engine.get.pooled.tasks.error"; +// private static final String ERR_QUERY_TASKS = "activiti.engine.query.tasks.error"; +// private static final String ERR_GET_TASK_INST = "activiti.engine.get.task.instance.error"; + private static final String ERR_UPDATE_TASK = "activiti.engine.update.task.error"; + private static final String ERR_UPDATE_TASK_UNEXISTING = "activiti.engine.update.task.unexisting.error"; + private static final String ERR_UPDATE_START_TASK = "activiti.engine.update.starttask.illegal.error"; +// private static final String ERR_END_TASK = "activiti.engine.end.task.error"; + private static final String ERR_END_UNEXISTING_TASK = "activiti.engine.end.task.unexisting.error"; + private static final String ERR_GET_TASK_BY_ID = "activiti.engine.get.task.by.id.error"; + private static final String ERR_END_TASK_INVALID_TRANSITION = "activiti.engine.end.task.invalid.transition"; + + private final static String WORKFLOW_TOKEN_SEPERATOR = "\\$"; + + private RepositoryService repoService; + private RuntimeService runtimeService; + private TaskService taskService; + private HistoryService historyService; + private ManagementService managementService; + private FormService formService; + private ActivitiUtil activitiUtil; + + private NodeService nodeService; + private SearchService unprotectedSearchService; + private PersonService personService; + private ActivitiTypeConverter typeConverter; + private ActivitiPropertyConverter propertyConverter; + private WorkflowAuthorityManager authorityManager; + private WorkflowNodeConverter nodeConverter; + private WorkflowObjectFactory factory; + + private MessageService messageService; + private TenantService tenantService; + private NamespaceService namespaceService; + + private String companyHomePath; + private StoreRef companyHomeStore; + + + public ActivitiWorkflowEngine() + { + super(); + } + /** + * {@inheritDoc} + */ + @Override + public void afterPropertiesSet() throws Exception + { + super.afterPropertiesSet(); + this.repoService = activitiUtil.getRepositoryService(); + this.runtimeService = activitiUtil.getRuntimeService(); + this.taskService = activitiUtil.getTaskService(); + this.formService = activitiUtil.getFormService(); + this.historyService = activitiUtil.getHistoryService(); + this.managementService = activitiUtil.getManagementService(); + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowInstance cancelWorkflow(String workflowId) + { + String localId = createLocalId(workflowId); + try + { + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(localId).singleResult(); + if(processInstance == null) + { + throw new WorkflowException(messageService.getMessage(ERR_CANCEL_UNEXISTING_WORKFLOW)); + } + + // TODO: Cancel VS delete? + // Delete the process instance + runtimeService.deleteProcessInstance(processInstance.getId(), WorkflowConstants.PROP_CANCELLED); + + // Convert historic process instance + HistoricProcessInstance deletedInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstance.getId()) + .singleResult(); + WorkflowInstance result = typeConverter.convert(deletedInstance); + + // Delete the historic process instance + historyService.deleteHistoricProcessInstance(deletedInstance.getId()); + + return result; + } + catch(ActivitiException ae) + { + String msg = messageService.getMessage(ERR_CANCEL_WORKFLOW); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowInstance deleteWorkflow(String workflowId) + { + String localId = createLocalId(workflowId); + try + { + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(localId).singleResult(); + if(processInstance == null) + { + throw new WorkflowException(messageService.getMessage(ERR_DELETE_UNEXISTING_WORKFLOW)); + } + + // Delete the process instance + runtimeService.deleteProcessInstance(processInstance.getId(), ActivitiConstants.DELETE_REASON_DELETED); + + // Convert historic process instance + HistoricProcessInstance deletedInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstance.getId()) + .singleResult(); + WorkflowInstance result = typeConverter.convert(deletedInstance); + + // Delete the historic process instance + historyService.deleteHistoricProcessInstance(deletedInstance.getId()); + + return result; + } + catch(ActivitiException ae) + { + String msg = messageService.getMessage(ERR_DELETE_WORKFLOW); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowDeployment deployDefinition(InputStream workflowDefinition, String mimetype) + { + try + { + String resourceName = GUID.generate() + BpmnDeployer.BPMN_RESOURCE_SUFFIX; + Deployment deployment = repoService.createDeployment() + .addInputStream(resourceName, workflowDefinition) + .deploy(); + + // No problems can be added to the WorkflowDeployment, warnings are + // not exposed + return typeConverter.convert(deployment); + } + catch(ActivitiException ae) + { + String msg = messageService.getMessage(ERR_DEPLOY_WORKFLOW); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowPath fireEvent(String pathId, String event) + { + String message = messageService.getMessage(ERR_FIRE_EVENT_NOT_SUPPORTED); + throw new WorkflowException(message); + } + + /** + * {@inheritDoc} + */ + @Override + public List getActiveWorkflows(String workflowDefinitionId) + { + try + { + return getWorkflowInstances(workflowDefinitionId, true); + } + catch(ActivitiException ae) + { + String message = messageService.getMessage(ERR_GET_ACTIVE_WORKFLOW_INSTS, workflowDefinitionId); + throw new WorkflowException(message, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getAllDefinitions() + { + try { + List definitions = repoService.createProcessDefinitionQuery().list(); + return getValidWorkflowDefinitions(definitions); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getAllDefinitionsByName(String workflowName) + { + try + { + String name = workflowName; + if(tenantService.isEnabled()) + { + // When a definition is requested from a wrong domain, an + // AlfrescoRuntimeException will be thrown + name = tenantService.getName(createLocalId(workflowName)); + } else { + name = createLocalId(workflowName); + } + List definitions = repoService.createProcessDefinitionQuery() + .processDefinitionKey(name) + .list(); + return typeConverter.convert(definitions); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_ALL_DEFS_BY_NAME, workflowName); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getCompletedWorkflows(String workflowDefinitionId) + { + try + { + return getWorkflowInstances(workflowDefinitionId, false); + } + catch(ActivitiException ae) + { + String message = messageService.getMessage(ERR_GET_COMPLETED_WORKFLOW_INSTS, workflowDefinitionId); + throw new WorkflowException(message, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowDefinition getDefinitionById(String workflowDefinitionId) + { + try + { + String localId = createLocalId(workflowDefinitionId); + ProcessDefinition procDef = repoService.createProcessDefinitionQuery() + .processDefinitionId(localId ) + .singleResult(); + return typeConverter.convert(procDef); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF_BY_ID, workflowDefinitionId); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowDefinition getDefinitionByName(String workflowName) + { + try + { + String name = workflowName; + if(tenantService.isEnabled()) + { + // When a definition is requested from a wrong domain, and + // ActivitiRuntimeException will be thrown + name = tenantService.getName(createLocalId(workflowName)); + } else { + name = createLocalId(workflowName); + } + + ProcessDefinition definition = repoService.createProcessDefinitionQuery() + .processDefinitionKey(name) + .latestVersion() + .singleResult(); + return typeConverter.convert(definition); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF_BY_NAME, workflowName); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public byte[] getDefinitionImage(String workflowDefinitionId) + { + try + { + String processDefinitionId = createLocalId(workflowDefinitionId); + ProcessDefinition processDefinition = repoService.createProcessDefinitionQuery() + .processDefinitionId(processDefinitionId) + .singleResult(); + + if(processDefinition == null) + { + throw new WorkflowException(messageService.getMessage(ERR_GET_DEF_UNEXISTING_IMAGE, workflowDefinitionId)); + } + + String diagramResourceName = ((ReadOnlyProcessDefinition)processDefinition).getDiagramResourceName(); + if(diagramResourceName != null) + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InputStream resourceInputStream = repoService.getResourceAsStream(processDefinitionId, diagramResourceName); + // Write the resource to a ByteArrayOutpurStream + IOUtils.copy(resourceInputStream, out); + return out.toByteArray(); + } + // No image was found for the process definition + return null; + } + catch(IOException ioe) + { + String msg = messageService.getMessage(ERR_GET_DEF_IMAGE, workflowDefinitionId); + throw new WorkflowException(msg, ioe); + } + catch(ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_DEF_IMAGE, workflowDefinitionId); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getDefinitions() + { + try + { + List definitions = repoService.createProcessDefinitionQuery() + .latestVersion() + .list(); + return getValidWorkflowDefinitions(definitions); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Map getPathProperties(String pathId) + { + String executionId = createLocalId(pathId); + return propertyConverter.getPathProperties(executionId); + } + + /** + * {@inheritDoc} + */ + @Override + public List getTaskDefinitions(String workflowDefinitionId) + { + List defs = new ArrayList(); + String processDefinitionId = createLocalId(workflowDefinitionId); + + // This should return all task definitions, including the start-task + ReadOnlyProcessDefinition processDefinition =((RepositoryServiceImpl)repoService).getDeployedProcessDefinition(processDefinitionId); + + String processName = processDefinition.getName(); + if (tenantService.isEnabled()) + { + // If domain doesn't match, an exception is thrown + tenantService.checkDomain(processName); + } + + // Process start task definition + PvmActivity startEvent = processDefinition.getInitial(); + + String startTaskName = null; + StartFormData startFormData = formService.getStartFormData(processDefinition.getId()); + if(startFormData != null) + { + startTaskName = startFormData.getFormKey(); + } + + // Add start task definition + defs.add(typeConverter.getTaskDefinition(startEvent, startTaskName, processDefinition.getId())); + + // Now, continue through process, finding all user-tasks + Collection taskActivities = findUserTasks(startEvent); + for(PvmActivity act : taskActivities) + { + String formKey = getFormKey(act); + defs.add(typeConverter.getTaskDefinition(act, formKey, processDefinition.getId())); + } + + return defs; + } + + private String getFormKey(PvmActivity act) + { + if(act instanceof ActivityImpl) + { + ActivityImpl actImpl = (ActivityImpl) act; + if (actImpl.getActivityBehavior() instanceof UserTaskActivityBehavior) + { + UserTaskActivityBehavior uta = (UserTaskActivityBehavior) actImpl.getActivityBehavior(); + TaskFormHandler handler = uta.getTaskDefinition().getTaskFormHandler(); + if(handler != null && handler instanceof DefaultTaskFormHandler) + { + // We cast to DefaultTaskFormHandler since we do not configure our own + return ((DefaultTaskFormHandler)handler).getFormKey(); + } + + } + } + return null; + } + + private Collection findUserTasks(PvmActivity startEvent) + { + // Use a linked hashmap to get the task defs in the right order + Map userTasks = new LinkedHashMap(); + + // Start finding activities recursively + findUserTasks(startEvent, userTasks); + + return userTasks.values(); + } + + private void findUserTasks(PvmActivity currentActivity, Map userTasks) + { + // Only process activity if not already present to prevent endless loops + if(!userTasks.containsKey(currentActivity.getId())) + { + if(isUserTask(currentActivity)) + { + userTasks.put(currentActivity.getId(), currentActivity); + } + + // Process outgoing transitions + if(currentActivity.getOutgoingTransitions() != null) + { + for(PvmTransition transition : currentActivity.getOutgoingTransitions()) + { + if(transition.getDestination() != null) + { + findUserTasks(transition.getDestination(), userTasks); + } + } + } + } + } + + private boolean isUserTask(PvmActivity currentActivity) + { + // TODO: Validate if this is the best way to find out an activity is a usertask + String type = (String) currentActivity.getProperty(ActivitiConstants.NODE_TYPE); + if(type != null && type.equals(ActivitiConstants.USER_TASK_NODE_TYPE)) + { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public List getTasksForWorkflowPath(String pathId) + { + try + { + // Extract the Activiti ID from the path + String executionId = getExecutionIdFromPath(pathId); + if(executionId == null) + { + throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, pathId)); + } + + // Check if the execution exists + Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult(); + if(execution == null) + { + throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_NULL, pathId)); + } + + List resultList = new ArrayList(); + + // Check if workflow's start task has been completed. If not, return the virtual task + // Otherwise, just return the runtime activiti tasks + Date startTaskEndDate = (Date) runtimeService.getVariable(execution.getProcessInstanceId(), + ActivitiConstants.PROP_START_TASK_END_DATE); + boolean startTaskEnded = (startTaskEndDate != null); + + if(startTaskEnded) + { + List tasks = taskService.createTaskQuery().executionId(executionId).list(); + for(Task task : tasks) + { + resultList.add(typeConverter.convert(task)); + } + } + else + { + resultList.add(typeConverter.getVirtualStartTask(executionId, true)); + } + return resultList; + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_TASKS_FOR_PATH, pathId); + throw new WorkflowException(msg, ae); + } + } + + + + protected String getExecutionIdFromPath(String workflowPath) { + if(workflowPath != null) + { + String[] parts = workflowPath.split(WORKFLOW_TOKEN_SEPERATOR); + if(parts.length != 2) + { + throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, workflowPath)); + } + return parts[1]; + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public List getTimers(String workflowId) + { + try + { + List timers = new ArrayList(); + + String processInstanceId = createLocalId(workflowId); + List timerJobs = managementService.createJobQuery() + .processInstanceId(processInstanceId) + .timers() + .list(); + + // Only fetch process-instance when timers are available, to prevent extra unneeded query + ProcessInstance jobsProcessInstance = null; + if(timerJobs.size() > 0) + { + // Reuse the process-instance, is used from WorkflowPath creation + jobsProcessInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId).singleResult(); + } + + // Convert the timerJobs to WorkflowTimers + for(Job job : timerJobs) + { + Execution jobExecution = runtimeService.createExecutionQuery() + .executionId(job.getExecutionId()).singleResult(); + + WorkflowPath path = typeConverter.convert(jobExecution, jobsProcessInstance); + WorkflowTask workflowTask = getTaskForTimer(job, jobsProcessInstance, jobExecution); + + WorkflowTimer workflowTimer = factory.createWorkflowTimer(job.getId(), job.getId(), + job.getExceptionMessage(), job.getDuedate(), path, workflowTask); + timers.add(workflowTimer); + } + + return timers; + + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_TIMERS, workflowId); + throw new WorkflowException(msg, ae); + } + } + + private WorkflowTask getTaskForTimer(Job job, ProcessInstance processInstance, Execution jobExecution) { + if (job instanceof TimerEntity) { + + ReadOnlyProcessDefinition def = activitiUtil.getDeployedProcessDefinition(processInstance.getProcessDefinitionId()); + List activeActivityIds = runtimeService.getActiveActivityIds(jobExecution.getId()); + + if(activeActivityIds.size() == 1) + { + PvmActivity targetActivity = def.findActivity(activeActivityIds.get(0)); + if(targetActivity != null) + { + // Only get tasks of active activity is a user-task + String activityType = (String) targetActivity.getProperty(ActivitiConstants.NODE_TYPE); + if(ActivitiConstants.USER_TASK_NODE_TYPE.equals(activityType)) + { + Task task = taskService.createTaskQuery().executionId(job.getExecutionId()).singleResult(); + return typeConverter.convert(task); + } + } + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowInstance getWorkflowById(String workflowId) + { + try + { + WorkflowInstance instance = null; + + String processInstanceId = createLocalId(workflowId); + ProcessInstance processIntance = runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + + if(processIntance != null) + { + instance = typeConverter.convert(processIntance); + } + else + { + // The process instance can be finished + HistoricProcessInstance historicInstance = historyService + .createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + + if(historicInstance != null) + { + instance = typeConverter.convert(historicInstance); + } + } + return instance; + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_DEF); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getWorkflowPaths(String workflowId) + { + try + { + String processInstanceId = createLocalId(workflowId); + + List executions = runtimeService + .createExecutionQuery() + .processInstanceId(processInstanceId) + .list(); + + return typeConverter.convertExecution(executions); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_WORKFLOW_PATHS); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getWorkflows(String workflowDefinitionId) + { + try + { + return getWorkflowInstances(workflowDefinitionId, null); + } + catch(ActivitiException ae) + { + String message = messageService.getMessage(ERR_GET_WORKFLOW_INSTS, workflowDefinitionId); + throw new WorkflowException(message, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDefinitionDeployed(InputStream workflowDefinition, String mimetype) + { + String key = null; + try + { + key = getProcessKey(workflowDefinition); + } + catch (Exception e) + { + throw new WorkflowException(e.getMessage(), e); + } + + try + { + long count = repoService.createProcessDefinitionQuery() + .processDefinitionKey(key ) + .count(); + return count>0; + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_IS_WORKFLOW_DEPLOYED); + throw new WorkflowException(msg, ae); + } + } + + private String getProcessKey(InputStream workflowDefinition) throws Exception + { + try { + InputSource inputSource = new InputSource(workflowDefinition); + DOMParser parser = new DOMParser(); + parser.parse(inputSource); + Document document = parser.getDocument(); + NodeList elemnts = document.getElementsByTagName("process"); + if (elemnts.getLength() < 1) + { + throw new IllegalArgumentException("The input stream does not contain a process definition!"); + } + NamedNodeMap attributes = elemnts.item(0).getAttributes(); + Node idAttrib = attributes.getNamedItem("id"); + if (idAttrib == null) + { + throw new IllegalAccessError("The process definition does not have an id!"); + } + return idAttrib.getNodeValue(); + } + finally + { + workflowDefinition.close(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowPath signal(String pathId, String transitionId) + { + String execId = createLocalId(pathId); + Execution oldExecution = activitiUtil.getExecution(execId); + runtimeService.signal(execId); + Execution execution = activitiUtil.getExecution(execId); + if(execution !=null) + { + return typeConverter.convert(execution); + } + return typeConverter.buildCompletedPath(execId, oldExecution.getProcessInstanceId()); + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowPath startWorkflow(String workflowDefinitionId, Map parameters) + { + try + { + String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + Authentication.setAuthenticatedUserId(currentUserName); + + String processDefId = createLocalId(workflowDefinitionId); + + // Set start task properties. This should be done before instance is started, since it's id will be used + Map variables = propertyConverter.getStartVariables(processDefId, parameters); + variables.put(WorkflowConstants.PROP_CANCELLED, Boolean.FALSE); + + // Add company home + Object companyHome = nodeConverter.convertNode(getCompanyHome()); + variables.put(WorkflowConstants.PROP_COMPANY_HOME, companyHome); + + // Add the initiator + NodeRef initiator = getPersonNodeRef(currentUserName); + if (initiator != null) + { + variables.put(WorkflowConstants.PROP_INITIATOR, nodeConverter.convertNode(initiator)); + // Also add the initiator home reference, if one exists + NodeRef initiatorHome = (NodeRef) nodeService.getProperty(initiator, ContentModel.PROP_HOMEFOLDER); + if (initiatorHome != null) + { + variables.put(WorkflowConstants.PROP_INITIATOR_HOME, nodeConverter.convertNode(initiatorHome)); + } + } + + // Start the process-instance + ProcessInstance instance = runtimeService.startProcessInstanceById(processDefId, variables); + + // Set ID of workflowinstance after ProcessInstance is created + runtimeService.setVariable(instance.getId(), WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID, createGlobalId(instance.getId())); + return typeConverter.convert((Execution)instance); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_START_WORKFLOW, workflowDefinitionId); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void undeployDefinition(String workflowDefinitionId) + { + try + { + String procDefId = createLocalId(workflowDefinitionId); + ProcessDefinition procDef = repoService.createProcessDefinitionQuery() + .processDefinitionId(procDefId) + .singleResult(); + if(procDef == null) + { + String msg = messageService.getMessage(ERR_UNDEPLOY_WORKFLOW_UNEXISTING, workflowDefinitionId); + throw new WorkflowException(msg); + } + String deploymentId = procDef.getDeploymentId(); + repoService.deleteDeployment(deploymentId); + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_UNDEPLOY_WORKFLOW, workflowDefinitionId); + throw new WorkflowException(msg, ae); + } + } + + /** + * Converts the given list of {@link ProcessDefinition}s to a list of {@link WorkflowDefinition}s + * that have a valid domain. If TenantService is disabled, all definitions are converted. + * @param processDefinitions + */ + private List getValidWorkflowDefinitions(List processDefinitions) + { + List resultList = new ArrayList(); + for(ProcessDefinition processDefinition : processDefinitions) + { + if (tenantService.isEnabled()) + { + try + { + tenantService.checkDomain(processDefinition.getName()); + } + catch (RuntimeException re) + { + // Domain doesn't match, skip this process definition + continue; + } + } + resultList.add(typeConverter.convert(processDefinition)); + + } + return resultList; + } + + /** + * Gets the Company Home + * + * @return company home node ref + */ + private NodeRef getCompanyHome() + { + if (tenantService.isEnabled()) + { + try + { + return tenantService.getRootNode(nodeService, unprotectedSearchService, namespaceService, + companyHomePath, nodeService.getRootNode(companyHomeStore)); + } + catch (RuntimeException re) + { + String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_INVALID, companyHomePath); + throw new IllegalStateException(msg, re); + } + } + else + { + List refs = unprotectedSearchService.selectNodes(nodeService.getRootNode(companyHomeStore), + companyHomePath, null, namespaceService, false); + if (refs.size() != 1) + { + String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_MULTIPLE, companyHomePath, refs.size()); + throw new IllegalStateException(msg); + } + return refs.get(0); + } + } + + /** + * Gets an Alfresco Person reference for the given name. + * + * @param name the person name + * @return the Alfresco person. Returns null, if no person is found with the + * given name. + */ + private NodeRef getPersonNodeRef(String name) + { + NodeRef authority = null; + if (name != null) + { + if (personService.personExists(name)) + { + authority = personService.getPerson(name); + } + } + return authority; + } + + /** + * @param propertyConverter the propertyConverter to set + */ + public void setPropertyConverter(ActivitiPropertyConverter propertyConverter) + { + this.propertyConverter = propertyConverter; + } + + /** + * Sets the Node Service + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the Company Home Path + * + * @param companyHomePath + */ + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /** + * Sets the Company Home Store + * + * @param companyHomeStore + */ + public void setCompanyHomeStore(String companyHomeStore) + { + this.companyHomeStore = new StoreRef(companyHomeStore); + } + + /** + * Set the unprotected search service - so we can find the node ref for + * company home when folk do not have read access to company home TODO: + * review use with DC + * + * @param unprotectedSearchService + */ + public void setUnprotectedSearchService(SearchService unprotectedSearchService) + { + this.unprotectedSearchService = unprotectedSearchService; + } + + /** + * Sets the Person Service + * + * @param personService + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * Sets the Authority DAO + /** + * @param authorityManager the authorityManager to set + */ + public void setAuthorityManager(WorkflowAuthorityManager authorityManager) + { + this.authorityManager = authorityManager; + } + + ///////////// Task Component ////////// + + /** + * {@inheritDoc} + */ + @Override + public WorkflowTask endTask(String taskId, String transition) + { + String localTaskId = createLocalId(taskId); + // Check if the task is a virtual start task + if(localTaskId.startsWith(ActivitiConstants.START_TASK_PREFIX)) + { + return endStartTask(taskId, localTaskId, transition); + } + + return endNormalTask(taskId, localTaskId, transition); + } + + private WorkflowTask endNormalTask(String taskId, String localTaskId, String transition) + { + // Retrieve task + Task task = taskService.createTaskQuery().taskId(localTaskId).singleResult(); + + if(task == null) + { + String msg = messageService.getMessage(ERR_END_UNEXISTING_TASK, taskId); + throw new WorkflowException(msg); + } + + // Signal the transition on the task + if (transition != null && + ActivitiConstants.DEFAULT_TRANSITION_NAME.equals(transition)==false) + { + // Only 'Next' is supported as transition. + String msg = messageService.getMessage(ERR_END_TASK_INVALID_TRANSITION, transition, taskId, ActivitiConstants.DEFAULT_TRANSITION_NAME); + throw new WorkflowException(msg); + } + setOutcome(task); + taskService.complete(localTaskId); + // The task should have a historicTaskInstance + HistoricTaskInstance historicTask = historyService.createHistoricTaskInstanceQuery().taskId(task.getId()).singleResult(); + return typeConverter.convert(historicTask); + } + + private void setOutcome(Task task) + { + Map properties = propertyConverter.getTaskProperties(task, false); + QName outcomePropName = (QName) properties.get(WorkflowModel.PROP_OUTCOME_PROPERTY_NAME); + if(outcomePropName !=null) + { + Serializable rawOutcome = properties.get(outcomePropName); + String outcomeValue = ActivitiConstants.DEFAULT_TRANSITION_NAME; + if(rawOutcome != null) + { + outcomeValue = DefaultTypeConverter.INSTANCE.convert(String.class, rawOutcome); + } + String outcomeName = factory.mapQNameToName(WorkflowModel.PROP_OUTCOME); + taskService.setVariableLocal(task.getId(), outcomeName, outcomeValue); + } + } + + private WorkflowTask endStartTask(String taskId, String localTaskId, String transition) + { + // We don't end a task, we set a variable on the process-instance + // to indicate that it's started + String processInstanceId = localTaskId.replace(ActivitiConstants.START_TASK_PREFIX, ""); + + // Set start task end date on the process + runtimeService.setVariable(processInstanceId, ActivitiConstants.PROP_START_TASK_END_DATE, new Date()); + + // Return virtual start task for the execution, it's safe to use the processInstanceId + return typeConverter.getVirtualStartTask(processInstanceId, false); + } + + /** + * {@inheritDoc} + */ + @Override + public List getAssignedTasks(String authority, WorkflowTaskState state) + { + try + { + if(state == WorkflowTaskState.IN_PROGRESS) + { + List tasks = taskService.createTaskQuery() + .taskAssignee(authority) + .list(); + return typeConverter.convert(tasks); + } + else + { + List historicTasks = historyService.createHistoricTaskInstanceQuery() + .taskAssignee(authority) + .list(); + return typeConverter.convert(historicTasks); + } + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_ASSIGNED_TASKS); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getPooledTasks(List authorities) + { + try + { + if(authorities != null && authorities.size() > 0) { + // As an optimisation, we assume the first authority CAN be a user. All the + // others are groups to which the user (or group) belongs. This way, we don't have to + // check for the type of the authority. + + String firstAuthority = authorities.get(0); + // Use a map, can be that a task has multiple candidate-groups, which are inside the list + // of authorities + Map resultingTasks = new HashMap(); + if (authorityManager.isUser(firstAuthority)) + { + // Candidate user + addTasksForCandidateUser(firstAuthority, resultingTasks); + } + else + { + // Candidate group + addTasksForCandidateGroup(firstAuthority, resultingTasks); + } + for(int i=1; i tasks = new ArrayList(); + + // Only tasks that have NO assignee, should be returned + for(Task task : resultingTasks.values()) { + if(task.getAssignee() == null) { + tasks.add(task); + } + } + return typeConverter.convert(tasks); + } + + return Collections.emptyList(); + } + catch(ActivitiException ae) + { + String authorityString = null; + if(authorities != null) + { + authorityString = StringUtils.join(authorities.iterator(), ", "); + } + String msg = messageService.getMessage(ERR_GET_POOLED_TASKS, authorityString); + throw new WorkflowException(msg, ae); + } + } + + private void addTasksForCandidateGroup(String groupName, Map resultingTasks) + { + List tasks = taskService.createTaskQuery().taskCandidateGroup(groupName).list(); + for(Task task : tasks) + { + resultingTasks.put(task.getId(), task); + } + } + + private void addTasksForCandidateUser(String userName, Map resultingTasks) + { + List tasks = taskService.createTaskQuery().taskCandidateUser(userName).list(); + for(Task task : tasks) + { + resultingTasks.put(task.getId(), task); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowTask getTaskById(String taskId) + { + try + { + String localId = createLocalId(taskId); + if(localId.startsWith(ActivitiConstants.START_TASK_PREFIX)) + { + String processInstanceId = localId.replace(ActivitiConstants.START_TASK_PREFIX ,""); + return getVirtualStartTaskForProcessInstance(processInstanceId); + } + else + { + Task task = activitiUtil.getTaskInstance(localId); + if(task != null) + { + return typeConverter.convert(task); + } + HistoricTaskInstance historicTask = activitiUtil.getHistoricTaskInstance(localId); + return typeConverter.convert(historicTask); + } + } + catch (ActivitiException ae) + { + String msg = messageService.getMessage(ERR_GET_TASK_BY_ID); + throw new WorkflowException(msg, ae); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List queryTasks(WorkflowTaskQuery query) + { + // TODO: complete, only small part is implemented + ArrayList result = new ArrayList(); + WorkflowTaskState taskState = query.getTaskState(); + if(WorkflowTaskState.COMPLETED.equals(taskState) == false) + { + result.addAll(queryRuntimeTasks(query)); + } + + // Depending on the state, history should be included/excluded as wel + if(WorkflowTaskState.IN_PROGRESS.equals(taskState) == false) + { + result.addAll(queryHistoricTasks(query)); + result.addAll(queryStartTasks(query)); + } + return result; + } + + private List queryRuntimeTasks(WorkflowTaskQuery query) + { + // Runtime-tasks only exist on process-instances that are active + // so no use in querying runtime tasks if not active + if(!Boolean.FALSE.equals(query.isActive())) + { + // Add task name + TaskQuery taskQuery = taskService.createTaskQuery(); + if(query.getTaskName() != null) { + // Task 'key' is stored as variable on task + String formKey = query.getTaskName().toPrefixString(namespaceService); + taskQuery.taskVariableValueEquals(ActivitiConstants.PROP_TASK_FORM_KEY, formKey); + } + + if(query.getProcessId() != null) { + String processInstanceId = createLocalId(query.getProcessId()); + taskQuery.processInstanceId(processInstanceId); + } + + if(query.getProcessName() != null) + { + // String processName = getProcessNameMTSafe(query.getProcessName()); + // TODO: Method added in http://jira.codehaus.org/browse/ACT-459 + // taskQuery.processDefinitionName(processName); + } + + + if(query.getActorId() != null) { + taskQuery.taskAssignee(query.getActorId()); + } + + if(query.getTaskId() != null) { + String taskId = createLocalId(query.getTaskId()); + taskQuery.taskId(taskId); + } + + // Custom task properties + if(query.getTaskCustomProps() != null) + { + addTaskPropertiesToQuery(query.getTaskCustomProps(), taskQuery); + } + + if(query.getProcessCustomProps() != null) + { + addProcessPropertiesToQuery(query.getProcessCustomProps(), taskQuery); + } + // Add ordering + if (query.getOrderBy() != null) + { + WorkflowTaskQuery.OrderBy[] orderBy = query.getOrderBy(); + orderQuery(taskQuery, orderBy); + } + + List currentTasks = typeConverter.convert(taskQuery.list()); + return currentTasks; + } + return new ArrayList(); + } + + private void addProcessPropertiesToQuery( + Map processCustomProps, TaskQuery taskQuery) { + // TODO: implement + } + + protected String getProcessNameMTSafe(QName processNameQName) + { + String processName = null; + if (tenantService.isEnabled()) + { + QName baseProcessName = tenantService.getBaseName(processNameQName, true); + processName = tenantService.getName(baseProcessName.toPrefixString(namespaceService)); + } + else + { + processName = processNameQName.toPrefixString(namespaceService); + } + return processName; + } + + private void orderQuery(TaskQuery taskQuery, OrderBy[] orderBy) { + for (WorkflowTaskQuery.OrderBy orderByPart : orderBy) + { + if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Asc) + { + taskQuery.orderByTaskAssignee().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Desc) + { + taskQuery.orderByTaskAssignee().desc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Asc) + { + taskQuery.orderByTaskCreateTime().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Desc) + { + taskQuery.orderByTaskCreateTime().desc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Asc) + { + // TODO: order by dueDate? It's a task-variable + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Desc) + { + // TODO: order by duedate? It's a task-variable + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Asc) + { + taskQuery.orderByTaskId().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Desc) + { + taskQuery.orderByTaskId().desc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Asc) + { + taskQuery.orderByTaskName().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Desc) + { + taskQuery.orderByTaskName().desc(); + } + // All workflows are active, no need to order on WorkflowTaskQuery.OrderBy.TaskState_Asc + } + } + + private void orderQuery(HistoricTaskInstanceQuery taskQuery, OrderBy[] orderBy) { + for (WorkflowTaskQuery.OrderBy orderByPart : orderBy) + { + if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Asc) + { + // TODO: add in activiti + // taskQuery.orderByTaskAssignee().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Desc) + { + // TODO: add in activiti + // taskQuery.orderByTaskAssignee().desc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Asc) + { + taskQuery.orderByHistoricActivityInstanceStartTime().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Desc) + { + taskQuery.orderByHistoricActivityInstanceStartTime().desc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Asc) + { + // TODO: order by dueDate? It's a task-variable + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskDue_Desc) + { + // TODO: order by duedate? It's a task-variable + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Asc) + { + // TODO: add in activiti + // taskQuery.orderByTaskId().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Desc) + { + // TODO: add in activiti + // taskQuery.orderByTaskId().desc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Asc) + { + taskQuery.orderByTaskName().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Desc) + { + taskQuery.orderByTaskName().desc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Asc) + { + taskQuery.orderByHistoricTaskInstanceEndTime().asc(); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Desc) + { + taskQuery.orderByHistoricTaskInstanceEndTime().asc(); + } + } + } + + private void addTaskPropertiesToQuery(Map taskCustomProps, TaskQuery taskQuery) + { + for(Entry customProperty : taskCustomProps.entrySet()) + { + String name =factory.mapQNameToName(customProperty.getKey()); + taskQuery.taskVariableValueEquals(name, customProperty.getValue()); + } + } + + private List queryHistoricTasks(WorkflowTaskQuery query) + { + // TODO: Implement complete query + HistoricTaskInstanceQuery historicQuery = historyService + .createHistoricTaskInstanceQuery() + .finished(); + + if(query.getTaskId() != null) { + String taskId = createLocalId(query.getTaskId()); + historicQuery.taskId(taskId); + } + + if(query.getProcessId() != null) + { + String processInstanceId = createLocalId(query.getProcessId()); + historicQuery.processInstanceId(processInstanceId); + } + + if(query.getTaskName() != null) + { + historicQuery.taskDefinitionKey(query.getTaskName().toPrefixString()); + } + + if(query.getActorId() != null) + { + historicQuery.taskAssignee(query.getActorId()); + } + + if(query.getProcessName() != null) + { + // String processName = getProcessNameMTSafe(query.getProcessName()); + // TODO: add to query + } + + // TODO: task properties from history query + // TODO: process properties + + // Order query + if(query.getOrderBy() != null) + { + orderQuery(historicQuery, query.getOrderBy()); + } + + List workflowTasks = new ArrayList(); + List historicTasks = historicQuery.list(); + + for(HistoricTaskInstance historicTask : historicTasks) + { + WorkflowTask wfTask = typeConverter.convert(historicTask); + if(wfTask != null) + { + // Converter returns null if the task belongs to deleted/cancelled WF + workflowTasks.add(wfTask); + } + } + return workflowTasks; + + } + + private List queryStartTasks(WorkflowTaskQuery query) + { + + // TODO: implement further + List startTasks = new ArrayList(); + + String processInstanceId = null; + String taskId = query.getTaskId(); + if(taskId != null ) + { + String localTaskId = createLocalId(taskId); + if(localTaskId.startsWith(ActivitiConstants.START_TASK_PREFIX)) + processInstanceId = localTaskId.substring(ActivitiConstants.START_TASK_PREFIX.length()); + } + else + { + String processId = query.getProcessId(); + if(processId != null) + { + // Start task for a specific process + processInstanceId = createLocalId(processId); + } + } + + // Only return start-task when a process or task id is set + if(processInstanceId != null) + { + // Extract processInstanceId + WorkflowTask workflowTask = getVirtualStartTaskForProcessInstance(processInstanceId); + if(workflowTask != null) + { + boolean startTaskMatches = isStartTaskMatching(workflowTask, query); + if(startTaskMatches) + { + startTasks.add(workflowTask); + } + } + } + return startTasks; + } + + + private boolean isStartTaskMatching(WorkflowTask workflowTask, + WorkflowTaskQuery query) { + + if(query.getActorId() != null && !query.getActorId().equals(workflowTask.getProperties().get(ContentModel.PROP_OWNER))) + { + return false; + } + + if(query.getProcessCustomProps() != null) + { + // Get properties for process instance, based on path of start task, which is process-instance + Map props = getPathProperties(workflowTask.getPath().getId()); + if(!checkPropertiesPresent(query.getProcessCustomProps(), props)) + { + return false; + } + } + + if(query.getProcessId() != null) + { + if(!query.getProcessId().equals(workflowTask.getPath().getInstance().getId())) + { + return false; + } + } + + if(query.getProcessName() != null) + { + String processName = factory.mapQNameToName(query.getProcessName()); + if(!processName.equals(workflowTask.getPath().getInstance().getDefinition().getName())) + { + return false; + } + } + + if(query.getTaskCustomProps() != null) + { + if(!checkPropertiesPresent(query.getTaskCustomProps(), workflowTask.getProperties())) + { + return false; + } + } + + if(query.getTaskId() != null) + { + if(!query.getTaskId().equals(workflowTask.getId())) + { + return false; + } + } + + if(query.getTaskName() != null) + { + if(!query.getTaskName().equals(workflowTask.getDefinition().getMetadata().getName())) + { + return false; + } + } + + if(query.getTaskState() != null) + { + if(!query.getTaskState().equals(workflowTask.getState())) + { + return false; + } + } + + // If we fall through, start task matches the query + return true; + } + + private boolean checkPropertiesPresent(Map expectedProperties, Map props) + { + for(Map.Entry entry : expectedProperties.entrySet()) + { + if(props.containsKey(entry.getValue())) + { + Object requiredValue = entry.getValue(); + Object actualValue = props.get(entry.getKey()); + + if(requiredValue != null) + { + if(!requiredValue.equals(actualValue)) + { + return false; + } + } + else + { + if(actualValue != null) + { + return false; + } + } + } + if(entry.getValue() != null) + { + // If variable is not found and required value is non null, start-task doesn't match + return false; + } + } + + return true; + } + + + /** + * {@inheritDoc} + */ + @Override + public WorkflowTask getStartTask(String workflowInstanceId) + { + String instanceId = createLocalId(workflowInstanceId); + return getVirtualStartTaskForProcessInstance(instanceId); + } + + public WorkflowTask getVirtualStartTaskForProcessInstance(String processInstanceId) + { + + ProcessInstance runningInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + + if(runningInstance != null) + { + // Check the process instance variable to see if start-task has been completed + Date startTaskEndDate = (Date) runtimeService.getVariable(runningInstance.getProcessInstanceId(), + ActivitiConstants.PROP_START_TASK_END_DATE); + boolean startTaskEnded = (startTaskEndDate != null); + return typeConverter.getVirtualStartTask(runningInstance.getId(), !startTaskEnded); + } + else + { + HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + + return typeConverter.getVirtualStartTask(hpi); + } + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowTask startTask(String taskId) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowTask suspendTask(String taskId) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public WorkflowTask updateTask(String taskId, Map properties, Map> add, + Map> remove) + { + try + { + if(taskId.startsWith(ActivitiConstants.START_TASK_PREFIX)) + { + // Known limitation, start-tasks cannot be updated + String msg = messageService.getMessage(ERR_UPDATE_START_TASK, taskId); + throw new WorkflowException(msg); + } + + Task task = taskService.createTaskQuery().taskId(createLocalId(taskId)).singleResult(); + if(task != null) + { + Task updatedTask = propertyConverter.updateTask(task, properties, add, remove); + return typeConverter.convert(updatedTask); + } + else + { + String msg = messageService.getMessage(ERR_UPDATE_TASK_UNEXISTING, taskId); + throw new WorkflowException(msg); + } + } + catch(ActivitiException ae) + { + String msg = messageService.getMessage(ERR_UPDATE_TASK, taskId); + throw new WorkflowException(msg, ae); + } + } + + private List getWorkflowInstances(String workflowDefinitionId, Boolean isActive) + { + String processDefId = createLocalId(workflowDefinitionId); + LinkedList results = new LinkedList(); + if(Boolean.FALSE.equals(isActive)==false) + { + List activeInstances = runtimeService.createProcessInstanceQuery() + .processDefinitionId(processDefId) + .list(); + List activeResults = typeConverter.convert(activeInstances); + results.addAll(activeResults); + } + if(Boolean.TRUE.equals(isActive)==false) + { + List completedInstances = historyService.createHistoricProcessInstanceQuery() + .processDefinitionId(processDefId) + .finished() + .list(); + List completedResults = typeConverter.convert(completedInstances); + results.addAll(completedResults); + } + return results; + } + + /** + * @param nodeConverter the nodeConverter to set + */ + public void setNodeConverter(WorkflowNodeConverter nodeConverter) + { + this.nodeConverter = nodeConverter; + } + + + /** + * @param factory the factory to set + */ + public void setFactory(WorkflowObjectFactory factory) + { + this.factory = factory; + } + + /** + * @param messageService the messageService to set + */ + public void setMessageService(MessageService messageService) + { + this.messageService = messageService; + } + + /** + * @param tenantService the tenantService to set + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * @param typeConverter the typeConverter to set + */ + public void setTypeConverter(ActivitiTypeConverter typeConverter) + { + this.typeConverter = typeConverter; + } + + /** + * @param activitiUtil the activitiUtil to set + */ + public void setActivitiUtil(ActivitiUtil activitiUtil) + { + this.activitiUtil = activitiUtil; + } + + /** + * @param namespaceService the namespaceService to set + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManager.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManager.java new file mode 100644 index 0000000000..fc9999d200 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManager.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import org.alfresco.repo.workflow.WorkflowNodeConverter; +import org.alfresco.repo.workflow.WorkflowPropertyHandlerRegistry; +import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; + +/** + * @author Nick + * + */ +public class ActivitiWorkflowManager +{ + private final ActivitiPropertyConverter propertyConverter; + private final WorkflowNodeConverter nodeConverter; + private final WorkflowPropertyHandlerRegistry handlerRegistry; + private final ActivitiWorkflowEngine workflowEngine; + /** + * @param workflowEngine + * @param propertyConverter + * @param handlerRegistry + * @param nodeConverter + */ + public ActivitiWorkflowManager(ActivitiWorkflowEngine workflowEngine, ActivitiPropertyConverter propertyConverter, + WorkflowPropertyHandlerRegistry handlerRegistry, WorkflowNodeConverter nodeConverter) + { + this.workflowEngine = workflowEngine; + this.propertyConverter = propertyConverter; + this.handlerRegistry = handlerRegistry; + this.nodeConverter = nodeConverter; + } + + /** + * @return the propertyConverter + */ + public ActivitiPropertyConverter getPropertyConverter() + { + return propertyConverter; + } + + /** + * @return the nodeConverter + */ + public WorkflowNodeConverter getNodeConverter() + { + return nodeConverter; + } + + /** + * @return the handlerRegistry + */ + public WorkflowPropertyHandlerRegistry getPropertyHandlerRegistry() + { + return handlerRegistry; + } + + /** + * @return the workflowEngine + */ + public ActivitiWorkflowEngine getWorkflowEngine() + { + return workflowEngine; + } + + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java new file mode 100644 index 0000000000..196fb512d4 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import org.activiti.engine.ProcessEngine; +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.DefaultWorkflowPropertyHandler; +import org.alfresco.repo.workflow.WorkflowAuthorityManager; +import org.alfresco.repo.workflow.WorkflowObjectFactory; +import org.alfresco.repo.workflow.WorkflowPropertyHandlerRegistry; +import org.alfresco.repo.workflow.WorkflowQNameConverter; +import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.namespace.NamespaceService; +import org.springframework.beans.factory.FactoryBean; + +/** + * @author Nick + * + */ +public class ActivitiWorkflowManagerFactory implements FactoryBean +{ + // Set fields + private TenantService tenantService; + private MessageService messageService; + private ServiceRegistry serviceRegistry; + private SearchService unprotectedSearchService; + private BPMEngineRegistry bpmEngineRegistry; + private AuthorityDAO authorityDAO; + private NamespaceService namespaceService; + private DictionaryService dictionaryService; + private NodeService nodeService; + private PersonService personService; + + private ProcessEngine processEngine; + + private String engineId; + private String companyHomePath; + private String companyHomeStore; + + /** + * {@inheritDoc} + */ + @Override + public ActivitiWorkflowManager getObject() throws Exception + { + if (messageService ==null) + { + throw new WorkflowException("MessageService not specified"); + } + if (serviceRegistry ==null) + { + throw new WorkflowException("ServiceRegistry not specified"); + } + if (tenantService ==null) + { + throw new WorkflowException("TenantService not specified"); + } + ActivitiNodeConverter nodeConverter = new ActivitiNodeConverter(serviceRegistry); + DefaultWorkflowPropertyHandler defaultPropertyHandler = new DefaultWorkflowPropertyHandler(); + defaultPropertyHandler.setMessageService(messageService); + defaultPropertyHandler.setNodeConverter(nodeConverter); + + WorkflowQNameConverter qNameConverter = new WorkflowQNameConverter(namespaceService); + WorkflowPropertyHandlerRegistry handlerRegistry = new WorkflowPropertyHandlerRegistry(defaultPropertyHandler, qNameConverter); + + WorkflowAuthorityManager authorityManager = new WorkflowAuthorityManager(authorityDAO); + WorkflowObjectFactory factory = new WorkflowObjectFactory(qNameConverter, tenantService, messageService, dictionaryService, engineId); + ActivitiUtil activitiUtil = new ActivitiUtil(processEngine); + ActivitiPropertyConverter propertyConverter = new ActivitiPropertyConverter(activitiUtil, factory, handlerRegistry, authorityManager, messageService, nodeConverter); + ActivitiTypeConverter typeConverter = new ActivitiTypeConverter(processEngine, factory, propertyConverter); + + ActivitiWorkflowEngine workflowEngine = new ActivitiWorkflowEngine(); + workflowEngine.setActivitiUtil(activitiUtil); + workflowEngine.setAuthorityManager(authorityManager); + workflowEngine.setBPMEngineRegistry(bpmEngineRegistry); + workflowEngine.setCompanyHomePath(companyHomePath); + workflowEngine.setCompanyHomeStore(companyHomeStore); + workflowEngine.setEngineId(engineId); + workflowEngine.setFactory(factory); + workflowEngine.setMessageService(messageService); + workflowEngine.setNamespaceService(namespaceService); + workflowEngine.setNodeConverter(nodeConverter); + workflowEngine.setNodeService(nodeService); + workflowEngine.setPersonService(personService); + workflowEngine.setPropertyConverter(propertyConverter); + workflowEngine.setTenantService(tenantService); + workflowEngine.setTypeConverter(typeConverter); + workflowEngine.setUnprotectedSearchService(unprotectedSearchService); + return new ActivitiWorkflowManager(workflowEngine, propertyConverter, handlerRegistry, nodeConverter); + } + + /** + * @param tenantService the tenantService to set + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * @param messageService the messageService to set + */ + public void setMessageService(MessageService messageService) + { + this.messageService = messageService; + } + + /** + * @param serviceRegistry the serviceRegistry to set + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param unprotectedSearchService the unprotectedSearchService to set + */ + public void setUnprotectedSearchService(SearchService unprotectedSearchService) + { + this.unprotectedSearchService = unprotectedSearchService; + } + + /** + * @param bpmEngineRegistry the bpmEngineRegistry to set + */ + public void setBPMEngineRegistry(BPMEngineRegistry bpmEngineRegistry) + { + this.bpmEngineRegistry = bpmEngineRegistry; + } + + /** + * @param processEngine the processEngine to set + */ + public void setProcessEngine(ProcessEngine processEngine) + { + this.processEngine = processEngine; + } + + /** + * @param engineId the engineId to set + */ + public void setEngineId(String engineId) + { + this.engineId = engineId; + } + + /** + * @param companyHomePath the companyHomePath to set + */ + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /** + * @param companyHomeStore the companyHomeStore to set + */ + public void setCompanyHomeStore(String companyHomeStore) + { + this.companyHomeStore = companyHomeStore; + } + + /** + * @param authorityDAO + * the authorityDAO to set + */ + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getObjectType() + { + return ActivitiWorkflowManager.class; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSingleton() + { + return true; + } + + /** + * @param namespaceService the namespaceService to set + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param dictionaryService the dictionaryService to set + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param nodeService the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param personService the personService to set + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowServiceIntegrationTest.java new file mode 100644 index 0000000000..4c9886393a --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowServiceIntegrationTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.workflow.AbstractWorkflowServiceIntegrationTest; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiWorkflowServiceIntegrationTest extends AbstractWorkflowServiceIntegrationTest +{ + public void testOutcome() throws Exception + { + WorkflowDefinition definition = deployDefinition("alfresco/workflow/review.bpmn20.xml"); + + personManager.setUser(USER1); + + // Create workflow parameters + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); // task instance field + + WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); + String instanceId = path.getInstance().getId(); + + WorkflowTask startTask = workflowService.getStartTask(instanceId); + workflowService.endTask(startTask.getId(), null); + + List paths = workflowService.getWorkflowPaths(instanceId); + assertEquals(1, paths.size()); + path = paths.get(0); + + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask reviewTask = tasks.get(0); + + // Set the transition property + QName outcomePropName = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "reviewOutcome"); + Map props = new HashMap(); + props.put(outcomePropName, "Approve"); + workflowService.updateTask(reviewTask.getId(), props, null, null); + + // End task and check outcome property + WorkflowTask result = workflowService.endTask(reviewTask.getId(), null); + Serializable outcome = result.getProperties().get(WorkflowModel.PROP_OUTCOME); + assertEquals("Approve", outcome); + } + + @Override + public void testQueryTasks() { + // TODO: Revive test once all pieces of queryTasks() are finished on ActivitiWorkflowEngine + } + + @Override + protected String getEngine() + { + return ActivitiConstants.ENGINE_ID; + } + + @Override + protected String getTestDefinitionPath() + { + return "activiti/testTransaction.bpmn20.xml"; + } + + @Override + protected String getAdhocDefinitionPath() + { + return "alfresco/workflow/adhoc.bpmn20.xml"; + } + + @Override + protected String getPooledReviewDefinitionPath() + { + return "alfresco/workflow/review-pooled.bpmn20.xml"; + } + + @Override + protected String getTestTimerDefinitionPath() + { + return "activiti/testTimer.bpmn20.xml"; + } + + @Override + protected QName getAdhocProcessName() { + return QName.createQName("activitiAdhoc"); + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/AddTaskListenerParseListener.java b/source/java/org/alfresco/repo/workflow/activiti/AddTaskListenerParseListener.java new file mode 100644 index 0000000000..9d7a7d70f1 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/AddTaskListenerParseListener.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.workflow.activiti; + +import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior; +import org.activiti.engine.impl.bpmn.parser.BpmnParseListener; +import org.activiti.engine.impl.pvm.delegate.ActivityBehavior; +import org.activiti.engine.impl.pvm.delegate.TaskListener; +import org.activiti.engine.impl.pvm.process.ActivityImpl; +import org.activiti.engine.impl.pvm.process.ScopeImpl; +import org.activiti.engine.impl.pvm.process.TransitionImpl; +import org.activiti.engine.impl.repository.ProcessDefinitionEntity; +import org.activiti.engine.impl.util.xml.Element; +import org.activiti.engine.impl.variable.VariableDeclaration; + +/** + * A {@link BpmnParseListener} that adds a start- and endTaskListener to + * all parsed userTasks. + * + * This is used to wire in custom logic when task is created and completed. + * + * @author Frederik Heremans + */ +public class AddTaskListenerParseListener implements BpmnParseListener +{ + private TaskListener completeTaskListener; + private TaskListener createTaskListener; + + @Override + public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) + { + ActivityBehavior activitybehaviour = activity.getActivityBehavior(); + if(activitybehaviour instanceof UserTaskActivityBehavior) + { + UserTaskActivityBehavior userTaskActivity = (UserTaskActivityBehavior) activitybehaviour; + if(createTaskListener != null) + { + userTaskActivity.getTaskDefinition().addTaskListener(TaskListener.EVENTNAME_CREATE, createTaskListener); + } + if(completeTaskListener != null) + { + userTaskActivity.getTaskDefinition().addTaskListener(TaskListener.EVENTNAME_COMPLETE, completeTaskListener); + } + } + } + + @Override + public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition) + { + // Nothing to do here + } + + @Override + public void parseStartEvent(Element startEventElement, ScopeImpl scope, + ActivityImpl startEventActivity) + { + // Nothing to do here + } + + @Override + public void parseExclusiveGateway(Element exclusiveGwElement, ScopeImpl scope, + ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseParallelGateway(Element parallelGwElement, ScopeImpl scope, + ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseScriptTask(Element scriptTaskElement, ScopeImpl scope, ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseServiceTask(Element serviceTaskElement, ScopeImpl scope, ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseTask(Element taskElement, ScopeImpl scope, ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseManualTask(Element manualTaskElement, ScopeImpl scope, ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseEndEvent(Element endEventElement, ScopeImpl scope, ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseBoundaryTimerEventDefinition(Element timerEventDefinition, + boolean interrupting, ActivityImpl timerActivity) + { + // Nothing to do here + } + + @Override + public void parseSubProcess(Element subProcessElement, ScopeImpl scope, ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseCallActivity(Element callActivityElement, ScopeImpl scope, + ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseProperty(Element propertyElement, VariableDeclaration variableDeclaration, + ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseSequenceFlow(Element sequenceFlowElement, ScopeImpl scopeElement, + TransitionImpl transition) + { + // Nothing to do here + } + + @Override + public void parseSendTask(Element sendTaskElement, ScopeImpl scope, ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseBusinessRuleTask(Element businessRuleTaskElement, ScopeImpl scope, + ActivityImpl activity) + { + // Nothing to do here + } + + @Override + public void parseBoundaryErrorEventDefinition(Element errorEventDefinition, + boolean interrupting, ActivityImpl activity, + ActivityImpl nestedErrorEventActivity) + { + // Nothing to do here + } + + @Override + public void parseIntermediateTimerEventDefinition( + Element timerEventDefinition, ActivityImpl timerActivity) + { + // Nothing to do here + } + + @Override + public void parseMultiInstanceLoopCharacteristics(Element activityElement, + Element multiInstanceLoopCharacteristicsElement, + ActivityImpl activity) { + // Nothing to do here + } + + public void setCompleteTaskListener(TaskListener completeTaskListener) + { + this.completeTaskListener = completeTaskListener; + } + + public void setCreateTaskListener(TaskListener createTaskListener) + { + this.createTaskListener = createTaskListener; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java new file mode 100644 index 0000000000..838f271b31 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java @@ -0,0 +1,46 @@ +package org.alfresco.repo.workflow.activiti; + +import java.util.List; + +import org.activiti.engine.impl.jobexecutor.JobHandler; +import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler; +import org.activiti.engine.impl.variable.SerializableType; +import org.activiti.engine.impl.variable.VariableType; +import org.activiti.spring.SpringProcessEngineConfiguration; + +public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfiguration +{ + private List customTypes; + + @Override + protected void initVariableTypes() + { + super.initVariableTypes(); + // Add custom types before SerializableType + if(customTypes != null) + { + int serializableIndex = variableTypes.getTypeIndex(SerializableType.TYPE_NAME); + for(VariableType type : customTypes) { + variableTypes.addType(type, serializableIndex); + } + } + } + + @Override + protected void initJobExecutor() { + super.initJobExecutor(); + + // Get the existing timer-job handler and wrap + // with one that is alfresco-authentication aware + JobHandler jobHandler = jobHandlers.get(TimerExecuteNestedActivityJobHandler.TYPE); + JobHandler wrappingJobHandler = new AuthenticatedTimerJobHandler(jobHandler); + + jobHandlers.put(TimerExecuteNestedActivityJobHandler.TYPE, wrappingJobHandler); + } + + public void setCustomTypes(List customTypes) + { + this.customTypes = customTypes; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java new file mode 100644 index 0000000000..58d96fa3ff --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti; + +import org.activiti.engine.impl.TaskQueryImpl; +import org.activiti.engine.impl.interceptor.CommandContext; +import org.activiti.engine.impl.jobexecutor.JobHandler; +import org.activiti.engine.impl.pvm.PvmActivity; +import org.activiti.engine.impl.runtime.ExecutionEntity; +import org.activiti.engine.task.Task; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; + +/** + * An {@link JobHandler} which executes activiti timer-jobs + * authenticated against Alfresco. It runs the timer execution + * as the task's assignee (if any) when the timer is applied to a + * task. If not, system user is used to execute timer. + * + * It wraps another JobHandler to which the actual execution is delegated to. + * + * @author Frederik Heremans + */ +public class AuthenticatedTimerJobHandler implements JobHandler +{ + private JobHandler wrappedHandler; + + public AuthenticatedTimerJobHandler(JobHandler jobHandler) + { + if(jobHandler == null) + { + throw new IllegalArgumentException("JobHandler to delegate to is required"); + } + this.wrappedHandler = jobHandler; + } + + @Override + public void execute(final String configuration, final ExecutionEntity execution, + final CommandContext commandContext) + { + String userName = null; + + PvmActivity targetActivity = execution.getActivity(); + if(targetActivity != null) + { + // Only try getting active task, if execution timer is waiting on is a userTask + String activityType = (String) targetActivity.getProperty(ActivitiConstants.NODE_TYPE); + if(ActivitiConstants.USER_TASK_NODE_TYPE.equals(activityType)) + { + Task task = new TaskQueryImpl(commandContext) + .executionId(execution.getId()) + .executeSingleResult(commandContext); + + if(task != null && task.getAssignee() != null) + { + userName = task.getAssignee(); + } + } + } + + // When no task assignee is set, use system user to run job + if(userName == null) + { + userName = AuthenticationUtil.getSystemUserName(); + } + + // Execute timer + AuthenticationUtil.runAs(new RunAsWork() + { + @SuppressWarnings("synthetic-access") + public Void doWork() throws Exception + { + wrappedHandler.execute(configuration, execution, commandContext); + return null; + } + }, userName); + } + + @Override + public String getType() { + return wrappedHandler.getType(); + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/activiti.cfg.xml b/source/java/org/alfresco/repo/workflow/activiti/activiti.cfg.xml new file mode 100644 index 0000000000..1fad80053e --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/activiti.cfg.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/activiti.mysql.create.sql b/source/java/org/alfresco/repo/workflow/activiti/activiti.mysql.create.sql new file mode 100644 index 0000000000..d3c8c4c3fd --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/activiti.mysql.create.sql @@ -0,0 +1,235 @@ +create table ACT_GE_PROPERTY ( + NAME_ varchar(255), + VALUE_ varchar(255), + REV_ integer, + primary key (NAME_) +) TYPE=InnoDB; + +insert into ACT_GE_PROPERTY +values ('schema.version', '5.0.beta1', 1); + +insert into ACT_GE_PROPERTY +values ('next.dbid', '1', 1); + +create table ACT_GE_BYTEARRAY ( + ID_ varchar(255), + REV_ integer, + NAME_ varchar(255), + DEPLOYMENT_ID_ varchar(255), + BYTES_ LONGBLOB, + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_RE_DEPLOYMENT ( + ID_ varchar(255), + NAME_ varchar(255), + DEPLOY_TIME_ timestamp, + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_RU_EXECUTION ( + ID_ varchar(255), + REV_ integer, + PROC_INST_ID_ varchar(255), + PARENT_ID_ varchar(255), + PROC_DEF_ID_ varchar(255), + SUPER_EXEC_ varchar(255), + ACTIVITY_ID_ varchar(255), + IS_ACTIVE_ TINYINT, + IS_CONCURRENT_ TINYINT, + IS_SCOPE_ TINYINT, + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_RU_JOB ( + ID_ varchar(255) NOT NULL, + REV_ integer, + TYPE_ varchar(255) NOT NULL, + LOCK_EXP_TIME_ timestamp, + LOCK_OWNER_ varchar(255), + EXCLUSIVE_ boolean, + EXECUTION_ID_ varchar(255), + PROCESS_INSTANCE_ID_ varchar(255), + RETRIES_ integer, + EXCEPTION_ varchar(255), + DUEDATE_ timestamp NULL, + REPEAT_ varchar(255), + HANDLER_TYPE_ varchar(255), + HANDLER_CFG_ varchar(255), + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_ID_GROUP ( + ID_ varchar(255), + REV_ integer, + NAME_ varchar(255), + TYPE_ varchar(255), + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_ID_MEMBERSHIP ( + USER_ID_ varchar(255), + GROUP_ID_ varchar(255), + primary key (USER_ID_, GROUP_ID_) +) TYPE=InnoDB; + +create table ACT_ID_USER ( + ID_ varchar(255), + REV_ integer, + FIRST_ varchar(255), + LAST_ varchar(255), + EMAIL_ varchar(255), + PWD_ varchar(255), + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_RE_PROC_DEF ( + ID_ varchar(255), + NAME_ varchar(255), + KEY_ varchar(255), + VERSION_ integer, + DEPLOYMENT_ID_ varchar(255), + RESOURCE_NAME_ varchar(255), + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_RU_TASK ( + ID_ varchar(255), + REV_ integer, + EXECUTION_ID_ varchar(255), + PROC_INST_ID_ varchar(255), + PROC_DEF_ID_ varchar(255), + NAME_ varchar(255), + DESCRIPTION_ varchar(255), + FORM_ varchar(255), + ASSIGNEE_ varchar(255), + PRIORITY_ integer, + CREATE_TIME_ timestamp, + START_DEADLINE_ timestamp, + COMPLETION_DEADLINE_ timestamp, + SKIPPABLE_ TINYINT, + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_RU_TASKINVOLVEMENT ( + ID_ varchar(255), + REV_ integer, + GROUP_ID_ varchar(255), + TYPE_ varchar(255), + USER_ID_ varchar(255), + TASK_ID_ varchar(255), + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_RU_VARIABLE ( + ID_ varchar(255) not null, + REV_ integer, + TYPE_ varchar(255) not null, + NAME_ varchar(255) not null, + EXECUTION_ID_ varchar(255), + PROC_INST_ID_ varchar(255), + TASK_ID_ varchar(255), + BYTEARRAY_ID_ varchar(255), + DATE_ timestamp, + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(255), + primary key (ID_) +) TYPE=InnoDB; + +create table ACT_HI_PROC_INST ( + ID_ varchar(255) not null, + PROC_INST_ID_ varchar(255) not null, + PROC_DEF_ID_ varchar(255) not null, + START_TIME_ datetime not null, + END_TIME_ datetime, + DURATION_ bigint, + -- TODO: check endStateName length + END_ACT_ID_ varchar(255), + primary key (ID_), + unique (PROC_INST_ID_) +) TYPE=InnoDB; + +create table ACT_HI_ACT_INST ( + ID_ varchar(255) not null, + ACT_ID_ varchar(255) not null, + ACT_NAME_ varchar(255), + ACT_TYPE_ varchar(255) not null, + PROC_INST_ID_ varchar(255) not null, + PROC_DEF_ID_ varchar(255) not null, + START_TIME_ datetime not null, + END_TIME_ datetime, + DURATION_ bigint, + primary key (ID_), + unique (ACT_ID_, PROC_INST_ID_) +) TYPE=InnoDB; + +alter table ACT_GE_BYTEARRAY + add constraint FK_ACT_BYTEARR_DEPL + foreign key (DEPLOYMENT_ID_) + references ACT_RE_DEPLOYMENT (ID_); + +alter table ACT_RU_EXECUTION + add constraint FK_ACT_EXE_PROCINST + foreign key (PROC_INST_ID_) + references ACT_RU_EXECUTION (ID_) on delete cascade on update cascade; + +alter table ACT_RU_EXECUTION + add constraint FK_ACT_EXE_PARENT + foreign key (PARENT_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_EXECUTION + add constraint FK_ACT_EXE_SUPER + foreign key (SUPER_EXEC_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_ID_MEMBERSHIP + add constraint FK_ACT_MEMB_GROUP + foreign key (GROUP_ID_) + references ACT_ID_GROUP (ID_); + +alter table ACT_ID_MEMBERSHIP + add constraint FK_ACT_MEMB_USER + foreign key (USER_ID_) + references ACT_ID_USER (ID_); + +alter table ACT_RU_TASKINVOLVEMENT + add constraint FK_ACT_TSKASS_TASK + foreign key (TASK_ID_) + references ACT_RU_TASK (ID_); + +alter table ACT_RU_TASK + add constraint FK_ACT_TASK_EXEC + foreign key (EXECUTION_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_TASK + add constraint FK_ACT_TASK_PROCINST + foreign key (PROC_INST_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_TASK + add constraint FK_ACT_TASK_PROCDEF + foreign key (PROC_DEF_ID_) + references ACT_RE_PROC_DEF (ID_); + +alter table ACT_RU_VARIABLE + add constraint FK_ACT_VAR_TASK + foreign key (TASK_ID_) + references ACT_RU_TASK (ID_); + +alter table ACT_RU_VARIABLE + add constraint FK_ACT_VAR_EXE + foreign key (EXECUTION_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_VARIABLE + add constraint FK_ACT_VAR_PROCINST + foreign key (PROC_INST_ID_) + references ACT_RU_EXECUTION(ID_); + +alter table ACT_RU_VARIABLE + add constraint FK_ACT_VAR_BYTEARRAY + foreign key (BYTEARRAY_ID_) + references ACT_GE_BYTEARRAY (ID_); \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/adhoc.bpmn20.xml b/source/java/org/alfresco/repo/workflow/activiti/adhoc.bpmn20.xml new file mode 100644 index 0000000000..f714516143 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/adhoc.bpmn20.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + Perform some arbitrary human task. + + + + ${taskAssignee} + + + + + + + + + Verify the arbitrary task was completed. + + + + ${initiator} + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/listener/ScriptExecutionListener.java b/source/java/org/alfresco/repo/workflow/activiti/listener/ScriptExecutionListener.java new file mode 100644 index 0000000000..d1b53eeefa --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/listener/ScriptExecutionListener.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.listener; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.activiti.engine.delegate.DelegateExecution; +import org.activiti.engine.impl.pvm.delegate.ExecutionListener; +import org.activiti.engine.impl.pvm.delegate.ExecutionListenerExecution; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.workflow.WorkflowQNameConverter; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; +import org.alfresco.repo.workflow.activiti.ActivitiScriptNode; +import org.alfresco.repo.workflow.activiti.script.ActivitiScriptBase; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; + +public class ScriptExecutionListener extends ActivitiScriptBase implements ExecutionListener +{ + private static final String DELETED_FLAG = "deleted"; + private static final String CANCELLED_FLAG = "cancelled"; + + @Override + public void notify(ExecutionListenerExecution execution) throws Exception { + if(script != null) + { + String scriptString = getStringValue(script, execution); + String scriptProcessorName = getStringValue(scriptProcessor, execution); + String runAsUser = getStringValue(runAs, execution); + + // Make sure there is an authenticated user for the current thread, so when + // the script is executed using no 'runAs' from a job-executor thread, the workflow + // owner us used + boolean clearAuthenticationContext = checkFullyAuthenticatedUser(execution); + + // Get all activiti-defined objects + Map scriptModel = getInputMap(execution, runAsUser); + + // Add core alfresco objects to the input-map + getServiceRegistry().getScriptService().buildCoreModel(scriptModel); + + try + { + Object scriptOutput = executeScript(scriptString, scriptModel, scriptProcessorName, runAsUser); + + // TODO: What to do with the script-output? + if(scriptOutput != null) + { + // delegateTask.setVariableLocal("scriptOutput", scriptOutput); + } + } + finally + { + if(clearAuthenticationContext) + { + // If the current user has been set to the Task's assignee, we should clear it agian + AuthenticationUtil.clearCurrentSecurityContext(); + } + } + } + else + { + throw new IllegalArgumentException("The field 'script' should be set on the TaskListener"); + } + } + + protected Map getInputMap(ExecutionListenerExecution execution, String runAsUser) + { + HashMap scriptModel = new HashMap(1); + + // Add current logged-in user and it's user home + ActivitiScriptNode personNode = getPersonNode(runAsUser); + if(personNode != null) + { + ServiceRegistry registry = getServiceRegistry(); + scriptModel.put(PERSON_BINDING_NAME, personNode); + NodeRef userHomeNode = (NodeRef) registry.getNodeService().getProperty(personNode.getNodeRef(), ContentModel.PROP_HOMEFOLDER); + if (userHomeNode != null) + { + scriptModel.put(USERHOME_BINDING_NAME, new ActivitiScriptNode(userHomeNode, registry)); + } + } + + // Add activiti-specific objects + scriptModel.put(EXECUTION_BINDING_NAME, execution); + + // Add all workflow variables to model + Map variables = execution.getVariables(); + + for(Entry varEntry : variables.entrySet()) + { + scriptModel.put(varEntry.getKey(), varEntry.getValue()); + } + + // Add deleted/cancelled flags + boolean cancelled = false; + boolean deleted = false; + + if(ActivitiConstants.DELETE_REASON_DELETED.equals(execution.getDeleteReason())) + { + deleted = true; + } + else if(ActivitiConstants.DELETE_REASON_CANCELLED.equals(execution.getDeleteReason())) + { + cancelled = true; + } + scriptModel.put(DELETED_FLAG, deleted); + scriptModel.put(CANCELLED_FLAG, cancelled); + + return scriptModel; + } + + /** + * Checks a valid Fully Authenticated User is set. + * If none is set then attempts to set the workflow owner + * @param execution the execution + * @return true if the Fully Authenticated User was changed, otherwise false. + */ + private boolean checkFullyAuthenticatedUser(final DelegateExecution execution) { + if(AuthenticationUtil.getFullyAuthenticatedUser() == null) + { + NamespaceService namespaceService = getServiceRegistry().getNamespaceService(); + WorkflowQNameConverter qNameConverter = new WorkflowQNameConverter(namespaceService); + String ownerVariableName = qNameConverter.mapQNameToName(ContentModel.PROP_OWNER); + + String userName = (String) execution.getVariable(ownerVariableName); + if (userName != null) + { + AuthenticationUtil.setFullyAuthenticatedUser(userName); + return true; + } + } + return false; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiDescriptionPropertyHandler.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiDescriptionPropertyHandler.java new file mode 100644 index 0000000000..de3ac6d9cd --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiDescriptionPropertyHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.properties; + +import java.io.Serializable; + +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiDescriptionPropertyHandler extends ActivitiTaskPropertyHandler +{ + + /** + * {@inheritDoc} + */ + + @Override + protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value) + { + checkType(key, value, String.class); + task.setDescription((String) value); + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + @Override + protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value) + { + checkType(key, value, String.class); + task.setDescription((String) value); + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + + @Override + protected QName getKey() + { + return WorkflowModel.PROP_DESCRIPTION; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiOwnerPropertyHandler.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiOwnerPropertyHandler.java new file mode 100644 index 0000000000..e1188f8511 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiOwnerPropertyHandler.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.properties; + +import java.io.Serializable; + +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.task.Task; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiOwnerPropertyHandler extends ActivitiTaskPropertyHandler +{ + + /** + * {@inheritDoc} + */ + @Override + protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value) + { + //Task assignment needs to be done after setting all properties + // so it is handled in ActivitiPropertyConverter. + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + @Override + protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value) + { + checkType(key, value, String.class); + String assignee = (String) value; + String currentAssignee = task.getAssignee(); + // Only set the assignee if the value has changes to prevent + // triggering assignementhandlers when not needed + if (currentAssignee == null || !currentAssignee.equals(assignee)) + { + task.setAssignee(assignee); + } + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + + @Override + protected QName getKey() + { + return ContentModel.PROP_OWNER; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPackagePropertyHandler.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPackagePropertyHandler.java new file mode 100644 index 0000000000..6558ce9b7d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPackagePropertyHandler.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.properties; + +import java.io.Serializable; + +import org.activiti.engine.RuntimeService; +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiPackagePropertyHandler extends ActivitiTaskPropertyHandler +{ + private static final String PCKG_KEY = "bpm_package"; + + private RuntimeService runtimeService; + + /** + * {@inheritDoc} + */ + @Override + protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value) + { + return handlePackage(value, task.getProcessInstanceId()); + } + + /** + * {@inheritDoc} + */ + @Override + protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value) + { + return handlePackage(value, task.getProcessInstanceId()); + } + + private Object handlePackage(Serializable value, String processId) + { + Object currentPckg = runtimeService.getVariableLocal(processId, PCKG_KEY); + // Do not change package if one already exists! + if(currentPckg == null) + { + if(value instanceof NodeRef) + { + return nodeConverter.convertNode((NodeRef)value); + } + else + { + throw getInvalidPropertyValueException(WorkflowModel.ASSOC_PACKAGE, value); + } + } + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + + @Override + protected QName getKey() + { + return WorkflowModel.ASSOC_PACKAGE; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPooledActorsPropertyHandler.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPooledActorsPropertyHandler.java new file mode 100644 index 0000000000..d1e31af19b --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPooledActorsPropertyHandler.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.properties; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.activiti.engine.TaskService; +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.impl.task.IdentityLinkEntity; +import org.activiti.engine.impl.task.TaskEntity; +import org.activiti.engine.task.IdentityLink; +import org.activiti.engine.task.IdentityLinkType; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.WorkflowAuthorityManager; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiPooledActorsPropertyHandler extends ActivitiTaskPropertyHandler +{ + private TaskService taskService; + private WorkflowAuthorityManager authorityManager; + + /** + * {@inheritDoc} + */ + @Override + protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value) + { + List links = taskService.getIdentityLinksForTask(task.getId()); + UserAndGroupUpdates updates = getUserAndGroupUpdates(value, links); + updateTaskCandidates(task.getId(), updates); + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + @Override + protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value) + { + List links = ((TaskEntity)task).getIdentityLinks(); + UserAndGroupUpdates updates = getUserAndGroupUpdates(value, links); + updateTaskCandidates(task, updates); + return DO_NOT_ADD; + } + + @SuppressWarnings("unchecked") + private Collection getNodes(Serializable value) + { + if(value instanceof Collection) + { + return (Collection) value; + } + if(value instanceof NodeRef) + { + return Collections.singletonList((NodeRef)value); + } + throw getInvalidPropertyValueException(WorkflowModel.ASSOC_POOLED_ACTORS, value); + } + + private void updateTaskCandidates(String taskId, UserAndGroupUpdates updates) + { + // Only new candidates are present in pooledUsers and pooledGroups, create Links for these + for(String user : updates.getUsers()) + { + taskService.addCandidateUser(taskId, user); + } + for(String group : updates.getGroups()) + { + taskService.addCandidateGroup(taskId, group); + } + + + // Remove all candidates which have been removed + for(IdentityLink link : updates.getLinksToRemove()) + { + if(link.getUserId() != null) + { + taskService.deleteUserIdentityLink(link.getTaskId(), + link.getUserId(), link.getType()); + } + else + { + taskService.deleteGroupIdentityLink(link.getTaskId(), + link.getGroupId(), link.getType()); + } + } + } + + private void updateTaskCandidates(DelegateTask task, UserAndGroupUpdates updates) + { + // Only new candidates are present in pooledUsers and pooledGroups, create Links for these + for(String user : updates.getUsers()) + { + task.addCandidateUser( user); + } + for(String group : updates.getGroups()) + { + task.addCandidateGroup( group); + } + + // Remove all candidates which have been removed + for(IdentityLink link : updates.linksToRemove) + { + if(link.getUserId() != null) + { + task.deleteUserIdentityLink(link.getUserId(), link.getType()); + } + else + { + task.deleteGroupIdentityLink(link.getGroupId(), link.getType()); + } + } + } + + /** + * Returns a DTO containing the users and groups to add and the links to remove. + * + * @param actors + * @param links + * @return + */ + private UserAndGroupUpdates getUserAndGroupUpdates(Serializable value, Collection links) + { + Collection actors = getNodes(value); + + List users = new ArrayList(); + List groups = new ArrayList(); + for (NodeRef actor : actors) + { + String authorityName = authorityManager.getAuthorityName(actor); + List pooledAuthorities = authorityManager.isUser(authorityName)? users : groups; + pooledAuthorities.add(authorityName); + } + + // Removes all users and groups that are already links. + // Also records links that are not part of the new set of users and + // groups, in order to delete them. + List linksToRemove = new LinkedList(); + for (IdentityLink link : links) + { + if(IdentityLinkType.CANDIDATE.equals(link.getType())) { + String userId = link.getUserId(); + if(userId!=null) + { + if(users.remove(userId)==false) + { + linksToRemove.add(link); + } + } + else + { + String groupId = link.getGroupId(); + if(groupId!=null && groups.remove(groupId)==false) + { + linksToRemove.add(link); + } + } + } + } + return new UserAndGroupUpdates(users, groups, linksToRemove); + } + + /** + * {@inheritDoc} + */ + @Override + protected QName getKey() + { + return WorkflowModel.ASSOC_POOLED_ACTORS; + } + + /** + * @param taskService the taskService to set + */ + public void setTaskService(TaskService taskService) + { + this.taskService = taskService; + } + + private static class UserAndGroupUpdates + { + private final List users; + private final List groups; + + private final List linksToRemove; + + public UserAndGroupUpdates(List users, List groups, List linksToRemove) + { + this.users = users; + this.groups = groups; + this.linksToRemove = linksToRemove; + } + + /** + * @return the users + */ + public List getUsers() + { + return users; + } + + /** + * @return the groups + */ + public List getGroups() + { + return groups; + } + + /** + * @return the linksToRemove + */ + public List getLinksToRemove() + { + return linksToRemove; + } + + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPriorityPropertyHandler.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPriorityPropertyHandler.java new file mode 100644 index 0000000000..e4a8603020 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPriorityPropertyHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.properties; + +import java.io.Serializable; + +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.task.Task; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiPriorityPropertyHandler extends ActivitiTaskPropertyHandler +{ + /** + * {@inheritDoc} + */ + + @Override + protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value) + { + checkType(key, value, Integer.class); + task.setPriority((Integer) value); + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + + @Override + protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value) + { + checkType(key, value, Integer.class); + task.setPriority((Integer) value); + return DO_NOT_ADD; + } + + /** + * {@inheritDoc} + */ + + @Override + protected QName getKey() + { + return WorkflowModel.PROP_PRIORITY; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java new file mode 100644 index 0000000000..6db8170807 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java @@ -0,0 +1,938 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.properties; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.activiti.engine.TaskService; +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.history.HistoricActivityInstance; +import org.activiti.engine.history.HistoricDetail; +import org.activiti.engine.history.HistoricDetailQuery; +import org.activiti.engine.history.HistoricProcessInstance; +import org.activiti.engine.history.HistoricTaskInstance; +import org.activiti.engine.history.HistoricVariableUpdate; +import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; +import org.activiti.engine.impl.task.TaskEntity; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.task.IdentityLink; +import org.activiti.engine.task.IdentityLinkType; +import org.activiti.engine.task.Task; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.workflow.WorkflowAuthorityManager; +import org.alfresco.repo.workflow.WorkflowConstants; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.WorkflowNodeConverter; +import org.alfresco.repo.workflow.WorkflowObjectFactory; +import org.alfresco.repo.workflow.WorkflowPropertyHandlerRegistry; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; +import org.alfresco.repo.workflow.activiti.ActivitiScriptNode; +import org.alfresco.repo.workflow.activiti.ActivitiTaskTypeManager; +import org.alfresco.repo.workflow.activiti.ActivitiUtil; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassAttributeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; + +/** + * @since 4.0 + * @author Nick Smith + * + */ +public class ActivitiPropertyConverter +{ + private static final String ERR_CONVERT_VALUE = "activiti.engine.convert.value.error"; + private static final String ERR_SET_TASK_PROPS_INVALID_VALUE = "activiti.engine.set.task.properties.invalid.value"; + private static final String ERR_MANDATORY_TASK_PROPERTIES_MISSING = "activiti.engine.mandatory.properties.missing"; + + private final ActivitiTaskTypeManager typeManager; + private final MessageService messageService; + private final WorkflowObjectFactory factory; + private final WorkflowAuthorityManager authorityManager; + private final WorkflowPropertyHandlerRegistry handlerRegistry; + private final WorkflowNodeConverter nodeConverter; + + private final ActivitiUtil activitiUtil; + + public ActivitiPropertyConverter(ActivitiUtil activitiUtil, + WorkflowObjectFactory factory, + WorkflowPropertyHandlerRegistry handlerRegistry, + WorkflowAuthorityManager authorityManager, + MessageService messageService, + WorkflowNodeConverter nodeConverter) + { + this.activitiUtil = activitiUtil; + this.factory = factory; + this.handlerRegistry = handlerRegistry; + this.authorityManager = authorityManager; + this.messageService = messageService; + this.nodeConverter = nodeConverter; + this.typeManager = new ActivitiTaskTypeManager(factory, activitiUtil.getFormService()); + } + + public Map getTaskProperties(Task task, boolean localOnly) + { + // retrieve type definition for task + TypeDefinition taskDef = typeManager.getFullTaskDefinition(task); + Map taskProperties = taskDef.getProperties(); + Map taskAssociations = taskDef.getAssociations(); + + Map properties = new HashMap(); + TaskService taskService = activitiUtil.getTaskService(); + // Get the local task variables + Map localVariables = taskService.getVariablesLocal(task.getId()); + Map variables = null; + + if(!localOnly) + { + variables = new HashMap(); + variables.putAll(localVariables); + + // Execution-variables should also be added, if no value is present locally + Map executionVariables = activitiUtil.getExecutionVariables(task.getExecutionId()); + + for(Entry entry : executionVariables.entrySet()) + { + if(!localVariables.containsKey(entry.getKey())) + { + variables.put(entry.getKey(), entry.getValue()); + } + } + } + else + { + // Only local variables should be used. + variables = localVariables; + } + + // Map the arbitrary properties + mapArbitraryProperties(variables, properties, localVariables, taskProperties, taskAssociations); + + // Map activiti task instance fields to properties + properties.put(WorkflowModel.PROP_TASK_ID, task.getId()); + properties.put(WorkflowModel.PROP_DESCRIPTION, task.getDescription()); + // Since the task is never started explicitally, we use the create time + properties.put(WorkflowModel.PROP_START_DATE, task.getCreateTime()); + + // Due date is present on the process instance + String dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE); + properties.put(WorkflowModel.PROP_DUE_DATE, (Serializable) variables.get(dueDateKey)); + + // Since this is a runtime-task, it's not completed yet + properties.put(WorkflowModel.PROP_COMPLETION_DATE, null); + properties.put(WorkflowModel.PROP_PRIORITY, task.getPriority()); + properties.put(ContentModel.PROP_CREATED, task.getCreateTime()); + properties.put(ContentModel.PROP_OWNER, task.getAssignee()); + + // Be sure to fetch the outcome + String outcomeVarName = factory.mapQNameToName(WorkflowModel.PROP_OUTCOME); + if(variables.get(outcomeVarName) != null) + { + properties.put(WorkflowModel.PROP_OUTCOME, (Serializable) variables.get(outcomeVarName)); + } + + List links = taskService.getIdentityLinksForTask(task.getId()); + mapPooledActors(links, properties); + + return filterTaskProperties(properties); + } + + public Map getPathProperties(String executionId) + { + Map variables = activitiUtil.getExecutionVariables(executionId); + Map properties = new HashMap(variables.size()); + for (Entry entry: variables.entrySet()) + { + QName qNameKey = factory.mapNameToQName(entry.getKey()); + Serializable value = convertPropertyValue(entry.getValue()); + properties.put(qNameKey, value); + } + return properties; + } + + public List getPooledActorsReference(Collection links) + { + List pooledActorRefs = new ArrayList(); + if(links != null) + { + for(IdentityLink link : links) + { + if(IdentityLinkType.CANDIDATE.equals(link.getType())) + { + String id = link.getGroupId(); + if(id == null) + { + id = link.getUserId(); + } + NodeRef pooledNodeRef = authorityManager.mapNameToAuthority(id); + if (pooledNodeRef != null) + { + pooledActorRefs.add(pooledNodeRef); + } + } + } + } + return pooledActorRefs; + } + + private void mapPooledActors(Collection links, Map properties) + { + List pooledActorRefs = getPooledActorsReference(links); + if (pooledActorRefs != null) + { + properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable) pooledActorRefs); + } + } + + public Map getTaskProperties(DelegateTask task, TypeDefinition typeDefinition, boolean localOnly) + { + Map taskProperties = typeDefinition.getProperties(); + Map taskAssociations = typeDefinition.getAssociations(); + + Map properties = new HashMap(); + + // Get the local task variables + Map localVariables = task.getVariablesLocal(); + Map variables = null; + + if(localOnly==false) + { + variables = new HashMap(); + variables.putAll(localVariables); + + // Execution-variables should also be added, if no value is present locally + Map executionVariables = task.getExecution().getVariables(); + + for(Entry entry : executionVariables.entrySet()) + { + String key = entry.getKey(); + if(localVariables.containsKey(key)==false) + { + variables.put(key, entry.getValue()); + } + } + } + else + { + // Only local variables should be used. + variables = localVariables; + } + + // Map the arbitrary properties + mapArbitraryProperties(variables, properties, localVariables, taskProperties, taskAssociations); + + // Map activiti task instance fields to properties + properties.put(WorkflowModel.PROP_TASK_ID, task.getId()); + properties.put(WorkflowModel.PROP_DESCRIPTION, task.getDescription()); + // Since the task is never started explicitally, we use the create time + properties.put(WorkflowModel.PROP_START_DATE, task.getCreateTime()); + + // Due date is present on the process instance + String dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE); + if(properties.containsKey(WorkflowModel.PROP_DUE_DATE)==false) + { + properties.put(WorkflowModel.PROP_DUE_DATE, (Serializable) variables.get(dueDateKey)); + } + + // Since this is a runtime-task, it's not completed yet + properties.put(WorkflowModel.PROP_COMPLETION_DATE, null); + properties.put(WorkflowModel.PROP_PRIORITY, task.getPriority()); + properties.put(ContentModel.PROP_CREATED, task.getCreateTime()); + properties.put(ContentModel.PROP_OWNER, task.getAssignee()); + + // TODO: Expose in DelegateTask + Set links = ((TaskEntity)task).getCandidates(); + mapPooledActors(links, properties); + + return filterTaskProperties(properties); + } + + @SuppressWarnings("unchecked") + public Map getTaskProperties(HistoricTaskInstance historicTask, Map variables) + { + // Retrieve type definition for task, based on taskFormKey variable + String formKey = (String) variables.get(ActivitiConstants.PROP_TASK_FORM_KEY); + TypeDefinition taskDef = typeManager.getFullTaskDefinition(formKey); + + Map taskProperties = taskDef.getProperties(); + Map taskAssociations = taskDef.getAssociations(); + + Map properties = new HashMap(); + + // Map the arbitrary properties + mapArbitraryProperties(variables, properties, variables, taskProperties, taskAssociations); + + // Map activiti task instance fields to properties + properties.put(WorkflowModel.PROP_TASK_ID, historicTask.getId()); + properties.put(WorkflowModel.PROP_DESCRIPTION, historicTask.getDescription()); + + // Since the task is never started explicitly, we use the create time + properties.put(WorkflowModel.PROP_START_DATE, historicTask.getStartTime()); + + Object dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE); + properties.put(WorkflowModel.PROP_DUE_DATE, (Serializable) variables.get(dueDateKey)); + properties.put(WorkflowModel.PROP_COMPLETION_DATE, historicTask.getEndTime()); + + // TODO: Put task priority in history - http://jira.codehaus.org/browse/ACT-484 + // properties.put(WorkflowModel.PROP_PRIORITY, historicTask.getPriority()); + + properties.put(ContentModel.PROP_CREATED, historicTask.getStartTime()); + properties.put(ContentModel.PROP_OWNER, historicTask.getAssignee()); + + // Be sure to fetch the outcome + String outcomeVarName = factory.mapQNameToName(WorkflowModel.PROP_OUTCOME); + if(variables.get(outcomeVarName) != null) + { + properties.put(WorkflowModel.PROP_OUTCOME, (Serializable) variables.get(outcomeVarName)); + } + + // History of pooled actors is stored in task variable + List pooledActors = new ArrayList(); + List pooledActorRefIds = (List) variables.get(ActivitiConstants.PROP_POOLED_ACTORS_HISTORY); + if(pooledActorRefIds != null) + { + for(String nodeId : pooledActorRefIds) + { + pooledActors.add(new NodeRef(nodeId)); + } + } + // Add pooled actors. When no actors are found, set empty list + properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable) pooledActors); + + return filterTaskProperties(properties); + } + +// /** +// * Sets all default workflow properties that should be set based on the given taskproperties. +// * When the currentValues already contains a value for a certain key, this value is retained +// * and the value in taskProperties is ignored. +// * +// * @param startTask +// * start task instance +// */ +// public void setDefaultWorkflowProperties(Map currentValues, Map taskProperties) +// { +// if(taskProperties != null) +// { +// String workflowDescriptionName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); +// if (!currentValues.containsKey(workflowDescriptionName)) +// { +// currentValues.put(workflowDescriptionName, taskProperties +// .get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); +// } +// String workflowDueDateName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE); +// if (!currentValues.containsKey(workflowDueDateName)) +// { +// currentValues.put(workflowDueDateName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_DUE_DATE)); +// } +// String workflowPriorityName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY); +// if (!currentValues.containsKey(workflowPriorityName)) +// { +// currentValues.put(workflowPriorityName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_PRIORITY)); +// } +// String workflowContextName = factory.mapQNameToName(WorkflowModel.PROP_CONTEXT); +// if (!currentValues.containsKey(workflowContextName)) +// { +// Serializable contextRef = taskProperties.get(WorkflowModel.PROP_CONTEXT); +// currentValues.put(workflowContextName, convertNodeRefs(false, contextRef)); +// } +// String pckgName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE); +// if (!currentValues.containsKey(pckgName)) +// { +// Serializable pckgNode = taskProperties.get(WorkflowModel.ASSOC_PACKAGE); +// currentValues.put(pckgName, convertNodeRefs(false, pckgNode)); +// } +// } +// } + + /** + * Sets Default Properties of Task + * + * @param instance + * task instance + */ + public void setDefaultTaskProperties(DelegateTask task) + { + TypeDefinition typeDefinition = typeManager.getFullTaskDefinition(task); + // Only local task properties should be set to default value + Map existingValues = getTaskProperties(task, typeDefinition, true); + Map defaultValues = new HashMap(); + + Map propertyDefs = typeDefinition.getProperties(); + + // for each property, determine if it has a default value + for (Map.Entry entry : propertyDefs.entrySet()) + { + QName key = entry.getKey(); + String defaultValue = entry.getValue().getDefaultValue(); + if (defaultValue != null && existingValues.get(key) == null) + { + defaultValues.put(key, defaultValue); + } + } + + // Special case for task description default value + String description = (String) existingValues.get(WorkflowModel.PROP_DESCRIPTION); + if (description == null || description.length() == 0) + { + // Use the shared description set in the workflowinstance + String descriptionKey = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); + description = (String) task.getExecution().getVariable(descriptionKey); + if (description != null && description.length() > 0) + { + defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description); + } + else + { + String processDefinitionKey = ((TaskEntity)task).getExecution().getProcessDefinition().getId(); + + // Revert to title in metaData + String title = factory.getTaskTitle(typeDefinition, factory.buildGlobalId(processDefinitionKey), task.getName(), task.getName()); + defaultValues.put(WorkflowModel.PROP_DESCRIPTION, title); + } + } + + // Assign the default values to the task + if (defaultValues.size() > 0) + { + setTaskProperties(task, defaultValues); + } + } + + public Map getStartTaskProperties(HistoricProcessInstance historicProcessInstance, String taskDefId, boolean completed) + { + Map properties = new HashMap(); + + TypeDefinition taskDef = typeManager.getStartTaskDefinition(taskDefId); + Map taskProperties = taskDef.getProperties(); + Map taskAssociations = taskDef.getAssociations(); + + Map variables = getStartVariables(historicProcessInstance); + + // Map all the properties + mapArbitraryProperties(variables, properties, variables, taskProperties, taskAssociations); + + // Map activiti task instance fields to properties + properties.put(WorkflowModel.PROP_TASK_ID, ActivitiConstants.START_TASK_PREFIX + historicProcessInstance.getId()); + // properties.put(WorkflowModel.PROP_DESCRIPTION, historicTask.getDescription()); + + properties.put(WorkflowModel.PROP_START_DATE, historicProcessInstance.getStartTime()); + + // Use workflow due-date at the time of starting the process + String wfDueDateKey = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE); + String dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE); + Serializable dueDate = (Serializable) variables.get(wfDueDateKey); + if(dueDate == null) + { + dueDate = (Serializable) variables.get(dueDateKey); + } + properties.put(WorkflowModel.PROP_DUE_DATE, dueDate); + + // TODO: is it okay to use start-date? + properties.put(WorkflowModel.PROP_COMPLETION_DATE, historicProcessInstance.getStartTime()); + + // Use workflow priority at the time of starting the process + String priorityKey = factory.mapQNameToName(WorkflowModel.PROP_PRIORITY); + Serializable priority = (Serializable) variables.get(priorityKey); + if(priority == null) + { + String wfPriorityKey = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY); + priority = (Serializable) variables.get(wfPriorityKey); + } + properties.put(WorkflowModel.PROP_PRIORITY, priority); + + properties.put(ContentModel.PROP_CREATED, historicProcessInstance.getStartTime()); + + // Use initiator username as owner + ActivitiScriptNode ownerNode = (ActivitiScriptNode) variables.get(WorkflowConstants.PROP_INITIATOR); + if(ownerNode != null) + { + properties.put(ContentModel.PROP_OWNER, (Serializable) ownerNode.getProperties().get("userName")); + } + + if(completed) + { + // Override default 'Not Yet Started' when start-task is completed + properties.put(WorkflowModel.PROP_STATUS, WorkflowConstants.TASK_STATUS_COMPLETED); + + // Outcome is default transition + properties.put(WorkflowModel.PROP_OUTCOME, ActivitiConstants.DEFAULT_TRANSITION_NAME); + } + + return filterTaskProperties(properties); + } + + /** + * @param historicProcessInstance + * @return + */ + public Map getStartVariables(HistoricProcessInstance historicProcessInstance) + { + // Get historic variable values for start-event + HistoricActivityInstance startEvent = activitiUtil.getHistoryService() + .createHistoricActivityInstanceQuery() + .processInstanceId(historicProcessInstance.getId()) + .activityId(historicProcessInstance.getStartActivityId()) + .singleResult(); + + Map variables = getHistoricActivityVariables(startEvent.getId()); + return variables; + } + + /** + * Get all variable updates for process instance, latest updates on top + * + * @param processId + * @return + */ + public Map getHistoricProcessVariables(String processId) + { + HistoricDetailQuery query = activitiUtil.getHistoryService() + .createHistoricDetailQuery() + .processInstanceId(processId); + return getHistoricVariables(query); + } + + /** + * Get all variable updates for task instance, latest updates on top + * + * @param taskId + * @return + */ + public Map getHistoricTaskVariables(String taskId) + { + HistoricDetailQuery query = activitiUtil.getHistoryService() + .createHistoricDetailQuery() + .taskId(taskId); + return getHistoricVariables(query); + } + + /** + * Get all variable updates for activity, latest updates on top + * + * @param taskId + * @return + */ + public Map getHistoricActivityVariables(String activityId) + { + HistoricDetailQuery query = activitiUtil.getHistoryService() + .createHistoricDetailQuery() + .activityInstanceId(activityId); + return getHistoricVariables(query); + } + + private Map getHistoricVariables(HistoricDetailQuery query) + { + List historicDetails = query.variableUpdates() + .orderByVariableName().asc() + .orderByTime().desc() + .orderByVariableRevision().desc() + .list(); + return convertHistoricDetails(historicDetails); + } + + private void mapArbitraryProperties(Map variables, Map properties, + Map localVariables, Map taskProperties, Map taskAssociations) + { + // Map arbitrary task variables + for (Entry entry : variables.entrySet()) + { + String key = entry.getKey(); + QName qname = factory.mapNameToQName(key); + + // Add variable, only if part of task definition or locally defined + // on task + if (taskProperties.containsKey(qname) || taskAssociations.containsKey(qname) || localVariables.containsKey(key)) + { + Serializable value = convertPropertyValue(entry.getValue()); + properties.put(qname, value); + } + } + } + + /** + * Convert an Activiti variable value to an Alfresco value. + * + * @param value + * activti value + * @return alfresco value + */ + public Serializable convertPropertyValue(Object value) + { + if (value == null) + { + return null; + } + if(nodeConverter.isSupported(value)) + { + return nodeConverter.convert(value); + } + if (value instanceof Serializable) + { + return (Serializable) value; + } + String msg = messageService.getMessage(ERR_CONVERT_VALUE, value); + throw new WorkflowException(msg); + } + + @SuppressWarnings("unchecked") + private Map getNewTaskProperties(Task task, Map properties, Map> add, + Map> remove) + { + // create properties to set on task instance + Map newProperties = properties; + + if (add != null || remove != null) + { + if (newProperties == null ) + { + newProperties = new HashMap(10); + } + + Map existingProperties = getTaskProperties(task, false); + + if (add != null) + { + // add new associations + for (Entry> toAdd : add.entrySet()) + { + // retrieve existing list of noderefs for + // association + List existingAdd = (List) newProperties.get(toAdd.getKey()); + if (existingAdd == null) + { + existingAdd = (List) existingProperties.get(toAdd.getKey()); + } + + // make the additions + if (existingAdd == null) + { + newProperties.put(toAdd.getKey(), (Serializable) toAdd.getValue()); + } + else + { + for (NodeRef nodeRef : toAdd.getValue()) + { + if (!(existingAdd.contains(nodeRef))) + { + existingAdd.add(nodeRef); + } + } + } + } + } + + if (remove != null) + { + // add new associations + for (Entry> toRemove : remove.entrySet()) + { + // retrieve existing list of noderefs for + // association + List existingRemove = (List) newProperties.get(toRemove.getKey()); + if (existingRemove == null) + { + existingRemove = (List) existingProperties.get(toRemove.getKey()); + } + + // make the subtractions + if (existingRemove != null) + { + for (NodeRef nodeRef : toRemove.getValue()) + { + existingRemove.remove(nodeRef); + } + } + } + } + } + return newProperties; + } + + public void setTaskProperties(DelegateTask task, Map properties) + { + if(properties==null || properties.isEmpty()) + return; + TypeDefinition type = typeManager.getFullTaskDefinition(task); + Map variablesToSet = handlerRegistry.handleVariablesToSet(properties, type, task, DelegateTask.class); + if(variablesToSet.size() > 0) + { + task.setVariablesLocal(variablesToSet); + } + + } + + /** + * Sets the properties on the task, using Activiti API. + */ + public void setTaskProperties(Task task, Map properties) + { + if(properties==null || properties.isEmpty()) + return; + + TypeDefinition type = typeManager.getFullTaskDefinition(task); + Map variablesToSet = handlerRegistry.handleVariablesToSet(properties, type, task, Task.class); + + TaskService taskService = activitiUtil.getTaskService(); + // Will be set when an assignee is present in passed properties. + taskService.saveTask(task); + + // Set the collected variables on the task + taskService.setVariablesLocal(task.getId(), variablesToSet); + + setTaskOwner(task, properties); + } + + /** + * @param task + * @param properties + */ + private void setTaskOwner(Task task, Map properties) + { + QName ownerKey = ContentModel.PROP_OWNER; + if (properties.containsKey(ownerKey)) + { + Serializable owner = properties.get(ownerKey); + if (owner != null && !(owner instanceof String)) + { + throw getInvalidPropertyValueException(ownerKey, owner); + } + String assignee = (String) owner; + String currentAssignee = task.getAssignee(); + // Only set the assignee if the value has changes to prevent + // triggering assignementhandlers when not needed + if (ObjectUtils.equals(currentAssignee, assignee)==false) + { + activitiUtil.getTaskService().setAssignee(task.getId(), assignee); + } + } + } + + private WorkflowException getInvalidPropertyValueException(QName key, Serializable value) + { + String msg = messageService.getMessage(ERR_SET_TASK_PROPS_INVALID_VALUE, value, key); + return new WorkflowException(msg); + } + + /** + * Filter out all internal task-properties. + * + * @param properties + * @return filtered properties. + */ + private Map filterTaskProperties( + Map properties) { + if(properties != null) + { + properties.remove(QName.createQName(null, ActivitiConstants.PROP_POOLED_ACTORS_HISTORY)); + properties.remove(QName.createQName(null, ActivitiConstants.PROP_TASK_FORM_KEY)); + } + return properties; + } + + /** + * Convert a list of {@link HistoricDetail} to a map with key-value pairs. + * @param details the histroicDetails. Should be a list of {@link HistoricVariableUpdate}s. + */ + public Map convertHistoricDetails(List details) + { + // TODO: Sorting should be performed in query, not available right now. + Collections.sort(details, new Comparator() + { + @Override + public int compare(HistoricDetail o1, HistoricDetail o2) + { + Long id1 = Long.valueOf(o1.getId()); + Long id2 = Long.valueOf(o2.getId()); + return - id1.compareTo(id2); + } + }); + Map variables = new HashMap(); + for(HistoricDetail detail : details) + { + HistoricVariableUpdate varUpdate = (HistoricVariableUpdate) detail; + // First value for a single key is used + if(!variables.containsKey(varUpdate.getVariableName())) + { + variables.put(varUpdate.getVariableName(), varUpdate.getValue()); + } + } + return variables; + } + + + public Map getStartVariables(String processDefId, Map properties) + { + ProcessDefinition procDef = activitiUtil.getProcessDefinition(processDefId); + String startTaskTypeName = activitiUtil.getStartTaskTypeName(processDefId); + TypeDefinition startTaskType = factory.getTaskFullTypeDefinition(startTaskTypeName, true); + + // Lookup type definition for the startTask + Map taskProperties = startTaskType.getProperties(); + + // Get all default values from the definitions + Map defaultProperties = new HashMap(); + for (Map.Entry entry : taskProperties.entrySet()) + { + String defaultValue = entry.getValue().getDefaultValue(); + if (defaultValue != null) + { + defaultProperties.put(entry.getKey(), defaultValue); + } + } + + // Put all passed properties in map with defaults + if(properties != null) + { + defaultProperties.putAll(properties); + } + + // Special case for task description default value + // Use the shared description set in the workflowinstance + String description = (String) defaultProperties.get(WorkflowModel.PROP_DESCRIPTION); + if(description == null) + { + String wfDescription = (String) defaultProperties.get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); + String procDefKey = procDef.getKey(); + ReadOnlyProcessDefinition deployedDef = activitiUtil.getDeployedProcessDefinition(processDefId); + String startEventName = deployedDef.getInitial().getId(); + String wfDefKey = factory.buildGlobalId(procDefKey); + factory.getTaskDescription(startTaskType, wfDefKey, wfDescription, startEventName); + defaultProperties.put(WorkflowModel.PROP_DESCRIPTION, description); + } + return handlerRegistry.handleVariablesToSet(defaultProperties, startTaskType, null, Void.class); + } + + public void checkMandatoryProperties(DelegateTask task) + { + // Check all mandatory properties are set. This is checked here instead of in + // the completeTask() to allow taskListeners to set variable values before checking + List missingProps = getMissingMandatoryTaskProperties(task); + if (missingProps != null && missingProps.size() > 0) + { + String missingPropString = StringUtils.join(missingProps.iterator(), ", "); + throw new WorkflowException(messageService.getMessage(ERR_MANDATORY_TASK_PROPERTIES_MISSING, missingPropString)); + } + } + + /** + * Get missing mandatory properties on Task + * + * @param task + * task instance + * @return array of missing property names (or null, if none) + */ + private List getMissingMandatoryTaskProperties(DelegateTask task) + { + + TypeDefinition typeDefinition = typeManager.getFullTaskDefinition(task); + // retrieve properties of task + Map existingValues = getTaskProperties(task, typeDefinition, false); + + // retrieve definition of task + Map propertyDefs = typeDefinition.getProperties(); + Map assocDefs = typeDefinition.getAssociations(); + + List missingProps = findMissingProperties(existingValues, propertyDefs); + List missingAssocs= findMissingProperties(existingValues, assocDefs); + missingProps.addAll(missingAssocs); + return missingProps; + } + + private List findMissingProperties(Map existingValues, + Map definitions) + { + // for each property, determine if it is mandatory + List missingProps = new ArrayList(); + for (Map.Entry entry : definitions.entrySet()) + { + QName name = entry.getKey(); + // Skip System and CM properties. Why? + if ((name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) + || (name.getNamespaceURI().equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) + { + continue; + } + if (isMandatory(entry.getValue())) + { + Object value = existingValues.get(entry.getKey()); + if (value == null || isEmptyString(value)) + { + missingProps.add(entry.getKey()); + } + } + } + return missingProps; + } + + private boolean isMandatory(ClassAttributeDefinition definition) + { + if(definition instanceof PropertyDefinition) + { + PropertyDefinition propDef = (PropertyDefinition) definition; + return propDef.isMandatory(); + } + AssociationDefinition assocDSef = (AssociationDefinition) definition; + return assocDSef.isTargetMandatory(); + } + + /** + * @param value + * @return + */ + private boolean isEmptyString(Object value) + { + if(value instanceof String) + { + String str = (String)value; + return str.isEmpty(); + } + return false; + } + + public Task updateTask(Task task, Map properties, Map> add, + Map> remove) + { + Map newProperties = getNewTaskProperties(task, properties, add, remove); + if (newProperties != null) + { + setTaskProperties(task, newProperties); + return activitiUtil.getTaskInstance(task.getId()); + } + return task; + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/script/ActivitiScriptBase.java b/source/java/org/alfresco/repo/workflow/activiti/script/ActivitiScriptBase.java new file mode 100644 index 0000000000..693117ae7e --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/script/ActivitiScriptBase.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.script; + +import java.util.Map; + +import org.activiti.engine.delegate.VariableScope; +import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.activiti.engine.impl.context.Context; +import org.activiti.engine.impl.el.Expression; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; +import org.alfresco.repo.workflow.activiti.ActivitiScriptNode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.workflow.WorkflowException; + +/** + * Base class for execution scripts, using {@link ScriptService} as part of + * activiti workflow. + * + * @author Frederik Heremans + */ +public class ActivitiScriptBase { + + protected static final String PERSON_BINDING_NAME = "person"; + protected static final String USERHOME_BINDING_NAME = "userhome"; + protected static final String EXECUTION_BINDING_NAME = "execution"; + + protected Expression script; + protected Expression runAs; + protected Expression scriptProcessor; + + protected Object executeScript(String theScript, Map model, String scriptProcessorName, String runAsUser) + { + String user = AuthenticationUtil.getFullyAuthenticatedUser(); + + Object scriptResult = null; + if(runAsUser == null && user != null) + { + // Just execute the script using the current user + scriptResult = executeScript(theScript, model, scriptProcessorName); + } + else { + if(runAsUser != null) + { + // Check if the user used for running exists + validateRunAsUser(runAsUser); + } + else + { + // No current user is authenticated, use the system-user to execute the script + runAsUser = AuthenticationUtil.getSystemUserName(); + } + executeScriptAsUser(theScript, model, scriptProcessorName, runAsUser); + } + return scriptResult; + } + + protected Object executeScriptAsUser(final String theScript, final Map model, final String scriptProcessorName, final String runAsUser) + { + // execute as specified runAsUser + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + return executeScript(theScript, model, scriptProcessorName); + } + }, runAsUser); + } + + protected Object executeScript(String theScript, Map model, String scriptProcessorName) + { + // Execute the script using the appropriate processor + Object scriptResult = null; + if(scriptProcessorName != null) + { + scriptResult = getServiceRegistry().getScriptService().executeScriptString(scriptProcessorName, theScript, model); + } + else + { + // Use default script-processor + scriptResult = getServiceRegistry().getScriptService().executeScriptString(theScript, model); + } + + return scriptResult; + } + + protected String getStringValue(Expression expression, VariableScope scope) { + if(expression != null) + { + return (String) expression.getValue(scope); + } + return null; + } + + protected ServiceRegistry getServiceRegistry() + { + ProcessEngineConfigurationImpl config = Context.getProcessEngineConfiguration(); + if(config != null) + { + // Fetch the registry that is injected in the activiti spring-configuration + ServiceRegistry registry = (ServiceRegistry) config.getBeans().get(ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY); + if(registry == null) + { + throw new RuntimeException( + "Service-registry not present in ProcessEngineConfiguration beans, expected ServiceRegistry with key" + + ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY); + } + return registry; + } + throw new IllegalStateException("No ProcessEngineCOnfiguration found in active context"); + } + + /** + * Checks that the specified 'runAs' field + * specifies a valid username. + */ + private void validateRunAsUser(final String runAsUser) { + Boolean runAsExists = AuthenticationUtil.runAs(new RunAsWork() + { // Validate using System user to ensure sufficient permissions available to access person node. + + public Boolean doWork() throws Exception { + return getServiceRegistry().getPersonService().personExists(runAsUser); + } + }, AuthenticationUtil.getSystemUserName()); + if (!runAsExists) + { + throw new WorkflowException("runas user '" + runAsUser + "' does not exist."); + } + } + + protected ActivitiScriptNode getPersonNode(String runAsUser) + { + String userName = null; + if(runAsUser != null) + { + userName = runAsUser; + } + else + { + userName = AuthenticationUtil.getFullyAuthenticatedUser(); + } + if(userName != null) + { + ServiceRegistry services = getServiceRegistry(); + NodeRef person = services.getPersonService().getPerson(userName); + if(person !=null) + { + return new ActivitiScriptNode(person, services); + } + } + return null; + } + + public void setScript(Expression script) { + this.script = script; + } + + public void setRunAs(Expression runAs) { + this.runAs = runAs; + } + + public void setScriptProcessor(Expression scriptProcessor) { + this.scriptProcessor = scriptProcessor; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/tasklistener/ScriptTaskListener.java b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/ScriptTaskListener.java new file mode 100644 index 0000000000..49c1340f44 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/ScriptTaskListener.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.tasklistener; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.impl.pvm.delegate.TaskListener; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.workflow.activiti.ActivitiScriptNode; +import org.alfresco.repo.workflow.activiti.script.ActivitiScriptBase; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * A {@link TaskListener} that runs the script in property 'script' + * using scripting-language specified by 'language' + * + * @author Frederik Heremans + */ +public class ScriptTaskListener extends ActivitiScriptBase implements TaskListener +{ + private static final String TASK_BINDING_NAME = "task"; + + @Override + public void notify(DelegateTask delegateTask) + { + if(script != null) + { + String scriptString = getStringValue(script, delegateTask); + String scriptProcessorName = getStringValue(scriptProcessor, delegateTask); + String runAsUser = getStringValue(runAs, delegateTask); + + + // Make sure there is an authenticated user for the current thread, so when + // the script is executed using no 'runAs' from a job-executor thread, the task's assignee + // will be the authenticated user. + boolean clearAuthenticationContext = checkFullyAuthenticatedUser(delegateTask); + + // Get all activiti-defined objects + Map scriptModel = getInputMap(delegateTask, runAsUser); + + // Add core alfresco objects to the input-map + getServiceRegistry().getScriptService().buildCoreModel(scriptModel); + + try + { + Object scriptOutput = executeScript(scriptString, scriptModel, scriptProcessorName, runAsUser); + + // TODO: What to do with the script-output? + if(scriptOutput != null) + { + // delegateTask.setVariableLocal("scriptOutput", scriptOutput); + } + } + finally + { + if(clearAuthenticationContext) + { + // If the current user has been set to the Task's assignee, we should clear it agian + AuthenticationUtil.clearCurrentSecurityContext(); + } + } + } + else + { + throw new IllegalArgumentException("The field 'script' should be set on the TaskListener"); + } + } + + protected Map getInputMap(DelegateTask delegateTask, String runAsUser) + { + HashMap scriptModel = new HashMap(1); + + // Add current logged-in user and it's user home + ActivitiScriptNode personNode = getPersonNode(runAsUser); + if(personNode != null) + { + ServiceRegistry registry = getServiceRegistry(); + scriptModel.put(PERSON_BINDING_NAME, personNode); + NodeRef userHomeNode = (NodeRef) registry.getNodeService().getProperty(personNode.getNodeRef(), ContentModel.PROP_HOMEFOLDER); + if (userHomeNode != null) + { + scriptModel.put(USERHOME_BINDING_NAME, new ActivitiScriptNode(userHomeNode, registry)); + } + } + + // Add activiti-specific objects + scriptModel.put(TASK_BINDING_NAME, delegateTask); + scriptModel.put(EXECUTION_BINDING_NAME, delegateTask.getExecution()); + + // Add all workflow variables to model + Map variables = delegateTask.getExecution().getVariables(); + + for(Entry varEntry : variables.entrySet()) + { + scriptModel.put(varEntry.getKey(), varEntry.getValue()); + } + return scriptModel; + } + + /** + * Checks a valid Fully Authenticated User is set. + * If none is set then attempts to set the task assignee as the Fully Authenticated User. + * @param delegateTask the delegate task + * @return true if the Fully Authenticated User was changed, otherwise false. + */ + private boolean checkFullyAuthenticatedUser(final DelegateTask delegateTask) { + if(AuthenticationUtil.getFullyAuthenticatedUser() == null) + { + String userName = delegateTask.getAssignee(); + if (userName != null) + { + AuthenticationUtil.setFullyAuthenticatedUser(userName); + return true; + } + } + return false; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCompleteListener.java b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCompleteListener.java new file mode 100644 index 0000000000..0e45f1a239 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCompleteListener.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.tasklistener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.impl.pvm.delegate.TaskListener; +import org.activiti.engine.impl.task.IdentityLinkEntity; +import org.activiti.engine.impl.task.TaskEntity; +import org.activiti.engine.task.IdentityLink; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.WorkflowQNameConverter; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; +import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; + +/** + * Tasklistener that is notified when a task completes. + * + * This will set a few properties on the task, indicating it is complete + * and preparing it for historic usage. + * + * @author Frederik Heremans + */ +public class TaskCompleteListener implements TaskListener +{ + private ActivitiPropertyConverter propertyConverter; + private WorkflowQNameConverter qNameConverter; + + @Override + public void notify(DelegateTask task) + { + // Check all mandatory properties are set. This is checked here instead of in + // the completeTask() to allow taskListeners to set variable values before checking + propertyConverter.checkMandatoryProperties(task); + + // Set properties for ended task + Map endTaskVariables = new HashMap(); + + // Set task status to completed + String statusKey = qNameConverter.mapQNameToName(WorkflowModel.PROP_STATUS); + endTaskVariables.put(statusKey, "Completed"); + + // Add pooled actors to task-variables to be preserved in history (if any) + addPooledActorsAsVariable(task, endTaskVariables); + + // Set variables locally on the task + task.setVariablesLocal(endTaskVariables); + } + + private void addPooledActorsAsVariable(DelegateTask task, + Map variables) { + + List links = ((TaskEntity)task).getIdentityLinks(); + if(links.size() > 0) + { + // Add to list of IdentityLink + List identityLinks = new ArrayList(); + identityLinks.addAll(links); + + List pooledActorRefs = propertyConverter.getPooledActorsReference(identityLinks); + + // Save references as a variable + List nodeIds = new ArrayList(); + for(NodeRef ref : pooledActorRefs) + { + nodeIds.add(ref.toString()); + } + variables.put(ActivitiConstants.PROP_POOLED_ACTORS_HISTORY, nodeIds); + } + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.qNameConverter = new WorkflowQNameConverter(namespaceService); + } + + /** + * @param propertyConverter the propertyConverter to set + */ + public void setPropertyConverter(ActivitiPropertyConverter propertyConverter) + { + this.propertyConverter = propertyConverter; + } +} + diff --git a/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCreateListener.java b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCreateListener.java new file mode 100644 index 0000000000..219b2d0e2e --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/tasklistener/TaskCreateListener.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.tasklistener; + +import org.activiti.engine.delegate.DelegateTask; +import org.activiti.engine.form.FormData; +import org.activiti.engine.impl.form.TaskFormHandler; +import org.activiti.engine.impl.pvm.delegate.TaskListener; +import org.activiti.engine.impl.task.TaskEntity; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; +import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; + +/** + * Tasklistener that is notified when a task is created. This will set all + * default properties for this task. + * + * @author Frederik Heremans + */ +public class TaskCreateListener implements TaskListener +{ + private ActivitiPropertyConverter propertyConverter; + + @Override + public void notify(DelegateTask task) + { + // Set all default properties, based on the type-definition + propertyConverter.setDefaultTaskProperties(task); + + // The taskDefinition key is set as a variable in order to be available + // in the history + String taskFormKey = getFormKey(task); + if (taskFormKey != null) + { + task.setVariableLocal(ActivitiConstants.PROP_TASK_FORM_KEY, taskFormKey); + } + } + + private String getFormKey(DelegateTask task) + { + FormData formData = null; + TaskEntity taskEntity = (TaskEntity) task; + TaskFormHandler taskFormHandler = taskEntity.getTaskDefinition().getTaskFormHandler(); + if (taskFormHandler != null) + { + formData = taskFormHandler.createTaskForm(taskEntity); + if (formData != null) { return formData.getFormKey(); } + } + return null; + } + + /** + * @param propertyConverter the propertyConverter to set + */ + public void setPropertyConverter(ActivitiPropertyConverter propertyConverter) + { + this.propertyConverter = propertyConverter; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/variable/ScriptNodeListVariableType.java b/source/java/org/alfresco/repo/workflow/activiti/variable/ScriptNodeListVariableType.java new file mode 100644 index 0000000000..f2fc74b8e9 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/variable/ScriptNodeListVariableType.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.variable; + +import java.util.List; + +import org.activiti.engine.ActivitiException; +import org.activiti.engine.impl.variable.SerializableType; +import org.activiti.engine.impl.variable.ValueFields; +import org.activiti.engine.impl.variable.VariableType; +import org.alfresco.repo.workflow.activiti.ActivitiScriptNode; +import org.alfresco.repo.workflow.activiti.ActivitiScriptNodeList; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Custom {@link VariableType} that allows storing a list of {@link ActivitiScriptNode}s as + * a process variable in activiti. + * + * @author Frederik Heremans + */ +public class ScriptNodeListVariableType extends SerializableType +{ + public static final String TYPE_NAME = "alfrescoScriptNodeList"; + + private ServiceRegistry serviceRegistry; + + @Override + public String getTypeName() + { + return TYPE_NAME; + } + + @Override + public boolean isCachable() + { + // The ActivitiScriptNodeList can be cached since it uses the serviceRegistry internally + // for resolving actual values. + return true; + } + + @Override + public boolean isAbleToStore(Object value) + { + if(value == null) + { + return true; + } + return ActivitiScriptNodeList.class.isAssignableFrom(value.getClass()); + } + + @Override + public void setValue(Object value, ValueFields valueFields) + { + if(value != null) + { + if(!(value instanceof ActivitiScriptNodeList)) + { + throw new ActivitiException("Passed value is not an instance of ActivitiScriptNodeList, cannot set variable value."); + } + + // Extract all node references + List nodeRefs = ((ActivitiScriptNodeList) value).getNodeReferences(); + // Save the list as a serializable + super.setValue(nodeRefs, valueFields); + } + } + + @Override + @SuppressWarnings("unchecked") + public Object getValue(ValueFields valueFields) + { + Object serializable = super.getValue(valueFields); + if(serializable == null) + { + return null; + } + + if(!(serializable instanceof List)) + { + throw new ActivitiException("Serializable stored in variable is not instance of List, cannot get value."); + } + + ActivitiScriptNodeList scriptNodes = new ActivitiScriptNodeList(); + // Wrap all node references in an ActivitiScriptNode + List nodeRefs =(List) serializable; + for(NodeRef ref : nodeRefs) + { + scriptNodes.add(new ActivitiScriptNode(ref, serviceRegistry)); + } + return scriptNodes; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/variable/ScriptNodeVariableType.java b/source/java/org/alfresco/repo/workflow/activiti/variable/ScriptNodeVariableType.java new file mode 100644 index 0000000000..52576cea66 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/activiti/variable/ScriptNodeVariableType.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.activiti.variable; + +import org.activiti.engine.ActivitiException; +import org.activiti.engine.impl.variable.ValueFields; +import org.activiti.engine.impl.variable.VariableType; +import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.workflow.activiti.ActivitiScriptNode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Custom {@link VariableType} that allows storing {@link ActivitiScriptNode} as + * a process variable in activiti, allowing node properties being used in + * scripts. + * + * @author Frederik Heremans + */ +public class ScriptNodeVariableType implements VariableType +{ + public static final String TYPE_NAME = "alfrescoScriptNode"; + + private ServiceRegistry serviceRegistry; + + @Override + public String getTypeName() + { + return TYPE_NAME; + } + + @Override + public boolean isCachable() + { + // The ScriptNode can be cached since it uses the serviceRegistry internally + // for resolving actual values. + return true; + } + + @Override + public boolean isAbleToStore(Object value) + { + if(value == null) + { + return true; + } + return ScriptNode.class.isAssignableFrom(value.getClass()); + } + + @Override + public void setValue(Object value, ValueFields valueFields) + { + String textValue = null; + if(value != null) + { + if(!(value instanceof ActivitiScriptNode)) + { + throw new ActivitiException("Passed value is not an instance of ActivitiScriptNode, cannot set variable value."); + } + NodeRef reference = (((ActivitiScriptNode)value).getNodeRef()); + if(reference != null) + { + // Use the string representation of the NodeRef + textValue = reference.toString(); + } + } + valueFields.setTextValue(textValue); + } + + @Override + public Object getValue(ValueFields valueFields) + { + ScriptNode scriptNode = null; + String nodeRefString = valueFields.getTextValue(); + if(nodeRefString != null) + { + scriptNode = new ActivitiScriptNode(new NodeRef(nodeRefString), serviceRegistry); + } + return scriptNode; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java index 81b3206536..8840ad7005 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java @@ -174,11 +174,11 @@ public class AlfrescoJavaScriptIntegrationTest extends BaseAlfrescoSpringTest private String fullUser; private JBPMNode person = null; - public void storeUsers(JBPMNode person) + public void storeUsers(JBPMNode thePerson) { fullUser = AuthenticationUtil.getFullyAuthenticatedUser(); runAsUser = AuthenticationUtil.getRunAsUser(); - this.person = person; + this.person = thePerson; } } } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 4d8bf20e95..33134413a3 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -29,26 +29,22 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.zip.ZipInputStream; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.workflow.BPMEngine; -import org.alfresco.repo.workflow.TaskComponent; -import org.alfresco.repo.workflow.WorkflowComponent; +import org.alfresco.repo.workflow.AlfrescoBpmEngine; +import org.alfresco.repo.workflow.WorkflowConstants; +import org.alfresco.repo.workflow.WorkflowEngine; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; @@ -116,21 +112,14 @@ import org.springmodules.workflow.jbpm31.JbpmTemplate; /** * JBoss JBPM based implementation of: * - * Workflow Definition Component - * Workflow Component - * Task Component + * Workflow Definition Component Workflow Component Task Component * * @author davidc */ -public class JBPMEngine extends BPMEngine - implements WorkflowComponent, TaskComponent +public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { // Implementation dependencies - protected DictionaryService dictionaryService; - protected NamespaceService namespaceService; protected NodeService nodeService; - private TenantService tenantService; - private MessageService messageService; protected ServiceRegistry serviceRegistry; protected PersonService personService; protected AuthorityDAO authorityDAO; @@ -143,19 +132,13 @@ public class JBPMEngine extends BPMEngine // Note: jBPM query which is not provided out-of-the-box // TODO: Check jBPM 3.2 and get this implemented in jBPM - private final static String COMPLETED_TASKS_QUERY = - "select ti " + - "from org.jbpm.taskmgmt.exe.TaskInstance as ti " + - "where ti.actorId = :actorId " + - "and ti.isOpen = false " + - "and ti.end is not null"; + private final static String COMPLETED_TASKS_QUERY = "select ti " + "from org.jbpm.taskmgmt.exe.TaskInstance as ti " + + "where ti.actorId = :actorId " + "and ti.isOpen = false " + "and ti.end is not null"; // Note: jBPM query which is not provided out-of-the-box // TODO: Check jBPMg future and get this implemented in jBPM - private final static String PROCESS_TIMERS_QUERY = - "select timer " + - "from org.jbpm.job.Timer timer " + - "where timer.processInstance = :process "; + private final static String PROCESS_TIMERS_QUERY = "select timer " + "from org.jbpm.job.Timer timer " + + "where timer.processInstance = :process "; // Workflow Path Seperators private final static String WORKFLOW_PATH_SEPERATOR = "-"; @@ -199,21 +182,21 @@ public class JBPMEngine extends BPMEngine protected static final String ERR_END_TASK_INVALID_TRANSITION ="jbpm.engine.end.task.invalid.transition"; private static final String ERR_END_TASK = "jbpm.engine.end.task.error"; private static final String ERR_GET_TASK_BY_ID = "jbpm.engine.get.task.by.id.error"; + private static final String ERR_GET_START_TASK = "jbpm.engine.get.start.task.error"; private static final String ERR_COMPILE_PROCESS_DEF_zip = "jbpm.engine.compile.process.definition.zip.error"; private static final String ERR_COMPILE_PROCESS_DEF_XML = "jbpm.engine.compile.process.definition.xml.error"; private static final String ERR_COMPILE_PROCESS_DEF_UNSUPPORTED = "jbpm.engine.compile.process.definition.unsupported.error"; - private static final String ERR_GET_TASK_DEF = "jbpm.engine.get.task.definition.error"; private static final String ERR_GET_JBPM_ID = "jbpm.engine.get.jbpm.id.error"; private static final String ERR_GET_WORKFLOW_TOKEN_INVALID = "jbpm.engine.get.workflow.token.invalid"; private static final String ERR_GET_WORKFLOW_TOKEN_NULL = "jbpm.engine.get.workflow.token.is.null"; private static final String ERR_SET_TASK_PROPS_INVALID_VALUE = "jbpm.engine.set.task.properties.invalid.value"; - private static final String ERR_PACKAGE_ALREADY_ASSOCIATED = "jbpm.engine.package.already.associated.error"; private static final String ERR_CONVERT_VALUE = "jbpm.engine.convert.value.error"; private static final String ERR_GET_COMPANY_HOME_INVALID = "jbpm.engine.get.company.home.invalid"; private static final String ERR_GET_COMPANY_HOME_MULTIPLE = "jbpm.engine.get.company.home.multiple"; /** - * Sets the JBPM Template used for accessing JBoss JBPM in the correct context + * Sets the JBPM Template used for accessing JBoss JBPM in the correct + * context * * @param jbpmTemplate */ @@ -222,26 +205,6 @@ public class JBPMEngine extends BPMEngine this.jbpmTemplate = jbpmTemplate; } - /** - * Sets the Dictionary Service - * - * @param dictionaryService - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Sets the Namespace Service - * - * @param namespaceService - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - /** * Sets the Node Service * @@ -252,26 +215,6 @@ public class JBPMEngine extends BPMEngine this.nodeService = nodeService; } - /** - * Sets the Tenant Service - * - * @param tenantService - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - /** - * Sets the Message Service - * - * @param messageService - */ - public void setMessageService(MessageService messageService) - { - this.messageService = messageService; - } - /** * Sets the Person Service * @@ -323,8 +266,9 @@ public class JBPMEngine extends BPMEngine } /** - * Set the unprotected search service - so we can find the node ref for company home when folk do not have read access to company home - * TODO: review use with DC + * Set the unprotected search service - so we can find the node ref for + * company home when folk do not have read access to company home TODO: + * review use with DC * * @param unprotectedSearchService */ @@ -338,8 +282,12 @@ public class JBPMEngine extends BPMEngine // Workflow Definition... // - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#deployDefinition(java.io.InputStream) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#deployDefinition + * (java.io.InputStream) */ public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype) { @@ -368,9 +316,12 @@ public class JBPMEngine extends BPMEngine } } - - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#isDefinitionDeployed(java.io.InputStream, java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#isDefinitionDeployed + * (java.io.InputStream, java.lang.String) */ public boolean isDefinitionDeployed(final InputStream workflowDefinition, final String mimetype) { @@ -385,7 +336,8 @@ public class JBPMEngine extends BPMEngine // retrieve process definition from Alfresco Repository GraphSession graphSession = context.getGraphSession(); - ProcessDefinition existingDefinition = graphSession.findLatestProcessDefinition(processDefinition.def.getName()); + ProcessDefinition existingDefinition = graphSession + .findLatestProcessDefinition(processDefinition.def.getName()); return (existingDefinition == null) ? false : true; } }); @@ -397,8 +349,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#undeployDefinition(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#undeployDefinition + * (java.lang.String) */ public void undeployDefinition(final String workflowDefinitionId) { @@ -414,7 +370,8 @@ public class JBPMEngine extends BPMEngine // undeploy // NOTE: jBPM deletes all "in-flight" processes too - // TODO: Determine if there's a safer undeploy we can expose via the WorkflowService contract + // TODO: Determine if there's a safer undeploy we can expose + // via the WorkflowService contract graphSession.deleteProcessDefinition(processDefinition); // we're done @@ -429,8 +386,11 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() */ @SuppressWarnings("unchecked") public List getDefinitions() @@ -454,7 +414,8 @@ public class JBPMEngine extends BPMEngine } catch (RuntimeException re) { - // deliberately skip this one - due to domain mismatch + // deliberately skip this one - due to domain + // mismatch continue; } } @@ -473,8 +434,11 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() */ @SuppressWarnings("unchecked") public List getAllDefinitions() @@ -498,7 +462,8 @@ public class JBPMEngine extends BPMEngine } catch (RuntimeException re) { - // deliberately skip this one - due to domain mismatch + // deliberately skip this one - due to domain + // mismatch continue; } } @@ -517,8 +482,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitionById(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitionById + * (java.lang.String) */ public WorkflowDefinition getDefinitionById(final String workflowDefinitionId) { @@ -542,8 +511,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getDefinitionByName(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getDefinitionByName(java + * .lang.String) */ public WorkflowDefinition getDefinitionByName(final String workflowName) { @@ -555,7 +528,8 @@ public class JBPMEngine extends BPMEngine public Object doInJbpm(JbpmContext context) { GraphSession graphSession = context.getGraphSession(); - ProcessDefinition processDef = graphSession.findLatestProcessDefinition(tenantService.getName(createLocalId(workflowName))); + ProcessDefinition processDef = graphSession.findLatestProcessDefinition(tenantService + .getName(createLocalId(workflowName))); return processDef == null ? null : createWorkflowDefinition(processDef); } }); @@ -567,8 +541,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getAllDefinitionsByName(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getAllDefinitionsByName( + * java.lang.String) */ @SuppressWarnings("unchecked") public List getAllDefinitionsByName(final String workflowName) @@ -581,7 +559,8 @@ public class JBPMEngine extends BPMEngine public Object doInJbpm(JbpmContext context) { GraphSession graphSession = context.getGraphSession(); - List processDefs = (List)graphSession.findAllProcessDefinitionVersions(tenantService.getName(createLocalId(workflowName))); + List processDefs = graphSession.findAllProcessDefinitionVersions(tenantService + .getName(createLocalId(workflowName))); List workflowDefs = new ArrayList(processDefs.size()); for (ProcessDefinition processDef : processDefs) { @@ -599,8 +578,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getDefinitionImage(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getDefinitionImage(java. + * lang.String) */ public byte[] getDefinitionImage(final String workflowDefinitionId) { @@ -625,8 +608,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getAllTaskDefinitions(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getAllTaskDefinitions(java + * .lang.String) */ @SuppressWarnings("unchecked") public List getTaskDefinitions(final String workflowDefinitionId) @@ -650,7 +637,10 @@ public class JBPMEngine extends BPMEngine String processName = processDefinition.getName(); if (tenantService.isEnabled()) { - tenantService.checkDomain(processName); // throws exception if domain mismatch + tenantService.checkDomain(processName); // throws + // exception + // if domain + // mismatch } TaskMgmtDefinition taskMgmtDef = processDefinition.getTaskMgmtDefinition(); @@ -674,8 +664,10 @@ public class JBPMEngine extends BPMEngine /** * Gets a jBPM process definition * - * @param graphSession jBPM graph session - * @param workflowDefinitionId workflow definition id + * @param graphSession + * jBPM graph session + * @param workflowDefinitionId + * workflow definition id * @return process definition */ protected ProcessDefinition getProcessDefinition(GraphSession graphSession, String workflowDefinitionId) @@ -686,7 +678,11 @@ public class JBPMEngine extends BPMEngine { try { - tenantService.checkDomain(processDefinition.getName()); // throws exception if domain mismatch + tenantService.checkDomain(processDefinition.getName()); // throws + // exception + // if + // domain + // mismatch } catch (RuntimeException re) { @@ -707,9 +703,12 @@ public class JBPMEngine extends BPMEngine // Workflow Instance Management... // - - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#startWorkflow(java.lang.String, java.util.Map) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#startWorkflow(java.lang. + * String, java.util.Map) */ public WorkflowPath startWorkflow(final String workflowDefinitionId, final Map parameters) { @@ -720,7 +719,8 @@ public class JBPMEngine extends BPMEngine @SuppressWarnings("synthetic-access") public Object doInJbpm(JbpmContext context) { - // initialise jBPM actor (for any processes that wish to record the initiator) + // initialise jBPM actor (for any processes that wish to + // record the initiator) String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); context.setActorId(currentUserName); @@ -732,20 +732,31 @@ public class JBPMEngine extends BPMEngine // assign initial process context ContextInstance processContext = processInstance.getContextInstance(); - processContext.setVariable("cancelled", false); + processContext.setVariable(WorkflowConstants.PROP_CANCELLED, false); + if(parameters!=null) + { + Serializable packageNode = parameters.get(WorkflowModel.ASSOC_PACKAGE); + if (packageNode != null) + { + String pckgName = mapQNameToName(WorkflowModel.ASSOC_PACKAGE); + processContext.setVariable(pckgName, new JBPMNode((NodeRef) packageNode, serviceRegistry)); + } + } NodeRef companyHome = getCompanyHome(); - processContext.setVariable("companyhome", new JBPMNode(companyHome, serviceRegistry)); + processContext.setVariable(WorkflowConstants.PROP_COMPANY_HOME, new JBPMNode(companyHome, serviceRegistry)); NodeRef initiatorPerson = mapNameToPerson(currentUserName); if (initiatorPerson != null) { - processContext.setVariable("initiator", new JBPMNode(initiatorPerson, serviceRegistry)); - NodeRef initiatorHome = (NodeRef)nodeService.getProperty(initiatorPerson, ContentModel.PROP_HOMEFOLDER); + processContext.setVariable(WorkflowConstants.PROP_INITIATOR, new JBPMNode(initiatorPerson, serviceRegistry)); + NodeRef initiatorHome = (NodeRef) nodeService.getProperty(initiatorPerson, + ContentModel.PROP_HOMEFOLDER); if (initiatorHome != null) { - processContext.setVariable("initiatorhome", new JBPMNode(initiatorHome, serviceRegistry)); + processContext.setVariable(WorkflowConstants.PROP_INITIATOR_HOME, new JBPMNode(initiatorHome, serviceRegistry)); } } - processContext.setVariable("workflowinstanceid", createGlobalId(new Long(processInstance.getId()).toString())); + processContext.setVariable(WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID, createGlobalId(new Long(processInstance.getId()) + .toString())); // create the start task if one exists Token token = processInstance.getRootToken(); @@ -779,25 +790,36 @@ public class JBPMEngine extends BPMEngine return new WorkflowException(msg, e); } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getActiveWorkflows(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getActiveWorkflows(java. + * lang.String) */ - @SuppressWarnings("unchecked") public List getActiveWorkflows(final String workflowDefinitionId) { return getWorkflowsInternal(workflowDefinitionId, true); } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getCompletedWorkflows(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getCompletedWorkflows(java + * .lang.String) */ public List getCompletedWorkflows(final String workflowDefinitionId) { return getWorkflowsInternal(workflowDefinitionId, false); } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getWorkflows(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getWorkflows(java.lang.String + * ) */ public List getWorkflows(final String workflowDefinitionId) { @@ -814,11 +836,13 @@ public class JBPMEngine extends BPMEngine public Object doInJbpm(JbpmContext context) { GraphSession graphSession = context.getGraphSession(); - List processInstances = graphSession.findProcessInstances(getJbpmId(workflowDefinitionId)); + List processInstances = graphSession + .findProcessInstances(getJbpmId(workflowDefinitionId)); List workflowInstances = new ArrayList(processInstances.size()); for (ProcessInstance processInstance : processInstances) { - if ((active == null) || (!active && processInstance.hasEnded()) || (active && !processInstance.hasEnded())) + if ((active == null) || (!active && processInstance.hasEnded()) + || (active && !processInstance.hasEnded())) { WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); workflowInstances.add(workflowInstance); @@ -835,8 +859,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getWorkflowById(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getWorkflowById(java.lang + * .String) */ public WorkflowInstance getWorkflowById(final String workflowId) { @@ -849,7 +877,7 @@ public class JBPMEngine extends BPMEngine // retrieve workflow GraphSession graphSession = context.getGraphSession(); ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); - return processInstance == null ? null : createWorkflowInstance(processInstance); + return createWorkflowInstance(processInstance); } }); } @@ -867,7 +895,11 @@ public class JBPMEngine extends BPMEngine { try { - tenantService.checkDomain(processInstance.getProcessDefinition().getName()); // throws exception if domain mismatch + tenantService.checkDomain(processInstance.getProcessDefinition().getName()); // throws + // exception + // if + // domain + // mismatch } catch (RuntimeException re) { @@ -879,8 +911,11 @@ public class JBPMEngine extends BPMEngine /** * Gets a jBPM Process Instance - * @param graphSession jBPM graph session - * @param workflowId workflow id + * + * @param graphSession + * jBPM graph session + * @param workflowId + * workflow id * @return process instance */ protected ProcessInstance getProcessInstance(GraphSession graphSession, String workflowId) @@ -894,8 +929,12 @@ public class JBPMEngine extends BPMEngine return processInstance; } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getWorkflowPaths(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getWorkflowPaths(java.lang + * .String) */ @SuppressWarnings("unchecked") public List getWorkflowPaths(final String workflowId) @@ -933,8 +972,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getPathProperties(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getPathProperties(java.lang + * .String) */ @SuppressWarnings("unchecked") public Map getPathProperties(final String pathId) @@ -983,8 +1026,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang + * .String) */ public WorkflowInstance cancelWorkflow(final String workflowId) { @@ -999,8 +1046,10 @@ public class JBPMEngine extends BPMEngine ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); processInstance.getContextInstance().setVariable("cancelled", true); processInstance.end(); - // TODO: Determine if this is the most appropriate way to cancel workflow... - // It might be useful to record point at which it was cancelled etc + // TODO: Determine if this is the most appropriate way to + // cancel workflow... + // It might be useful to record point at which it was + // cancelled etc WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); // delete the process instance @@ -1016,8 +1065,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang + * .String) */ public WorkflowInstance deleteWorkflow(final String workflowId) { @@ -1030,12 +1083,11 @@ public class JBPMEngine extends BPMEngine // retrieve and cancel process instance GraphSession graphSession = context.getGraphSession(); ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); - WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); // delete the process instance graphSession.deleteProcessInstance(processInstance, true, true); - workflowInstance.active = false; - workflowInstance.endDate = new Date(); + Date endDate = new Date(); + WorkflowInstance workflowInstance = createWorkflowInstance(processInstance, endDate); return workflowInstance; } }); @@ -1047,8 +1099,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#signal(java.lang.String, java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#signal(java.lang.String, + * java.lang.String) */ public WorkflowPath signal(final String pathId, final String transition) { @@ -1072,7 +1128,8 @@ public class JBPMEngine extends BPMEngine Node node = token.getNode(); if (!node.hasLeavingTransition(transition)) { - throw new WorkflowException("Transition '" + transition + "' is invalid for Workflow path '" + pathId + "'"); + throw new WorkflowException("Transition '" + transition + + "' is invalid for Workflow path '" + pathId + "'"); } token.signal(transition); } @@ -1093,8 +1150,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#fireEvent(java.lang.String, java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#fireEvent(java.lang.String, + * java.lang.String) */ public WorkflowPath fireEvent(final String pathId, final String event) { @@ -1106,22 +1167,18 @@ public class JBPMEngine extends BPMEngine public Object doInJbpm(JbpmContext context) { // NOTE: Do not allow jBPM built-in events to be fired - if (event.equals(Event.EVENTTYPE_AFTER_SIGNAL) || - event.equals(Event.EVENTTYPE_BEFORE_SIGNAL) || - event.equals(Event.EVENTTYPE_NODE_ENTER) || - event.equals(Event.EVENTTYPE_NODE_LEAVE) || - event.equals(Event.EVENTTYPE_PROCESS_END) || - event.equals(Event.EVENTTYPE_PROCESS_START) || - event.equals(Event.EVENTTYPE_SUBPROCESS_CREATED) || - event.equals(Event.EVENTTYPE_SUBPROCESS_END) || - event.equals(Event.EVENTTYPE_SUPERSTATE_ENTER) || - event.equals(Event.EVENTTYPE_SUPERSTATE_LEAVE) || - event.equals(Event.EVENTTYPE_TASK_ASSIGN) || - event.equals(Event.EVENTTYPE_TASK_CREATE) || - event.equals(Event.EVENTTYPE_TASK_END) || - event.equals(Event.EVENTTYPE_TASK_START) || - event.equals(Event.EVENTTYPE_TIMER) || - event.equals(Event.EVENTTYPE_TRANSITION)) + if (event.equals(Event.EVENTTYPE_AFTER_SIGNAL) || event.equals(Event.EVENTTYPE_BEFORE_SIGNAL) + || event.equals(Event.EVENTTYPE_NODE_ENTER) || event.equals(Event.EVENTTYPE_NODE_LEAVE) + || event.equals(Event.EVENTTYPE_PROCESS_END) + || event.equals(Event.EVENTTYPE_PROCESS_START) + || event.equals(Event.EVENTTYPE_SUBPROCESS_CREATED) + || event.equals(Event.EVENTTYPE_SUBPROCESS_END) + || event.equals(Event.EVENTTYPE_SUPERSTATE_ENTER) + || event.equals(Event.EVENTTYPE_SUPERSTATE_LEAVE) + || event.equals(Event.EVENTTYPE_TASK_ASSIGN) + || event.equals(Event.EVENTTYPE_TASK_CREATE) || event.equals(Event.EVENTTYPE_TASK_END) + || event.equals(Event.EVENTTYPE_TASK_START) || event.equals(Event.EVENTTYPE_TIMER) + || event.equals(Event.EVENTTYPE_TRANSITION)) { String msg = messageService.getMessage(ERR_INVALID_EVENT, event); throw new WorkflowException(msg); @@ -1167,8 +1224,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getTasksForWorkflowPath(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getTasksForWorkflowPath( + * java.lang.String) */ @SuppressWarnings("unchecked") public List getTasksForWorkflowPath(final String pathId) @@ -1195,8 +1256,11 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.WorkflowComponent#getTimers(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.WorkflowComponent#getTimers(java.lang.String) */ @SuppressWarnings("unchecked") public List getTimers(final String workflowId) @@ -1239,9 +1303,12 @@ public class JBPMEngine extends BPMEngine // Task Management ... // - - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#getAssignedTasks(java.lang.String, org.alfresco.service.cmr.workflow.WorkflowTaskState) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#getAssignedTasks(java.lang.String + * , org.alfresco.service.cmr.workflow.WorkflowTaskState) */ @SuppressWarnings("unchecked") public List getAssignedTasks(final String authority, final WorkflowTaskState state) @@ -1271,15 +1338,18 @@ public class JBPMEngine extends BPMEngine /** * Gets the completed task list for the specified actor * - * TODO: This method provides a query that's not in JBPM! Look to have JBPM implement this. + * TODO: This method provides a query that's not in JBPM! Look + * to have JBPM implement this. * - * @param jbpmContext the jbpm context - * @param actorId the actor to retrieve tasks for + * @param jbpmContext + * the jbpm context + * @param actorId + * the actor to retrieve tasks for * @return the tasks */ - private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) + private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) { - List result = null; + List result = null; try { Session session = jbpmContext.getSession(); @@ -1303,8 +1373,11 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) */ @SuppressWarnings("unchecked") public List getPooledTasks(final List authorities) @@ -1329,8 +1402,12 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#queryTasks(org.alfresco.service.cmr.workflow.WorkflowTaskFilter) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#queryTasks(org.alfresco.service + * .cmr.workflow.WorkflowTaskFilter) */ @SuppressWarnings("unchecked") public List queryTasks(final WorkflowTaskQuery query) @@ -1375,7 +1452,8 @@ public class JBPMEngine extends BPMEngine } catch (RuntimeException re) { - // deliberately skip this one - due to domain mismatch - eg. when querying by group authority + // deliberately skip this one - due to domain mismatch - eg. + // when querying by group authority continue; } } @@ -1456,8 +1534,10 @@ public class JBPMEngine extends BPMEngine // process custom properties if (query.getProcessCustomProps() != null) { - // TODO: Due to Hibernate bug http://opensource.atlassian.com/projects/hibernate/browse/HHH-957 - // it's not possible to perform a sub-select with the criteria api. For now issue a + // TODO: Due to Hibernate bug + // http://opensource.atlassian.com/projects/hibernate/browse/HHH-957 + // it's not possible to perform a sub-select with the criteria api. + // For now issue a // secondary query and create an IN clause. Map props = query.getProcessCustomProps(); if (props.size() > 0) @@ -1548,7 +1628,8 @@ public class JBPMEngine extends BPMEngine return task; } - private Object[] getProcessIds(List processList) { + private Object[] getProcessIds(List processList) + { ArrayList ids = new ArrayList(processList.size()); if (processList.isEmpty()) { @@ -1579,7 +1660,7 @@ public class JBPMEngine extends BPMEngine // process active? if (query.isActive() != null) { - process = (process == null) ? root.createCriteria("processInstance") : process; + process = root.createCriteria("processInstance"); if (query.isActive()) { process.add(Restrictions.isNull("end")); @@ -1622,8 +1703,11 @@ public class JBPMEngine extends BPMEngine /** * Gets a jBPM Task Instance - * @param taskSession jBPM task session - * @param taskId task id + * + * @param taskSession + * jBPM task session + * @param taskId + * task id * @return task instance */ protected TaskInstance getTaskInstance(TaskMgmtSession taskSession, String taskId) @@ -1634,7 +1718,8 @@ public class JBPMEngine extends BPMEngine { try { - tenantService.checkDomain(taskInstance.getTask().getProcessDefinition().getName()); // throws exception if domain mismatch + // throws exception if domain mismatch + tenantService.checkDomain(taskInstance.getTask().getProcessDefinition().getName()); } catch (RuntimeException re) { @@ -1650,10 +1735,16 @@ public class JBPMEngine extends BPMEngine return taskInstance; } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, java.util.Map, java.util.Map, java.util.Map) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, + * java.util.Map, java.util.Map, java.util.Map) */ - public WorkflowTask updateTask(final String taskId, final Map properties, final Map> add, final Map> remove) + public WorkflowTask updateTask(final String taskId, + final Map properties, + final Map> add, final Map> remove) { try { @@ -1667,81 +1758,82 @@ public class JBPMEngine extends BPMEngine TaskInstance taskInstance = getTaskInstance(taskSession, taskId); // create properties to set on task instance - Map newProperties = properties !=null ? properties : new HashMap(10); - - if (add != null || remove != null) + Map newProperties = new HashMap(10); + if(properties!=null) { - Map existingProperties = getTaskProperties(taskInstance, false); - - if (add != null) + newProperties.putAll(properties); + } + Map existingProperties = getTaskProperties(taskInstance, false); + if (add != null) + { + // add new associations + for (Entry> toAdd : add.entrySet()) { - // add new associations - for (Entry> toAdd : add.entrySet()) - { - // retrieve existing list of noderefs for association - QName key = toAdd.getKey(); - Serializable existingValue = newProperties.get(key); + // retrieve existing list of noderefs for + // association + QName key = toAdd.getKey(); + Serializable existingValue = newProperties.get(key); - if (existingValue == null) + if (existingValue == null) + { + existingValue = existingProperties.get(key); + } + // make the additions + if (existingValue == null) + { + newProperties.put(key, (Serializable)toAdd.getValue()); + } + else + { + List existingAdd; + if (existingValue instanceof List) { - existingValue = existingProperties.get(key); - } - // make the additions - if (existingValue == null) - { - newProperties.put(key, (Serializable)toAdd.getValue()); + existingAdd = (List) existingValue; } else { - List existingAdd; - if (existingValue instanceof List) - { - existingAdd = (List) existingValue; - } - else - { - existingAdd = new LinkedList(); - existingAdd.add((NodeRef) existingValue); - } - - for (NodeRef nodeRef : toAdd.getValue()) - { - if (!(existingAdd.contains(nodeRef))) - { - existingAdd.add(nodeRef); - } - } - newProperties.put(key, (Serializable) existingAdd); + existingAdd = new LinkedList(); + existingAdd.add((NodeRef) existingValue); } + + for (NodeRef nodeRef : toAdd.getValue()) + { + if (!(existingAdd.contains(nodeRef))) + { + existingAdd.add(nodeRef); + } + } + newProperties.put(key, (Serializable) existingAdd); } } + } - if (remove != null) + if (remove != null) + { + // add new associations + for (Entry> toRemove: remove.entrySet()) { - // add new associations - for (Entry> toRemove: remove.entrySet()) - { - // retrieve existing list of noderefs for association - QName key = toRemove.getKey(); - Serializable existingValue = newProperties.get(key); + // retrieve existing list of noderefs for + // association + QName key = toRemove.getKey(); + Serializable existingValue = newProperties.get(key); - if (existingValue == null) + if (existingValue == null) + { + existingValue = existingProperties.get(key); + } + // make the subtractions + if (existingValue != null) + { + if(existingValue instanceof List) { - existingValue = existingProperties.get(key); + List existingRemove = (List) existingValue; + existingRemove.removeAll(toRemove.getValue()); + newProperties.put(key, (Serializable) existingRemove); } - // make the subtractions - if (existingValue != null) + else if(toRemove.getValue().contains(existingValue)) { - if(existingValue instanceof List) - { - List existingRemove = (List) existingValue; - existingRemove.removeAll(toRemove.getValue()); - newProperties.put(key, (Serializable) existingRemove); - } - else if(toRemove.getValue().contains(existingValue)) - { - newProperties.put(key, new LinkedList()); - } + newProperties.put(key, new LinkedList()); } } } @@ -1757,7 +1849,8 @@ public class JBPMEngine extends BPMEngine context.save(processInstance); } - // note: the ending of a task may not have signalled (i.e. more than one task exists at + // note: the ending of a task may not have signalled (i.e. + // more than one task exists at // this node) return createWorkflowTask(taskInstance); } @@ -1770,7 +1863,9 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.alfresco.repo.workflow.TaskComponent#startTask(java.lang.String) */ public WorkflowTask startTask(String taskId) @@ -1779,8 +1874,11 @@ public class JBPMEngine extends BPMEngine throw new UnsupportedOperationException(); } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#suspendTask(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#suspendTask(java.lang.String) */ public WorkflowTask suspendTask(String taskId) { @@ -1788,8 +1886,11 @@ public class JBPMEngine extends BPMEngine throw new UnsupportedOperationException(); } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#endTask(java.lang.String, java.lang.String) + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.workflow.TaskComponent#endTask(java.lang.String, + * java.lang.String) */ public WorkflowTask endTask(final String taskId, final String transition) { @@ -1837,7 +1938,8 @@ public class JBPMEngine extends BPMEngine ProcessInstance processInstance = taskInstance.getToken().getProcessInstance(); context.save(processInstance); - // note: the ending of a task may not have signalled (i.e. more than one task exists at + // note: the ending of a task may not have signalled (i.e. + // more than one task exists at // this node) return createWorkflowTask(taskInstance); } @@ -1850,8 +1952,11 @@ public class JBPMEngine extends BPMEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#getTaskById(java.lang.String) + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#getTaskById(java.lang.String) */ public WorkflowTask getTaskById(final String taskId) { @@ -1864,7 +1969,7 @@ public class JBPMEngine extends BPMEngine // retrieve task TaskMgmtSession taskSession = context.getTaskMgmtSession(); TaskInstance taskInstance = getTaskInstance(taskSession, taskId); - return taskInstance == null ? null : createWorkflowTask(taskInstance); + return createWorkflowTask(taskInstance); } }); } @@ -1875,6 +1980,38 @@ public class JBPMEngine extends BPMEngine } } + @Override + public WorkflowTask getStartTask(final String workflowInstanceId) + { + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowInstanceId); + Task startTask = processInstance.getProcessDefinition().getTaskMgmtDefinition().getStartTask(); + + // retrieve task + Session session = context.getSession(); + Criteria taskCriteria = session.createCriteria(TaskInstance.class); + taskCriteria.add(Restrictions.eq("name", startTask.getName())); + Criteria process = taskCriteria.createCriteria("processInstance"); + process.add(Restrictions.eq("id", processInstance.getId())); + TaskInstance taskInstance = (TaskInstance) taskCriteria.uniqueResult(); + return createWorkflowTask(taskInstance); + } + }); + } + catch (JbpmException e) + { + String msg = messageService.getMessage(ERR_GET_START_TASK, workflowInstanceId); + throw new WorkflowException(msg, e); + } + } + // // Helpers... // @@ -1901,10 +2038,13 @@ public class JBPMEngine extends BPMEngine /** - * Construct a Process Definition from the provided Process Definition stream + * Construct a Process Definition from the provided Process Definition + * stream * - * @param workflowDefinition stream to create process definition from - * @param mimetype mimetype of stream + * @param workflowDefinition + * stream to create process definition from + * @param mimetype + * mimetype of stream * @return process definition */ @SuppressWarnings("unchecked") @@ -1937,7 +2077,8 @@ public class JBPMEngine extends BPMEngine try { zipInputStream.close(); - } catch (IOException e) + } + catch (IOException e) { // Intentionally empty! } @@ -1967,7 +2108,7 @@ public class JBPMEngine extends BPMEngine throw new JbpmException(msg); } - if ((compiledDef != null) && tenantService.isEnabled()) + if (tenantService.isEnabled()) { compiledDef.def.setName(tenantService.getName(compiledDef.def.getName())); } @@ -1975,68 +2116,11 @@ public class JBPMEngine extends BPMEngine return compiledDef; } - /** - * Gets the Task definition of the specified Task - * - * @param task the task - * @return the task definition - */ - private TypeDefinition getTaskDefinition(Task task) - { - // TODO: Extend jBPM task instance to include dictionary definition qname? - QName typeName = QName.createQName(task.getName(), namespaceService); - TypeDefinition typeDef = dictionaryService.getType(typeName); - if (typeDef == null) - { - typeDef = dictionaryService.getType(task.getStartState() == null ? WorkflowModel.TYPE_WORKFLOW_TASK : WorkflowModel.TYPE_START_TASK); - if (typeDef == null) - { - String msg = messageService.getMessage(ERR_GET_TASK_DEF, WorkflowModel.TYPE_WORKFLOW_TASK); - throw new WorkflowException( msg); - } - } - return typeDef; - } - - /** - * Convert the specified Type definition to an anonymous Type definition. - * - * This collapses all mandatory aspects into a single Type definition. - * - * @param typeDef the type definition - * @return the anonymous type definition - */ - private TypeDefinition getAnonymousTaskDefinition(TypeDefinition typeDef) - { - List aspects = typeDef.getDefaultAspects(); - List aspectNames = new ArrayList(aspects.size()); - getMandatoryAspects(typeDef, aspectNames); - return dictionaryService.getAnonymousType(typeDef.getName(), aspectNames); - } - - /** - * Gets a flattened list of all mandatory aspects for a given class - * - * @param classDef the class - * @param aspects a list to hold the mandatory aspects - */ - private void getMandatoryAspects(ClassDefinition classDef, List aspects) - { - for (AspectDefinition aspect : classDef.getDefaultAspects()) - { - QName aspectName = aspect.getName(); - if (!aspects.contains(aspectName)) - { - aspects.add(aspect.getName()); - getMandatoryAspects(aspect, aspects); - } - } - } - /** * Get JBoss JBPM Id from Engine Global Id * - * @param id global id + * @param id + * global id * @return JBoss JBPM Id */ protected long getJbpmId(String id) @@ -2056,8 +2140,10 @@ public class JBPMEngine extends BPMEngine /** * Get the JBoss JBPM Token for the Workflow Path * - * @param session JBoss JBPM Graph Session - * @param pathId workflow path id + * @param session + * JBoss JBPM Graph Session + * @param pathId + * workflow path id * @return JBoss JBPM Token */ protected Token getWorkflowToken(GraphSession session, String pathId) @@ -2086,14 +2172,16 @@ public class JBPMEngine extends BPMEngine /** * Gets Properties of Task * - * @param instance task instance - * @param properties properties to set + * @param instance + * task instance + * @param properties + * properties to set */ @SuppressWarnings("unchecked") protected Map getTaskProperties(TaskInstance instance, boolean localProperties) { // retrieve type definition for task - TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + TypeDefinition taskDef = getFullTaskDefinition(instance); Map taskProperties = taskDef.getProperties(); Map taskAssocs = taskDef.getAssociations(); @@ -2128,7 +2216,8 @@ public class JBPMEngine extends BPMEngine String key = entry.getKey(); QName qname = mapNameToQName(key); - // add variable, only if part of task definition or locally defined on task + // add variable, only if part of task definition or locally defined + // on task boolean isAssoc = taskAssocs.containsKey(qname); if (taskProperties.containsKey(qname) || isAssoc || instance.hasVariableLocally(key)) { @@ -2156,11 +2245,11 @@ public class JBPMEngine extends BPMEngine } // map jBPM task instance collections to associations - Set pooledActors = instance.getPooledActors(); + Set pooledActors = instance.getPooledActors(); if (pooledActors != null) { List pooledNodeRefs = new ArrayList(pooledActors.size()); - for (PooledActor pooledActor : (Set)pooledActors) + for (PooledActor pooledActor : pooledActors) { NodeRef pooledNodeRef = null; String pooledActorId = pooledActor.getActorId(); @@ -2183,11 +2272,21 @@ public class JBPMEngine extends BPMEngine return properties; } + private TypeDefinition getFullTaskDefinition(TaskInstance instance) + { + Task task = instance.getTask(); + TypeDefinition taskType = factory.getTaskTypeDefinition(task.getName(), task.getStartState() != null); + TypeDefinition taskDef = dictionaryService.getAnonymousType(taskType.getName()); + return taskDef; + } + /** * Sets Properties of Task * - * @param instance task instance - * @param properties properties to set + * @param instance + * task instance + * @param properties + * properties to set */ protected void setTaskProperties(TaskInstance instance, Map properties) { @@ -2196,8 +2295,7 @@ public class JBPMEngine extends BPMEngine return; } - // establish task definition - TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + TypeDefinition taskDef = getFullTaskDefinition(instance); Map taskProperties = taskDef.getProperties(); Map taskAssocs = taskDef.getAssociations(); @@ -2222,7 +2320,8 @@ public class JBPMEngine extends BPMEngine // convert property value if (value instanceof Collection) { - value = (Serializable)DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), (Collection)value); + value = (Serializable) DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), + (Collection) value); } else { @@ -2346,40 +2445,6 @@ public class JBPMEngine extends BPMEngine instance.setPooledActors(pooledActors); continue; } - else if (key.equals(WorkflowModel.ASSOC_PACKAGE)) - { - // Attach workflow definition & instance id to Workflow Package in Repository - String name = mapQNameToName(key); - JBPMNode existingWorkflowPackage = (JBPMNode)instance.getVariable(name); - - // first check if provided workflow package has already been associated with another workflow instance - if (existingWorkflowPackage != null && value != null) - { - NodeRef newPackageNodeRef = ((JBPMNode)value).getNodeRef(); - ProcessInstance processInstance = instance.getToken().getProcessInstance(); - String packageInstanceId = (String)nodeService.getProperty(newPackageNodeRef, WorkflowModel.PROP_WORKFLOW_INSTANCE_ID); - if (packageInstanceId != null && packageInstanceId.length() > 0 && (processInstance.getId() == getJbpmId(packageInstanceId))) - { - String workflowInstanceId = createGlobalId(new Long(processInstance.getId()).toString()); - String msg = messageService.getMessage(ERR_PACKAGE_ALREADY_ASSOCIATED, newPackageNodeRef, workflowInstanceId, packageInstanceId); - throw new WorkflowException(msg); - } - } - - // initialise workflow package - if (existingWorkflowPackage == null && value != null) - { - // initialise workflow package - NodeRef newPackageNodeRef = ((JBPMNode)value).getNodeRef(); - ProcessInstance processInstance = instance.getToken().getProcessInstance(); - WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); - nodeService.setProperty(newPackageNodeRef, WorkflowModel.PROP_WORKFLOW_DEFINITION_ID, workflowInstance.definition.id); - nodeService.setProperty(newPackageNodeRef, WorkflowModel.PROP_WORKFLOW_DEFINITION_NAME, workflowInstance.definition.name); - nodeService.setProperty(newPackageNodeRef, WorkflowModel.PROP_WORKFLOW_INSTANCE_ID, workflowInstance.id); - } - - // NOTE: Fall-through to allow setting of Workflow Package on Task Instance - } } // untyped value, perform minimal conversion @@ -2392,7 +2457,8 @@ public class JBPMEngine extends BPMEngine } } - // no specific mapping to jBPM task has been established, so place into + // no specific mapping to jBPM task has been established, so place + // into // the generic task variable bag String name = mapQNameToName(key); instance.setVariableLocally(name, value); @@ -2408,7 +2474,8 @@ public class JBPMEngine extends BPMEngine /** * Sets Default Properties of Task * - * @param instance task instance + * @param instance + * task instance */ protected void setDefaultTaskProperties(TaskInstance instance) { @@ -2416,7 +2483,7 @@ public class JBPMEngine extends BPMEngine Map defaultValues = new HashMap(); // construct an anonymous type that flattens all mandatory aspects - ClassDefinition classDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + ClassDefinition classDef = getFullTaskDefinition(instance); Map propertyDefs = classDef.getProperties(); // for each property, determine if it has a default value @@ -2436,7 +2503,8 @@ public class JBPMEngine extends BPMEngine String description = (String)existingValues.get(WorkflowModel.PROP_DESCRIPTION); if (description == null || description.length() == 0) { - description = (String)instance.getContextInstance().getVariable(mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + description = (String) instance.getContextInstance().getVariable( + mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); if (description != null && description.length() > 0) { defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description); @@ -2444,7 +2512,7 @@ public class JBPMEngine extends BPMEngine else { WorkflowTask task = createWorkflowTask(instance); - defaultValues.put(WorkflowModel.PROP_DESCRIPTION, task.title); + defaultValues.put(WorkflowModel.PROP_DESCRIPTION, task.getTitle()); } } @@ -2458,14 +2526,16 @@ public class JBPMEngine extends BPMEngine /** * Sets default description for the Task * - * @param instance task instance + * @param instance + * task instance */ public void setDefaultStartTaskDescription(TaskInstance instance) { String description = instance.getTask().getDescription(); if (description == null || description.length() == 0) { - description = (String)instance.getContextInstance().getVariable(mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + description = (String) instance.getContextInstance().getVariable( + mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); if (description != null && description.length() > 0) { Map defaultValues = new HashMap(); @@ -2478,7 +2548,8 @@ public class JBPMEngine extends BPMEngine /** * Initialise Workflow Instance properties * - * @param startTask start task instance + * @param startTask + * start task instance */ protected void setDefaultWorkflowProperties(TaskInstance startTask) { @@ -2488,7 +2559,8 @@ public class JBPMEngine extends BPMEngine String workflowDescriptionName = mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); if (!processContext.hasVariable(workflowDescriptionName)) { - processContext.setVariable(workflowDescriptionName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + processContext.setVariable(workflowDescriptionName, taskProperties + .get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); } String workflowDueDateName = mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE); if (!processContext.hasVariable(workflowDueDateName)) @@ -2504,7 +2576,8 @@ public class JBPMEngine extends BPMEngine if (!processContext.hasVariable(workflowPackageName)) { Serializable packageNodeRef = taskProperties.get(WorkflowModel.ASSOC_PACKAGE); - processContext.setVariable(workflowPackageName, convertNodeRefs(packageNodeRef instanceof List, packageNodeRef)); + processContext.setVariable(workflowPackageName, convertNodeRefs(packageNodeRef instanceof List, + packageNodeRef)); } String workflowContextName = mapQNameToName(WorkflowModel.PROP_CONTEXT); if (!processContext.hasVariable(workflowContextName)) @@ -2517,7 +2590,8 @@ public class JBPMEngine extends BPMEngine /** * Get missing mandatory properties on Task * - * @param instance task instance + * @param instance + * task instance * @return array of missing property names (or null, if none) */ protected QName[] getMissingMandatoryTaskProperties(TaskInstance instance) @@ -2528,7 +2602,7 @@ public class JBPMEngine extends BPMEngine Map existingValues = getTaskProperties(instance, false); // retrieve definition of task - ClassDefinition classDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + ClassDefinition classDef = getFullTaskDefinition(instance); Map propertyDefs = classDef.getProperties(); Map assocDefs = classDef.getAssociations(); @@ -2536,7 +2610,8 @@ public class JBPMEngine extends BPMEngine for (Map.Entry entry : propertyDefs.entrySet()) { QName name = entry.getKey(); - if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI().equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) + if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI() + .equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) { boolean isMandatory = entry.getValue().isMandatory(); if (isMandatory) @@ -2556,7 +2631,8 @@ public class JBPMEngine extends BPMEngine for (Map.Entry entry : assocDefs.entrySet()) { QName name = entry.getKey(); - if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI().equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) + if (!(name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI) || (name.getNamespaceURI() + .equals(NamespaceService.SYSTEM_MODEL_1_0_URI)))) { boolean isMandatory = entry.getValue().isTargetMandatory(); if (isMandatory) @@ -2580,7 +2656,8 @@ public class JBPMEngine extends BPMEngine /** * Convert a jBPM Value to an Alfresco value * - * @param value jBPM value + * @param value + * jBPM value * @return alfresco value */ private Serializable convertValue(Object value) @@ -2620,8 +2697,10 @@ public class JBPMEngine extends BPMEngine /** * Convert a Repository association to JBPMNodeList or JBPMNode * - * @param isMany true => force conversion to list - * @param value value to convert + * @param isMany + * true => force conversion to list + * @param value + * value to convert * @return JBPMNodeList or JBPMNode */ @SuppressWarnings("unchecked") @@ -2634,7 +2713,7 @@ public class JBPMEngine extends BPMEngine // convert single node ref to list of node refs JBPMNodeList values = new JBPMNodeList(); values.add(new JBPMNode((NodeRef)value, serviceRegistry)); - value = (Serializable)values; + value = values; } else { @@ -2650,7 +2729,7 @@ public class JBPMEngine extends BPMEngine { values.add(new JBPMNode(nodeRef, serviceRegistry)); } - value = (Serializable)values; + value = values; } else { @@ -2665,7 +2744,8 @@ public class JBPMEngine extends BPMEngine /** * Convert person name to an Alfresco Person * - * @param names the person name to convert + * @param names + * the person name to convert * @return the Alfresco person */ private NodeRef mapNameToPerson(String name) @@ -2685,7 +2765,8 @@ public class JBPMEngine extends BPMEngine /** * Convert authority name to an Alfresco Authority * - * @param names the authority names to convert + * @param names + * the authority names to convert * @return the Alfresco authorities */ private NodeRef mapNameToAuthority(String name) @@ -2727,13 +2808,15 @@ public class JBPMEngine extends BPMEngine /** * Map jBPM variable name to QName * - * @param name jBPM variable name + * @param name + * jBPM variable name * @return qname */ private QName mapNameToQName(String name) { QName qname = null; - // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as new scheme (i.e. } -> _) + // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as + // new scheme (i.e. } -> _) String qnameStr = (name.indexOf('}') == -1) ? name.replaceFirst("_", ":") : name.replace("}", ":"); try { @@ -2749,12 +2832,14 @@ public class JBPMEngine extends BPMEngine /** * Map QName to jBPM variable name * - * @param name QName + * @param name + * QName * @return jBPM variable name */ private String mapQNameToName(QName name) { - // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as new scheme (i.e. } -> _) + // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as + // new scheme (i.e. } -> _) // NOTE: Use new scheme String nameStr = name.toPrefixString(this.namespaceService); if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':')) @@ -2767,9 +2852,12 @@ public class JBPMEngine extends BPMEngine /** * Get an I18N Label for a workflow item * - * @param displayId message resource id lookup - * @param labelKey label to lookup (title or description) - * @param defaultLabel default value if not found in message resource bundle + * @param displayId + * message resource id lookup + * @param labelKey + * label to lookup (title or description) + * @param defaultLabel + * default value if not found in message resource bundle * @return the label */ private String getLabel(String displayId, String labelKey, String defaultLabel) @@ -2792,7 +2880,8 @@ public class JBPMEngine extends BPMEngine { try { - return tenantService.getRootNode(nodeService, serviceRegistry.getSearchService(), namespaceService, companyHomePath, nodeService.getRootNode(companyHomeStore)); + return tenantService.getRootNode(nodeService, serviceRegistry.getSearchService(), namespaceService, + companyHomePath, nodeService.getRootNode(companyHomeStore)); } catch (RuntimeException re) { @@ -2802,7 +2891,8 @@ public class JBPMEngine extends BPMEngine } else { - List refs = unprotectedSearchService.selectNodes(nodeService.getRootNode(companyHomeStore), companyHomePath, null, namespaceService, false); + List refs = unprotectedSearchService.selectNodes(nodeService.getRootNode(companyHomeStore), + companyHomePath, null, namespaceService, false); if (refs.size() != 1) { String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_MULTIPLE, companyHomePath, refs.size()); @@ -2819,250 +2909,219 @@ public class JBPMEngine extends BPMEngine /** * Creates a Workflow Path * - * @param token JBoss JBPM Token + * @param token + * JBoss JBPM Token * @return Workflow Path */ protected WorkflowPath createWorkflowPath(Token token) { - WorkflowPath path = new WorkflowPath(); String tokenId = token.getFullName().replace("/", WORKFLOW_TOKEN_SEPERATOR); - path.id = createGlobalId(token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR + tokenId); - path.instance = createWorkflowInstance(token.getProcessInstance()); - path.node = createWorkflowNode(token.getNode()); - path.active = !token.hasEnded(); - return path; + String id = token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR + tokenId; + WorkflowInstance wfInstance = createWorkflowInstance(token.getProcessInstance()); + WorkflowNode node = createWorkflowNode(token.getNode()); + boolean isActive = !token.hasEnded(); + return factory.createPath(id, wfInstance, node, isActive); } /** * Creates a Workflow Node * - * @param node JBoss JBPM Node + * @param node + * JBoss JBPM Node * @return Workflow Node */ - @SuppressWarnings("unchecked") protected WorkflowNode createWorkflowNode(Node node) { String processName = node.getProcessDefinition().getName(); - - if (tenantService.isEnabled()) - { - tenantService.checkDomain(processName); // throws exception if domain mismatch - } - - WorkflowNode workflowNode = new WorkflowNode(); - workflowNode.name = node.getName(); - workflowNode.title = getLabel(processName + ".node." + workflowNode.name, TITLE_LABEL, workflowNode.name); - workflowNode.description = getLabel(processName + ".node." + workflowNode.name, DESC_LABEL, workflowNode.title); - workflowNode.type = getRealNode(node).getClass().getSimpleName(); + String name = node.getName(); + String type = getRealNode(node).getClass().getSimpleName(); // TODO: Is there a formal way of determing if task node? - workflowNode.isTaskNode = workflowNode.type.equals("TaskNode"); - List transitions = node.getLeavingTransitions(); - workflowNode.transitions = new WorkflowTransition[(transitions == null) ? 0 : transitions.size()]; + boolean isTaskNode = type.equals("TaskNode"); + List transitions = node.getLeavingTransitions(); + List wfTransitions; if (transitions != null) { - int i = 0; - for (Transition transition : (List)transitions) + wfTransitions = new ArrayList(transitions.size()); + for (Transition transition : transitions) { - workflowNode.transitions[i++] = createWorkflowTransition(transition); + wfTransitions.add(createWorkflowTransition(transition)); } + } + else + { + wfTransitions = Collections.emptyList(); } - return workflowNode; + WorkflowTransition[] transArr = wfTransitions.toArray(new WorkflowTransition[0]); + return factory.createNode(name, processName, name, null, type, isTaskNode, transArr); } /** * Create a Workflow Transition * - * @param transition JBoss JBPM Transition + * @param transition + * JBoss JBPM Transition * @return Workflow Transition */ protected WorkflowTransition createWorkflowTransition(Transition transition) { - if (tenantService.isEnabled()) - { - tenantService.checkDomain(transition.getProcessDefinition().getName()); // throws exception if domain mismatch - } - - WorkflowTransition workflowTransition = new WorkflowTransition(); - workflowTransition.id = transition.getName(); + String id = transition.getName(); Node node = transition.getFrom(); - workflowTransition.isDefault = node.getDefaultLeavingTransition().equals(transition); - if (workflowTransition.id == null || workflowTransition.id.length() == 0) + boolean isDefault = node.getDefaultLeavingTransition().equals(transition); + String title; + String description; + if (id == null || id.length() == 0) { - workflowTransition.title = getLabel(DEFAULT_TRANSITION_LABEL, TITLE_LABEL, workflowTransition.id); - workflowTransition.description = getLabel(DEFAULT_TRANSITION_LABEL, DESC_LABEL, workflowTransition.title); + title = getLabel(DEFAULT_TRANSITION_LABEL, TITLE_LABEL, id); + description = getLabel(DEFAULT_TRANSITION_LABEL, DESC_LABEL, title); } else { String nodeName = node.getName(); String processName = node.getProcessDefinition().getName(); - workflowTransition.title = getLabel(processName + ".node." + nodeName + ".transition." + workflowTransition.id, TITLE_LABEL, workflowTransition.id); - workflowTransition.description = getLabel(processName + ".node." + nodeName + ".transition." + workflowTransition.id, DESC_LABEL, workflowTransition.title); + title = getLabel(processName + ".node." + nodeName + ".transition." + id, TITLE_LABEL, id); + description = getLabel(processName + ".node." + nodeName + ".transition." + id, DESC_LABEL, title); } - return workflowTransition; + return new WorkflowTransition(id, title, description, isDefault); + } + + protected WorkflowInstance createWorkflowInstance(ProcessInstance instance) + { + return createWorkflowInstance(instance, null); } /** * Creates a Workflow Instance * - * @param instance JBoss JBPM Process Instance + * @param instance + * JBoss JBPM Process Instance + * @param endDate * @return Workflow instance */ - protected WorkflowInstance createWorkflowInstance(ProcessInstance instance) + @SuppressWarnings("unchecked") + protected WorkflowInstance createWorkflowInstance(ProcessInstance instance, Date endDate) { - if (tenantService.isEnabled()) - { - tenantService.checkDomain(instance.getProcessDefinition().getName()); // throws exception if domain mismatch - } + if(instance == null) + return null; - WorkflowInstance workflowInstance = new WorkflowInstance(); - workflowInstance.id = createGlobalId(new Long(instance.getId()).toString()); - workflowInstance.description = (String)instance.getContextInstance().getVariable(mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); - workflowInstance.priority = (Integer)instance.getContextInstance().getVariable(mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY)); - workflowInstance.dueDate = (Date)instance.getContextInstance().getVariable(mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE)); - workflowInstance.definition = createWorkflowDefinition(instance.getProcessDefinition()); - workflowInstance.active = !instance.hasEnded(); - JBPMNode initiator = (JBPMNode)instance.getContextInstance().getVariable("initiator"); - if (initiator != null) + String id = Long.toString(instance.getId()); + Map variables = instance.getContextInstance().getVariables(); + WorkflowDefinition definition = createWorkflowDefinition(instance.getProcessDefinition()); + Date startDate = instance.getStart(); + boolean isActive = false; + if (endDate == null) { - workflowInstance.initiator = initiator.getNodeRef(); + isActive = !instance.hasEnded(); + endDate = instance.getEnd(); } - JBPMNode context = (JBPMNode)instance.getContextInstance().getVariable(mapQNameToName(WorkflowModel.PROP_CONTEXT)); - if (context != null) - { - workflowInstance.context = context.getNodeRef(); - } - JBPMNode workflowPackage = (JBPMNode)instance.getContextInstance().getVariable(mapQNameToName(WorkflowModel.ASSOC_PACKAGE)); - if (workflowPackage != null) - { - workflowInstance.workflowPackage = workflowPackage.getNodeRef(); - } - workflowInstance.startDate = instance.getStart(); - workflowInstance.endDate = instance.getEnd(); - return workflowInstance; + return factory.createInstance(id, definition, variables, isActive, startDate, endDate); } /** * Creates a Workflow Definition * - * @param definition JBoss Process Definition + * @param definition + * JBoss Process Definition * @return Workflow Definition */ protected WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition) { - if (tenantService.isEnabled()) - { - tenantService.checkDomain(definition.getName()); // throws exception if domain mismatch - } + String id = Long.toString(definition.getId()); + String name = definition.getName(); + int version = definition.getVersion(); - final Task startTask = definition.getTaskMgmtDefinition().getStartTask(); - String name = tenantService.getBaseName(definition.getName()); - final String title = getLabel(name + ".workflow", TITLE_LABEL, name); - final String description = getLabel(name + ".workflow", DESC_LABEL, title); - return new WorkflowDefinition(createGlobalId(new Long(definition.getId()).toString()), - createGlobalId(name), - new Integer(definition.getVersion()).toString(), - title, - description, - (startTask != null - ? createWorkflowTaskDefinition(startTask) - : null)); + Task startTask = definition.getTaskMgmtDefinition().getStartTask(); + WorkflowTaskDefinition startTaskDef = createWorkflowTaskDefinition(startTask); + return factory.createDefinition(id, name, version, name, null, startTaskDef); } /** * Creates a Workflow Task * - * @param task JBoss Task Instance + * @param task + * JBoss Task Instance * @return Workflow Task */ protected WorkflowTask createWorkflowTask(TaskInstance task) { + if(task == null) + return null; + String processName = task.getTask().getProcessDefinition().getName(); if (tenantService.isEnabled()) { - tenantService.checkDomain(processName); // throws exception if domain mismatch + tenantService.checkDomain(processName); // throws exception if + // domain mismatch } - WorkflowTask workflowTask = new WorkflowTask(); - workflowTask.id = createGlobalId(new Long(task.getId()).toString()); - workflowTask.name = task.getName(); - workflowTask.path = createWorkflowPath(task.getToken()); - workflowTask.state = getWorkflowTaskState(task); - workflowTask.definition = createWorkflowTaskDefinition(task.getTask()); - workflowTask.properties = getTaskProperties(task, false); - workflowTask.title = getLabel(processName + ".task." + workflowTask.name, TITLE_LABEL, null); - if (workflowTask.title == null) - { - workflowTask.title = workflowTask.definition.metadata.getTitle(); - if (workflowTask.title == null) - { - workflowTask.title = workflowTask.name; - } - } - workflowTask.description = getLabel(processName + ".task." + workflowTask.name, DESC_LABEL, null); - if (workflowTask.description == null) - { - String description = workflowTask.definition.metadata.getDescription(); - workflowTask.description = (description == null) ? workflowTask.title : description; - } - return workflowTask; + String id = Long.toString(task.getId()); + String name = task.getName(); + WorkflowPath path = createWorkflowPath(task.getToken()); + WorkflowTaskState state = getWorkflowTaskState(task); + WorkflowTaskDefinition definition = createWorkflowTaskDefinition(task.getTask()); + Map properties = getTaskProperties(task, false); + return factory.createTask(id, definition, name, null, null, state, path, properties); } /** * Creates a Workflow Task Definition * - * @param task JBoss JBPM Task + * @param task + * JBoss JBPM Task * @return Workflow Task Definition */ protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task) { - WorkflowTaskDefinition taskDef = new WorkflowTaskDefinition(); - taskDef.id = task.getName(); - Node node = (task.getStartState() == null ? task.getTaskNode() : task.getStartState()); - taskDef.node = createWorkflowNode(node); - taskDef.metadata = getTaskDefinition(task); - return taskDef; + if (task == null) + return null; + String id = task.getName(); + boolean isStart = task.getStartState() != null; + Node node = isStart ? task.getStartState() : task.getTaskNode(); + WorkflowNode wfNode = createWorkflowNode(node); + return factory.createTaskDefinition(id, wfNode, id, isStart); } /** * Creates a Workflow Deployment * - * @param compiledDef compiled JBPM process definition + * @param compiledDef + * compiled JBPM process definition * @return workflow deployment */ protected WorkflowDeployment createWorkflowDeployment(CompiledProcessDefinition compiledDef) { - WorkflowDeployment deployment = new WorkflowDeployment(); - deployment.definition = createWorkflowDefinition(compiledDef.def); - deployment.problems = compiledDef.problems; - return deployment; + WorkflowDefinition definition = createWorkflowDefinition(compiledDef.def); + String[] problems = compiledDef.problems; + return factory.createDeployment(definition, problems); } /** * Creates a Workflow Timer - * @param timer jBPM Timer + * + * @param timer + * jBPM Timer * @return workflow timer */ protected WorkflowTimer createWorkflowTimer(Timer timer) { - WorkflowTimer workflowTimer = new WorkflowTimer(); - workflowTimer.id = createGlobalId(new Long(timer.getId()).toString()); - workflowTimer.name = timer.getName(); - workflowTimer.error = timer.getException(); - workflowTimer.dueDate = timer.getDueDate(); - workflowTimer.path = createWorkflowPath(timer.getToken()); + WorkflowPath path = createWorkflowPath(timer.getToken()); + + WorkflowTask workflowTask = null; TaskInstance taskInstance = timer.getTaskInstance(); if (taskInstance != null) { - workflowTimer.task = createWorkflowTask(taskInstance); + workflowTask = createWorkflowTask(taskInstance); } - return workflowTimer; + + return factory.createWorkflowTimer(new Long(timer.getId()).toString(), timer.getName(), + timer.getException(), timer.getDueDate(), path, workflowTask); } /** * Get the Workflow Task State for the specified JBoss JBPM Task * - * @param task task + * @param task + * task * @return task state */ protected WorkflowTaskState getWorkflowTaskState(TaskInstance task) @@ -3080,7 +3139,8 @@ public class JBPMEngine extends BPMEngine /** * Helper to retrieve the real jBPM Node * - * @param node Node + * @param node + * Node * @return real Node (i.e. the one that's not a Hibernate proxy) */ private Node getRealNode(Node node) diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java index e4c9fc9c4a..7855cfce58 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java @@ -59,18 +59,10 @@ import org.springframework.extensions.surf.util.I18NUtil; */ public class JBPMEngineTest extends BaseAlfrescoSpringTest { - /** - * - */ - private static final String USER3 = "JbpmEngineTestJoe"; - /** - * - */ - private static final String USER2 = "JbpmEngineTestJane"; - /** - * - */ private static final String USER1 = "JbpmEngineTestJohn"; + private static final String USER2 = "JbpmEngineTestJane"; + private static final String USER3 = "JbpmEngineTestJoe"; + private WorkflowComponent workflowComponent; private TaskComponent taskComponent; private WorkflowPackageComponent packageComponent; @@ -112,6 +104,11 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); } + public void todoTestGetStartTask() throws Exception + { + //TODO Implement + } + public void testGetWorkflowDefinitions() { List workflowDefs = workflowComponent.getDefinitions(); @@ -124,10 +121,10 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest { ClassPathResource processDef = new ClassPathResource("jbpmresources/test_processdefinition.xml"); WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); - testWorkflowDef = deployment.definition; + testWorkflowDef = deployment.getDefinition(); assertNotNull(testWorkflowDef); - assertEquals("jbpm$test", testWorkflowDef.name); - assertEquals("2", testWorkflowDef.version); + assertEquals("jbpm$test", testWorkflowDef.getName()); + assertEquals("2", testWorkflowDef.getVersion()); } @@ -135,7 +132,7 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest { try { - @SuppressWarnings("unused") WorkflowPath path = workflowComponent.startWorkflow("norfolknchance", null); + workflowComponent.startWorkflow("norfolknchance", null); fail("Failed to catch invalid definition id"); } catch(WorkflowException e) @@ -146,7 +143,7 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest // TODO: Determine why process definition is loaded, even though it doesn't exist // try // { -// @SuppressWarnings("unused") WorkflowPosition pos = workflowComponent.startProcess("1000", null); +// workflowComponent.startProcess("1000", null); // fail("Failed to catch workflow definition id that does not exist"); // } // catch(WorkflowException e) @@ -154,30 +151,30 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest // } WorkflowDefinition workflowDef = getTestDefinition(); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, null); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), null); assertNotNull(path); - assertTrue(path.id.endsWith("-@")); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.id, path.instance.definition.id); + assertTrue(path.getId().endsWith("-@")); + assertNotNull(path.getNode()); + assertNotNull(path.getInstance()); + assertEquals(workflowDef.getId(), path.getInstance().getDefinition().getId()); } public void testGetWorkflowById() { WorkflowDefinition workflowDef = getTestDefinition(); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, null); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), null); assertNotNull(path); - assertTrue(path.id.endsWith("-@")); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.id, path.instance.definition.id); - WorkflowInstance instance = workflowComponent.getWorkflowById(path.instance.id); + assertTrue(path.getId().endsWith("-@")); + assertNotNull(path.getNode()); + assertNotNull(path.getInstance()); + assertEquals(workflowDef.getId(), path.getInstance().getDefinition().getId()); + WorkflowInstance instance = workflowComponent.getWorkflowById(path.getInstance().getId()); assertNotNull(instance); - assertEquals(path.instance.id, instance.id); + assertEquals(path.getInstance().getId(), instance.getId()); - workflowComponent.cancelWorkflow(instance.id); - WorkflowInstance result = workflowComponent.getWorkflowById(instance.id); + workflowComponent.cancelWorkflow(instance.getId()); + WorkflowInstance result = workflowComponent.getWorkflowById(instance.getId()); assertNull("The workflow isntance should be null!", result); } @@ -196,24 +193,24 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); // context variable outside of task definition params.put(ContentModel.PROP_OWNER, AuthenticationUtil.getAdminUserName()); // task assignment - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), params); assertNotNull(path); - assertTrue(path.id.endsWith("-@")); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.id, path.instance.definition.id); - List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertTrue(path.getId().endsWith("-@")); + assertNotNull(path.getNode()); + assertNotNull(path.getInstance()); + assertEquals(workflowDef.getId(), path.getInstance().getDefinition().getId()); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks1); assertEquals(1, tasks1.size()); WorkflowTask task = tasks1.get(0); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); - assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_TASK_ID)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_DUE_DATE)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_PRIORITY)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); + assertTrue(task.getProperties().containsKey(ContentModel.PROP_OWNER)); - NodeRef initiator = path.instance.initiator; + NodeRef initiator = path.getInstance().getInitiator(); String initiatorUsername = (String)nodeService.getProperty(initiator, ContentModel.PROP_USERNAME); assertEquals(AuthenticationUtil.getAdminUserName(), initiatorUsername); } @@ -233,27 +230,27 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); // context variable outside of task definition params.put(ContentModel.PROP_OWNER, AuthenticationUtil.getAdminUserName()); // task assignment - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), params); assertNotNull(path); - assertTrue(path.id.endsWith("-@")); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.id, path.instance.definition.id); - List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + assertTrue(path.getId().endsWith("-@")); + assertNotNull(path.getNode()); + assertNotNull(path.getInstance()); + assertEquals(workflowDef.getId(), path.getInstance().getDefinition().getId()); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks1); assertEquals(1, tasks1.size()); WorkflowTask task = tasks1.get(0); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); - assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_TASK_ID)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_DUE_DATE)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_PRIORITY)); + assertTrue(task.getProperties().containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); + assertTrue(task.getProperties().containsKey(ContentModel.PROP_OWNER)); // update with null parameters try { - WorkflowTask taskU1 = taskComponent.updateTask(task.id, null, null, null); + WorkflowTask taskU1 = taskComponent.updateTask(task.getId(), null, null, null); assertNotNull(taskU1); } catch(Throwable e) @@ -264,8 +261,8 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest // update property value Map updateProperties2 = new HashMap(); updateProperties2.put(WorkflowModel.PROP_PERCENT_COMPLETE, 100); - WorkflowTask taskU2 = taskComponent.updateTask(task.id, updateProperties2, null, null); - assertEquals(100, taskU2.properties.get(WorkflowModel.PROP_PERCENT_COMPLETE)); + WorkflowTask taskU2 = taskComponent.updateTask(task.getId(), updateProperties2, null, null); + assertEquals(100, taskU2.getProperties().get(WorkflowModel.PROP_PERCENT_COMPLETE)); // add to assocation QName assocName = QName.createQName("", "TestAssoc"); @@ -275,9 +272,9 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest toAdd.add(new NodeRef("workspace://1/1003")); Map> addAssocs = new HashMap>(); addAssocs.put(assocName, toAdd); - WorkflowTask taskU3 = taskComponent.updateTask(task.id, null, addAssocs, null); - assertNotNull(taskU3.properties.get(assocName)); - assertEquals(3, ((List)taskU3.properties.get(assocName)).size()); + WorkflowTask taskU3 = taskComponent.updateTask(task.getId(), null, addAssocs, null); + assertNotNull(taskU3.getProperties().get(assocName)); + assertEquals(3, ((List)taskU3.getProperties().get(assocName)).size()); // add to assocation again List toAddAgain = new ArrayList(); @@ -285,9 +282,9 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest toAddAgain.add(new NodeRef("workspace://1/1005")); Map> addAssocsAgain = new HashMap>(); addAssocsAgain.put(assocName, toAddAgain); - WorkflowTask taskU4 = taskComponent.updateTask(task.id, null, addAssocsAgain, null); - assertNotNull(taskU4.properties.get(assocName)); - assertEquals(5, ((List)taskU4.properties.get(assocName)).size()); + WorkflowTask taskU4 = taskComponent.updateTask(task.getId(), null, addAssocsAgain, null); + assertNotNull(taskU4.getProperties().get(assocName)); + assertEquals(5, ((List)taskU4.getProperties().get(assocName)).size()); // remove assocation List toRemove = new ArrayList(); @@ -295,23 +292,23 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest toRemove.add(new NodeRef("workspace://1/1003")); Map> removeAssocs = new HashMap>(); removeAssocs.put(assocName, toRemove); - WorkflowTask taskU5 = taskComponent.updateTask(task.id, null, null, removeAssocs); - assertNotNull(taskU5.properties.get(assocName)); - assertEquals(3, ((List)taskU5.properties.get(assocName)).size()); + WorkflowTask taskU5 = taskComponent.updateTask(task.getId(), null, null, removeAssocs); + assertNotNull(taskU5.getProperties().get(assocName)); + assertEquals(3, ((List)taskU5.getProperties().get(assocName)).size()); } public void testGetWorkflowInstances() { WorkflowDefinition workflowDef = getTestDefinition(); - workflowComponent.startWorkflow(workflowDef.id, null); - workflowComponent.startWorkflow(workflowDef.id, null); - List instances = workflowComponent.getActiveWorkflows(workflowDef.id); + workflowComponent.startWorkflow(workflowDef.getId(), null); + workflowComponent.startWorkflow(workflowDef.getId(), null); + List instances = workflowComponent.getActiveWorkflows(workflowDef.getId()); assertNotNull(instances); assertEquals(2, instances.size()); for (WorkflowInstance instance : instances) { - assertEquals(workflowDef.id, instance.definition.id); + assertEquals(workflowDef.getId(), instance.getDefinition().getId()); } } @@ -319,37 +316,37 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest public void testGetPositions() { WorkflowDefinition workflowDef = getTestDefinition(); - workflowComponent.startWorkflow(workflowDef.id, null); - List instances = workflowComponent.getActiveWorkflows(workflowDef.id); + workflowComponent.startWorkflow(workflowDef.getId(), null); + List instances = workflowComponent.getActiveWorkflows(workflowDef.getId()); assertNotNull(instances); assertEquals(1, instances.size()); - List paths = workflowComponent.getWorkflowPaths(instances.get(0).id); + List paths = workflowComponent.getWorkflowPaths(instances.get(0).getId()); assertNotNull(paths); assertEquals(1, paths.size()); - assertEquals(instances.get(0).id, paths.get(0).instance.id); - assertTrue(paths.get(0).id.endsWith("-@")); + assertEquals(instances.get(0).getId(), paths.get(0).getInstance().getId()); + assertTrue(paths.get(0).getId().endsWith("-@")); } public void testCancelWorkflowInstance() throws Exception { WorkflowDefinition workflowDef = getTestDefinition(); - workflowComponent.startWorkflow(workflowDef.id, null); - List instances1 = workflowComponent.getActiveWorkflows(workflowDef.id); + workflowComponent.startWorkflow(workflowDef.getId(), null); + List instances1 = workflowComponent.getActiveWorkflows(workflowDef.getId()); assertNotNull(instances1); assertEquals(1, instances1.size()); List tasks = taskComponent.getAssignedTasks(AuthenticationUtil.getAdminUserName(), WorkflowTaskState.IN_PROGRESS); assertNotNull(tasks); assertTrue(tasks.size() > 0); - WorkflowInstance cancelledInstance = workflowComponent.cancelWorkflow(instances1.get(0).id); + WorkflowInstance cancelledInstance = workflowComponent.cancelWorkflow(instances1.get(0).getId()); assertNotNull(cancelledInstance); - assertFalse(cancelledInstance.active); - List instances2 = workflowComponent.getActiveWorkflows(workflowDef.id); + assertFalse(cancelledInstance.isActive()); + List instances2 = workflowComponent.getActiveWorkflows(workflowDef.getId()); assertNotNull(instances2); assertEquals(0, instances2.size()); List tasks1 = taskComponent.getAssignedTasks(AuthenticationUtil.getAdminUserName(), WorkflowTaskState.IN_PROGRESS); assertNotNull(tasks1); - tasks1 = filterTasksByWorkflowInstance(tasks1, cancelledInstance.id); + tasks1 = filterTasksByWorkflowInstance(tasks1, cancelledInstance.getId()); assertEquals(0, tasks1.size()); } @@ -383,7 +380,7 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest WorkflowPath path = workflowComponent.startWorkflow(parallelDef.getId(), parameters); WorkflowTask startTask = workflowComponent.getTasksForWorkflowPath(path.getId()).get(0); taskComponent.endTask(startTask.getId(), null); - checkInstanceExists(path.instance.getId(), parallelDef.getId(), true); + checkInstanceExists(path.getInstance().getId(), parallelDef.getId(), true); // Set all users to reject document. ParallelReject(USER1); @@ -428,9 +425,9 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest Map parameters = new HashMap(); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), rootNodeRef); WorkflowDefinition workflowDef = getTestDefinition(); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), parameters); assertNotNull(path); - WorkflowPath updatedPath = workflowComponent.signal(path.id, path.node.transitions[1].id); + WorkflowPath updatedPath = workflowComponent.signal(path.getId(), path.getNode().getTransitions()[1].getId()); assertNotNull(updatedPath); } @@ -442,22 +439,22 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), AuthenticationUtil.getAdminUserName()); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), rootNodeRef); parameters.put(QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package"), packageComponent.createPackage(null)); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), parameters); assertNotNull(path); - List tasks = workflowComponent.getTasksForWorkflowPath(path.id); + List tasks = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks); assertEquals(1, tasks.size()); - WorkflowTask updatedTask = taskComponent.endTask(tasks.get(0).id, path.node.transitions[0].id); + WorkflowTask updatedTask = taskComponent.endTask(tasks.get(0).getId(), path.getNode().getTransitions()[0].getId()); assertNotNull(updatedTask); List completedTasks = taskComponent.getAssignedTasks(AuthenticationUtil.getAdminUserName(), WorkflowTaskState.COMPLETED); assertNotNull(completedTasks); - completedTasks = filterTasksByWorkflowInstance(completedTasks, path.instance.id); + completedTasks = filterTasksByWorkflowInstance(completedTasks, path.getInstance().getId()); assertEquals(1, completedTasks.size()); List assignedTasks = taskComponent.getAssignedTasks(AuthenticationUtil.getAdminUserName(), WorkflowTaskState.IN_PROGRESS); assertNotNull(assignedTasks); - assignedTasks = filterTasksByWorkflowInstance(assignedTasks, path.instance.id); + assignedTasks = filterTasksByWorkflowInstance(assignedTasks, path.getInstance().getId()); assertEquals(1, assignedTasks.size()); - assertEquals("review", assignedTasks.get(0).name); + assertEquals("review", assignedTasks.get(0).getName()); } @@ -471,12 +468,12 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest Map parameters = new HashMap(); parameters.put(QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "assignees"), (Serializable)bpm_assignees); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), rootNodeRef); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), parameters); assertNotNull(path); - List tasks = workflowComponent.getTasksForWorkflowPath(path.id); + List tasks = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks); assertEquals(1, tasks.size()); - WorkflowTask updatedTask = taskComponent.endTask(tasks.get(0).id, "multi"); + WorkflowTask updatedTask = taskComponent.endTask(tasks.get(0).getId(), "multi"); assertNotNull(updatedTask); } @@ -488,20 +485,20 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), AuthenticationUtil.getAdminUserName()); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), rootNodeRef); parameters.put(QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package"), packageComponent.createPackage(null)); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), parameters); assertNotNull(path); - List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks1); assertEquals(1, tasks1.size()); - assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).state); - WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).id, null); + assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).getState()); + WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).getId(), null); assertNotNull(updatedTask); - assertEquals(WorkflowTaskState.COMPLETED, updatedTask.state); + assertEquals(WorkflowTaskState.COMPLETED, updatedTask.getState()); List completedTasks = taskComponent.getAssignedTasks(AuthenticationUtil.getAdminUserName(), WorkflowTaskState.COMPLETED); assertNotNull(completedTasks); - completedTasks = filterTasksByWorkflowInstance(completedTasks, path.instance.id); + completedTasks = filterTasksByWorkflowInstance(completedTasks, path.getInstance().getId()); assertEquals(1, completedTasks.size()); - assertEquals(WorkflowTaskState.COMPLETED, completedTasks.get(0).state); + assertEquals(WorkflowTaskState.COMPLETED, completedTasks.get(0).getState()); } @@ -511,15 +508,15 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest Map parameters = new HashMap(); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), AuthenticationUtil.getAdminUserName()); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), rootNodeRef); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), parameters); assertNotNull(path); assertNotNull(path); - List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks1); assertEquals(1, tasks1.size()); - WorkflowTask getTask = taskComponent.getTaskById(tasks1.get(0).id); + WorkflowTask getTask = taskComponent.getTaskById(tasks1.get(0).getId()); assertNotNull(getTask); - assertEquals(getTask.id, tasks1.get(0).id); + assertEquals(getTask.getId(), tasks1.get(0).getId()); } @@ -530,13 +527,13 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), AuthenticationUtil.getAdminUserName()); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), rootNodeRef); parameters.put(QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package"), packageComponent.createPackage(null)); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), parameters); assertNotNull(path); - List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks1); assertEquals(1, tasks1.size()); - assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).state); - WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).id, null); + assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).getState()); + WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).getId(), null); assertNotNull(updatedTask); } @@ -550,17 +547,17 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); assertNotNull(deployment); - WorkflowDefinition workflowDef = deployment.definition; + WorkflowDefinition workflowDef = deployment.getDefinition(); Map parameters = new HashMap(); parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), rootNodeRef); parameters.put(QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package"), packageComponent.createPackage(null)); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.getId(), parameters); assertNotNull(path); - List tasks1 = workflowComponent.getTasksForWorkflowPath(path.id); + List tasks1 = workflowComponent.getTasksForWorkflowPath(path.getId()); assertNotNull(tasks1); assertEquals(1, tasks1.size()); - assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).state); - WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).id, null); + assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).getState()); + WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).getId(), null); assertNotNull(updatedTask); } @@ -611,7 +608,7 @@ public class JBPMEngineTest extends BaseAlfrescoSpringTest List filteredTasks = new ArrayList(); for (WorkflowTask task : tasks) { - if (task.path.instance.id.equals(workflowInstanceId)) + if (task.getPath().getInstance().getId().equals(workflowInstanceId)) { filteredTasks.add(task); } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineUnitTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineUnitTest.java deleted file mode 100644 index e3082ccae1..0000000000 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineUnitTest.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ - -package org.alfresco.repo.workflow.jbpm; - -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.i18n.MessageService; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.workflow.BPMEngineRegistry; -import org.alfresco.repo.workflow.WorkflowModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowDeployment; -import org.alfresco.service.cmr.workflow.WorkflowException; -import org.alfresco.service.cmr.workflow.WorkflowInstance; -import org.alfresco.service.cmr.workflow.WorkflowPath; -import org.alfresco.service.cmr.workflow.WorkflowTask; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.NamespaceServiceMemoryImpl; -import org.alfresco.service.namespace.QName; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.io.ClassPathResource; -import org.springframework.test.AbstractTransactionalSpringContextTests; -import org.springmodules.workflow.jbpm31.JbpmTemplate; - -/** - * JBPMEngine Unit Tests. - * - * @author Nick Smith - */ -public class JBPMEngineUnitTest extends AbstractTransactionalSpringContextTests -{ - private static final String USER_NAME = "admin"; - - private static final String TEST_JBPM_ENGINE = "test_jbpm_engine"; - - private static final NodeRef companyHome = new NodeRef("for://test/home"); - - private JBPMEngine engine = new JBPMEngine(); - - private WorkflowDefinition workflowDef; - - public void testDeployWorkflow() throws Exception - { - ClassPathResource processDef = new ClassPathResource("jbpmresources/test_processdefinition.xml"); - List workflowDefs = engine.getDefinitions(); - assertFalse(engine.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); - assertNotNull(workflowDefs); - - int workflowDefCnt = workflowDefs.size(); - - deployTestDefinition(); - assertTrue(engine.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); - workflowDefs = engine.getDefinitions(); - assertNotNull(workflowDefs); - assertEquals(workflowDefCnt+1, workflowDefs.size()); - - assertNotNull(workflowDef); - assertEquals(TEST_JBPM_ENGINE + "$test", workflowDef.name); - assertEquals("1", workflowDef.version); - - deployTestDefinition(); - assertTrue(engine.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); - assertEquals("2", workflowDef.version); - } - - public void testGetWorkflowInstance() throws Exception - { - deployTestDefinition(); - WorkflowPath path = engine.startWorkflow(workflowDef.getId(), null); - checkPath(path); - WorkflowInstance instance = engine.getWorkflowById(path.instance.id); - assertNotNull(instance); - assertEquals(path.instance.id, instance.id); - } - - public void testStartWorkflowWithoutPackage() throws Exception - { - try - { - engine.startWorkflow("norfolknchance", null); - fail("Failed to catch invalid definition id"); - } - catch (WorkflowException e) - { // Do nothing here! - } - - deployTestDefinition(); - WorkflowPath path = engine.startWorkflow(workflowDef.getId(), null); - assertNotNull(path); - assertTrue(path.id.endsWith("-@")); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.getId(), path.instance.definition.id); - } - - public void testStartWorkflowParameters() throws Exception - { - Map params = new HashMap(); - - // protected - shouldn't be written - params.put(WorkflowModel.PROP_TASK_ID, 3); - - // task instance field - params.put(WorkflowModel.PROP_DUE_DATE, new Date()); - - params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field - params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable - - // context variable outside of task definition - params.put(QName.createQName("", "Message"), "Hello World"); - - // context variable outside of task definition - params.put(QName.createQName("", "Array"), new String[] { "one", "two" }); - - // context variable outside of task definition - params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); - - params.put(ContentModel.PROP_OWNER, "Owner"); // task assignment - - deployTestDefinition(); - WorkflowPath path = engine.startWorkflow(workflowDef.id, params); - checkPath(path); - List tasks1 = engine.getTasksForWorkflowPath(path.id); - assertNotNull(tasks1); - assertEquals(1, tasks1.size()); - - WorkflowTask task = tasks1.get(0); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); - assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER)); - - // NodeRef initiator = path.instance.initiator; - // String initiatorUsername = (String) - // nodeService.getProperty(initiator, - // ContentModel.PROP_USERNAME); - // assertEquals(AuthenticationUtil.getAdminUserName(), - // initiatorUsername); - } - - public void testUpdateTask() throws Exception - { - Map params = new HashMap(); - // protected - shouldn't be written - params.put(WorkflowModel.PROP_TASK_ID, 3); - - // task instance field - params.put(WorkflowModel.PROP_DUE_DATE, new Date()); - - params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field - params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable - - // context variable outside of task definition - params.put(QName.createQName("", "Message"), "Hello World"); - - // context variable outside of task definition - params.put(QName.createQName("", "Array"), new String[] { "one", "two" }); - - // context task assignment - params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); - - params.put(ContentModel.PROP_OWNER, USER_NAME); - - deployTestDefinition(); - WorkflowPath path = engine.startWorkflow(workflowDef.id, params); - checkPath(path); - List tasks1 = engine.getTasksForWorkflowPath(path.id); - assertNotNull(tasks1); - assertEquals(1, tasks1.size()); - - WorkflowTask task = tasks1.get(0); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY)); - assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE)); - assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER)); - - // update with null parameters - try - { - WorkflowTask taskU1 = engine.updateTask(task.id, null, null, null); - assertNotNull(taskU1); - } - catch (Throwable e) - { - fail("Task update failed with null parameters"); - } - - // update property value - Map updateProperties2 = new HashMap(); - updateProperties2.put(WorkflowModel.PROP_PERCENT_COMPLETE, 100); - WorkflowTask taskU2 = engine.updateTask(task.id, updateProperties2, null, null); - assertEquals(100, taskU2.properties.get(WorkflowModel.PROP_PERCENT_COMPLETE)); - - // add to assocation - QName assocName = QName.createQName("", "TestAssoc"); - List toAdd = new ArrayList(); - toAdd.add(new NodeRef("workspace://1/1001")); - toAdd.add(new NodeRef("workspace://1/1002")); - toAdd.add(new NodeRef("workspace://1/1003")); - Map> addAssocs = new HashMap>(); - addAssocs.put(assocName, toAdd); - WorkflowTask taskU3 = engine.updateTask(task.id, null, addAssocs, null); - assertNotNull(taskU3.properties.get(assocName)); - Object assoc = taskU3.properties.get(assocName); - assertNotNull(assoc); - assertEquals(3, ((List) assoc).size()); - - // add to assocation again - List toAddAgain = new ArrayList(); - toAddAgain.add(new NodeRef("workspace://1/1004")); - toAddAgain.add(new NodeRef("workspace://1/1005")); - Map> addAssocsAgain = new HashMap>(); - addAssocsAgain.put(assocName, toAddAgain); - WorkflowTask taskU4 = engine.updateTask(task.id, null, addAssocsAgain, null); - assertNotNull(taskU4.properties.get(assocName)); - assoc = taskU4.properties.get(assocName); - assertEquals(5, ((List) assoc).size()); - - // remove assocation - List toRemove = new ArrayList(); - toRemove.add(new NodeRef("workspace://1/1002")); - toRemove.add(new NodeRef("workspace://1/1003")); - Map> removeAssocs = new HashMap>(); - removeAssocs.put(assocName, toRemove); - WorkflowTask taskU5 = engine.updateTask(task.id, null, null, removeAssocs); - assertNotNull(taskU5.properties.get(assocName)); - assoc = taskU5.properties.get(assocName); - assertEquals(3, ((List) assoc).size()); - } - - public void testGetWorkflowInstances() throws Exception - { - deployTestDefinition(); - WorkflowPath path1 = engine.startWorkflow(workflowDef.id, null); - WorkflowPath path2 = engine.startWorkflow(workflowDef.id, null); - List instances = engine.getActiveWorkflows(workflowDef.id); - assertNotNull(instances); - assertEquals(2, instances.size()); - - HashSet ids = new HashSet(2); - ids.add(path1.instance.id); - ids.add(path2.instance.id); - - for (WorkflowInstance instance : instances) - { - assertEquals(workflowDef.id, instance.definition.id); - assertTrue(ids.contains(instance.id)); - } - } - - public void testGetPositions() throws Exception - { - deployTestDefinition(); - engine.startWorkflow(workflowDef.id, null); - List instances = engine.getActiveWorkflows(workflowDef.id); - assertNotNull(instances); - assertEquals(1, instances.size()); - List paths = engine.getWorkflowPaths(instances.get(0).id); - assertNotNull(paths); - assertEquals(1, paths.size()); - assertEquals(instances.get(0).id, paths.get(0).instance.id); - assertTrue(paths.get(0).id.endsWith("-@")); - } - - private void checkPath(WorkflowPath path) - { - assertNotNull(path); - assertTrue(path.id.endsWith("-@")); - assertNotNull(path.node); - assertNotNull(path.instance); - assertEquals(workflowDef.id, path.instance.definition.id); - } - - /* - * @seeorg.springframework.test.AbstractTransactionalSpringContextTests# - * onSetUpBeforeTransaction() - */ - @Override - protected void onSetUpBeforeTransaction() throws Exception - { - super.onSetUpBeforeTransaction(); - // Mock up various services. - NodeService nodeService = mock(NodeService.class); - TenantService tenantService = makeTenantService(); - NamespaceService namespaceService = makeNamespaceService(); - DictionaryService dictionaryService = makeDictionaryService(); - - // Add services to ServiceRegistry - ServiceRegistry serviceRegistry = mock(ServiceRegistry.class); - when(serviceRegistry.getNodeService()).thenReturn(nodeService); - when(serviceRegistry.getNamespaceService()).thenReturn(namespaceService); - when(serviceRegistry.getDictionaryService()).thenReturn(dictionaryService); - BPMEngineRegistry engineRegistry = (BPMEngineRegistry) getApplicationContext().getBean( - "test_bpm_engineRegistry"); - - ConfigurableApplicationContext ctx = getApplicationContext(); - JbpmTemplate jbpmTemplate = (JbpmTemplate) ctx.getBean("test_jbpm_template"); - // Set up the JBPMEngine. - engine.setJBPMTemplate(jbpmTemplate); - engine.setTenantService(tenantService); - engine.setNodeService(nodeService); - engine.setServiceRegistry(serviceRegistry); - engine.setNamespaceService(namespaceService); - engine.setMessageService(mock(MessageService.class)); - engine.setDictionaryService(dictionaryService); - engine.setEngineId(TEST_JBPM_ENGINE); - engine.setBPMEngineRegistry(engineRegistry); - - // Need to register JBPMEngine with bean factory so WorflowTaskInstance - // can load it. - ConfigurableApplicationContext appContext = getApplicationContext(); - if (!appContext.containsBean(TEST_JBPM_ENGINE)) - appContext.getBeanFactory().registerSingleton(TEST_JBPM_ENGINE, engine); - } - - @SuppressWarnings("unchecked") - private DictionaryService makeDictionaryService() - { - DictionaryService service = mock(DictionaryService.class); - - // DictionaryService.getType(QName) always returns a mock - // TypeDefinition. - TypeDefinition typeDef = mock(TypeDefinition.class); - when(service.getType((QName) any())).thenReturn(typeDef); - - // DictionaryService.getAnonymousType(QName, Collection) - // always returns a mock TypeDefinition - when(service.getAnonymousType((QName) any(),// - (Collection) any()))// - .thenReturn(typeDef); - return service; - } - - // deploy test process definition - private void deployTestDefinition() throws IOException - { - ClassPathResource processDef = new ClassPathResource("jbpmresources/test_processdefinition.xml"); - WorkflowDeployment deployment = engine.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); - workflowDef = deployment.definition; - assertNotNull(workflowDef); - assertEquals(TEST_JBPM_ENGINE + "$test", workflowDef.name); - } - - private NamespaceService makeNamespaceService() - { - NamespaceServiceMemoryImpl namespace = new NamespaceServiceMemoryImpl(); - namespace.registerNamespace(NamespaceService.DEFAULT_PREFIX, NamespaceService.DEFAULT_URI); - - namespace.registerNamespace("wf", "http://www.alfresco.org/model/bpm/1.0"); - namespace.registerNamespace("cm", "http://www.alfresco.org/model/content/1.0"); - namespace.registerNamespace("wcmwf", "http://www.alfresco.org/model/wcmworkflow/1.0"); - namespace.registerNamespace("imwf", "http://www.alfresco.org/model/workflow/invite/moderated/1.0"); - namespace.registerNamespace("inwf", "http://www.alfresco.org/model/workflow/invite/nominated/1.0"); - - return namespace; - } - - private TenantService makeTenantService() - { - TenantService tenantService = mock(TenantService.class); - - // Tenant Service.isEnabled() returns true. - when(tenantService.isEnabled()).thenReturn(true); - - // TenantService.getRootNode always returns companyHome. - when(tenantService.getRootNode((NodeService) any(),// - (SearchService) any(),// - (NamespaceService) any(),// - anyString(),// - (NodeRef) any()))// - .thenReturn(companyHome); - // Tenant Service.getName(String) will return the input param. - when(tenantService.getName(anyString())).thenAnswer(new Answer() - { - public String answer(InvocationOnMock invocation) throws Throwable - { - return (String) invocation.getArguments()[0]; - } - }); - when(tenantService.getBaseName(anyString())).thenAnswer(new Answer() - { - public String answer(InvocationOnMock invocation) throws Throwable - { - return (String) invocation.getArguments()[0]; - } - }); - return tenantService; - } - - /* - * @see - * org.springframework.test.AbstractSingleSpringContextTests#getConfigLocations - * () - */ - @Override - protected String[] getConfigLocations() - { - String[] locations = new String[] { - "classpath:jbpm-test/test-workflow-context.xml", }; - return locations; - } -} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMNode.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMNode.java index 350e90962c..e6794d3f64 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMNode.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMNode.java @@ -21,6 +21,7 @@ package org.alfresco.repo.workflow.jbpm; import java.io.Serializable; import java.util.Date; +import org.alfresco.repo.jscript.ScriptNode; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -36,7 +37,7 @@ import org.mozilla.javascript.Scriptable; * * @author davidc */ -public class JBPMNode extends org.alfresco.repo.jscript.ScriptNode +public class JBPMNode extends ScriptNode { private static final long serialVersionUID = -826970280203254365L; @@ -51,8 +52,8 @@ public class JBPMNode extends org.alfresco.repo.jscript.ScriptNode super(nodeRef, services, null); } - /* (non-Javadoc) - * @see org.alfresco.repo.jscript.Node#createValueConverter() + /** + * {@inheritDoc} */ @Override protected NodeValueConverter createValueConverter() @@ -63,7 +64,7 @@ public class JBPMNode extends org.alfresco.repo.jscript.ScriptNode /** * Value converter for beanshell. */ - private class JBPMNodeConverter extends org.alfresco.repo.jscript.ScriptNode.NodeValueConverter + private class JBPMNodeConverter extends NodeValueConverter { @Override public Serializable convertValueForRepo(Serializable value) @@ -79,11 +80,11 @@ public class JBPMNode extends org.alfresco.repo.jscript.ScriptNode } @Override - public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, Serializable value) + public Serializable convertValueForScript(ServiceRegistry serviceRegistry, Scriptable theScope, QName qname, Serializable value) { if (value instanceof NodeRef) { - return new JBPMNode(((NodeRef)value), services); + return new JBPMNode(((NodeRef)value), serviceRegistry); } else if (value instanceof Date) { @@ -91,7 +92,7 @@ public class JBPMNode extends org.alfresco.repo.jscript.ScriptNode } else { - return super.convertValueForScript(services, scope, qname, value); + return super.convertValueForScript(serviceRegistry, theScope, qname, value); } } } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java new file mode 100644 index 0000000000..03c43f1e90 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.workflow.jbpm; + +import org.alfresco.repo.workflow.AbstractWorkflowServiceIntegrationTest; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + + + +/** + * Workflow Service Implementation Tests + * + * @author davidc + */ +public class JbpmWorkflowServiceIntegrationTest extends AbstractWorkflowServiceIntegrationTest +{ + + @Override + protected String getEngine() + { + return "jbpm"; + } + + @Override + protected String getTestDefinitionPath() + { + return "jbpmresources/test_simple_processdefinition.xml"; + } + + @Override + protected String getAdhocDefinitionPath() + { + return "alfresco/workflow/adhoc_processdefinition.xml"; + } + + @Override + protected String getPooledReviewDefinitionPath() + { + return "alfresco/workflow/review_pooled_processdefinition.xml"; + } + + @Override + protected String getTestTimerDefinitionPath() + { + return "jbpmresources/test_timer.xml"; + } + + @Override + protected QName getAdhocProcessName() { + return QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "adhoc"); + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java index d2f0787108..f89a2b19cb 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java @@ -29,11 +29,20 @@ import java.io.Serializable; public class WorkflowDefinition implements Serializable { private static final long serialVersionUID = -4320345925926816927L; -//XXarielb these should most likely all be private + + @Deprecated public final String id; + + @Deprecated public final String name; + + @Deprecated public final String version; + + @Deprecated public final String title; + + @Deprecated public final String description; transient private final WorkflowTaskDefinition startTaskDefinition; diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowDeployment.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowDeployment.java index 9abd4a0220..0a61095862 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowDeployment.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowDeployment.java @@ -27,11 +27,24 @@ package org.alfresco.service.cmr.workflow; public class WorkflowDeployment { /** Workflow Definition */ + @Deprecated public WorkflowDefinition definition; /** Workflow Status */ + @Deprecated public String[] problems; + + public WorkflowDeployment() + { + // Default Constructor. + } + public WorkflowDeployment(WorkflowDefinition definition, String... problems) + { + this.definition = definition; + this.problems = problems; + } + /** * @return the definition */ diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java index 3982680451..2699e82348 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java @@ -36,38 +36,69 @@ public class WorkflowInstance implements Serializable private static final long serialVersionUID = 4221926809419223452L; /** Workflow Instance unique id */ + @Deprecated public String id; /** Workflow Instance description */ + @Deprecated public String description; /** Is this Workflow instance still "in-flight" or has it completed? */ + @Deprecated public boolean active; /** Initiator (cm:person) - null if System initiated */ + @Deprecated public NodeRef initiator; /** Workflow priority */ public Integer priority; /** Workflow Start Date */ + @Deprecated public Date startDate; /** Workflow Due Date */ public Date dueDate; /** Workflow End Date */ + @Deprecated public Date endDate; /** Workflow Package */ + @Deprecated public NodeRef workflowPackage; /** Workflow Context */ + @Deprecated public NodeRef context; /** Workflow Definition */ + @Deprecated public WorkflowDefinition definition; + public WorkflowInstance( + String id, + WorkflowDefinition definition, + String description, + NodeRef initiator, + NodeRef workflowPackage, + NodeRef context, + boolean active, + Date startDate, + Date endDate) + { + this.id = id; + this.definition = definition; + this.description = description; + this.initiator = initiator; + this.workflowPackage = workflowPackage; + this.context = context; + this.active = active; + this.startDate = startDate; + this.endDate = endDate; + } + /** * @return the serialversionuid */ diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java index 45512a6071..20b3183fe0 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java @@ -29,23 +29,42 @@ package org.alfresco.service.cmr.workflow; public class WorkflowNode { /** Workflow Node Name */ + @Deprecated public String name; /** Workflow Node Title (Localised) */ + @Deprecated public String title; /** Workflow Node Description (Localised) */ + @Deprecated public String description; /** Type of the Workflow Node (typically this is BPM engine specific - informational only */ + @Deprecated public String type; /** Does this Workflow Node represent human interaction? */ + @Deprecated public boolean isTaskNode; /** The transitions leaving this node (or null, if none) */ + @Deprecated public WorkflowTransition[] transitions; + public WorkflowNode(String name, + String title, String description, + String type, boolean isTaskNode, + WorkflowTransition... transitions) + { + this.name = name; + this.title = title; + this.description = description; + this.type = type; + this.isTaskNode = isTaskNode; + this.transitions = transitions; + } + /** * @return the name */ diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java index 85e18803ce..a25f6151e4 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java @@ -32,16 +32,28 @@ package org.alfresco.service.cmr.workflow; public class WorkflowPath { /** Unique id of Workflow Path */ + @Deprecated public String id; /** Workflow Instance this path is part of */ + @Deprecated public WorkflowInstance instance; /** The Workflow Node the path is at */ + @Deprecated public WorkflowNode node; /** Is the path still active? */ + @Deprecated public boolean active; + + public WorkflowPath(String id, WorkflowInstance instance, WorkflowNode node, boolean active) + { + this.id = id; + this.instance = instance; + this.node = node; + this.active = active; + } /** * @return the id @@ -75,12 +87,12 @@ public class WorkflowPath return active; } - /* (non-Javadoc) - * @see java.lang.Object#toString() + /** + * {@inheritDoc} */ @Override public String toString() { - return "WorkflowPath[id=" + id + ",instance=" + instance.toString() + ",active=" + active + ",node=" + node.toString()+ "]"; + return "WorkflowPath[id=" + id + ",instance=" + instance.toString() + ",active=" + active + ",node=" + ((node != null) ? node.toString() : "null") + "]"; } } diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index b65207aea5..58bc445a30 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -194,7 +194,7 @@ public interface WorkflowService public WorkflowPath startWorkflowFromTemplate(NodeRef templateDefinition); /** - * Gets all "in-flight" active workflow instances of the specified Workflow Definition + * Gets all active workflow instances of the specified Workflow Definition * * @param workflowDefinitionId the workflow definition id * @return the list of "in-flight" workflow instances @@ -203,7 +203,7 @@ public interface WorkflowService public List getActiveWorkflows(String workflowDefinitionId); /** - * Gets all "in-flight" completed workflow instances of the specified Workflow Definition + * Gets all completed workflow instances of the specified Workflow Definition * * @param workflowDefinitionId the workflow definition id * @return the list of "in-flight" workflow instances @@ -213,7 +213,7 @@ public interface WorkflowService public List getCompletedWorkflows(String workflowDefinitionId); /** - * Gets all "in-flight" workflow instances (both active and completed) of the specified Workflow Definition + * Gets all workflow instances (both active and completed) of the specified Workflow Definition * * @param workflowDefinitionId the workflow definition id * @return the list of "in-flight" workflow instances @@ -298,6 +298,15 @@ public interface WorkflowService */ @Auditable(parameters = {"pathId"}) public List getTasksForWorkflowPath(String pathId); + + /** + * Gets the start task instance for the given workflow instance. + * + * @param workflowInstanceId + * @return + */ + @Auditable(parameters = {"pathId"}) + public WorkflowTask getStartTask(String workflowInstanceId); // diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java index 376d1e2f96..e6a75cb679 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java @@ -34,29 +34,54 @@ import org.alfresco.service.namespace.QName; public class WorkflowTask { /** Unique id of Task */ + @Deprecated public String id; /** Task Name */ + @Deprecated public String name; /** Task Title (Localised) */ + @Deprecated public String title; /** Task Description (Localised) */ + @Deprecated public String description; /** Task State */ + @Deprecated public WorkflowTaskState state; /** Workflow path this Task is associated with */ + @Deprecated public WorkflowPath path; /** Task Definition */ + @Deprecated public WorkflowTaskDefinition definition; /** Task Properties as described by Task Definition */ + @Deprecated public Map properties; + + public WorkflowTask(String id, + WorkflowTaskDefinition definition, + String name, String title, String description, + WorkflowTaskState state, WorkflowPath path, + Map properties) + { + this.id = id; + this.definition = definition; + this.name = name; + this.title = title; + this.description = description; + this.state = state; + this.path = path; + this.properties = properties; + } + /** * @return the id */ diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java index 19c1a718be..1f15df29b9 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java @@ -32,14 +32,24 @@ import org.alfresco.service.cmr.dictionary.TypeDefinition; public class WorkflowTaskDefinition { /** Unique id of Workflow Task Definition */ + @Deprecated public String id; /** Workflow Node this task created from */ + @Deprecated public WorkflowNode node; /** Task Metadata */ + @Deprecated public TypeDefinition metadata; + public WorkflowTaskDefinition(String id, WorkflowNode node, TypeDefinition metadata) + { + this.id = id; + this.node = node; + this.metadata = metadata; + } + /** * @return the id */ @@ -64,8 +74,9 @@ public class WorkflowTaskDefinition return node; } - /* (non-Javadoc) - * @see java.lang.Object#toString() + /** + * + * {@inheritDoc} */ @Override public String toString() diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTimer.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTimer.java index d68e43a68b..0b386b71df 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowTimer.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTimer.java @@ -23,24 +23,43 @@ import java.util.Date; public class WorkflowTimer { /** Timer Id */ + @Deprecated public String id; /** Transition Name */ + @Deprecated public String name; /** Associated Workflow Path */ + @Deprecated public WorkflowPath path; /** Associated Workflow Task (if any) */ + @Deprecated public WorkflowTask task; /** Due Date */ + @Deprecated public Date dueDate; /** Error */ + @Deprecated public String error; - /** + + + public WorkflowTimer(String id, String name, WorkflowPath path, + WorkflowTask task, Date dueDate, String error) { + super(); + this.id = id; + this.name = name; + this.path = path; + this.task = task; + this.dueDate = dueDate; + this.error = error; + } + + /** * @return the id */ public String getId() diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTransition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTransition.java index b2e8e707a7..39de399644 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowTransition.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTransition.java @@ -27,18 +27,29 @@ package org.alfresco.service.cmr.workflow; public class WorkflowTransition { /** Transition Id */ + @Deprecated public String id; /** Transition Title (Localised) */ + @Deprecated public String title; /** Transition Description (Localised) */ + @Deprecated public String description; /** Is this the default transition */ + @Deprecated public boolean isDefault; - - + + public WorkflowTransition(String id, String title, String description, boolean isDefault) + { + this.id = id; + this.title = title; + this.description = description; + this.isDefault = isDefault; + } + /** * @return the id */ @@ -71,8 +82,8 @@ public class WorkflowTransition return isDefault; } - /* (non-Javadoc) - * @see java.lang.Object#toString() + /** + * {@inheritDoc} */ @Override public String toString() diff --git a/source/java/org/alfresco/util/BaseAlfrescoSpringTest.java b/source/java/org/alfresco/util/BaseAlfrescoSpringTest.java index 4a0825499d..f345c26c94 100644 --- a/source/java/org/alfresco/util/BaseAlfrescoSpringTest.java +++ b/source/java/org/alfresco/util/BaseAlfrescoSpringTest.java @@ -18,13 +18,21 @@ */ package org.alfresco.util; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; /** @@ -62,6 +70,7 @@ public abstract class BaseAlfrescoSpringTest extends BaseSpringTest /** * On setup in transaction override */ + @SuppressWarnings("deprecation") @Override protected void onSetUpInTransaction() throws Exception { @@ -82,11 +91,9 @@ public abstract class BaseAlfrescoSpringTest extends BaseSpringTest // Create the store and get the root node this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.rootNodeRef = this.nodeService.getRootNode(this.storeRef); - - - } + @SuppressWarnings("deprecation") @Override protected void onTearDownInTransaction() throws Exception { @@ -94,4 +101,18 @@ public abstract class BaseAlfrescoSpringTest extends BaseSpringTest super.onTearDownInTransaction(); } + protected NodeRef createNode(NodeRef parentNode, String name, QName type) + { + Map props = new HashMap(); + String fullName = name + System.currentTimeMillis(); + props.put(ContentModel.PROP_NAME, fullName); + QName childName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, fullName); + ChildAssociationRef childAssoc = nodeService.createNode(parentNode, + ContentModel.ASSOC_CONTAINS, + childName, + type, + props); + return childAssoc.getChildRef(); + } + } diff --git a/source/test-resources/activiti/test-activiti-component-context.xml b/source/test-resources/activiti/test-activiti-component-context.xml new file mode 100644 index 0000000000..c437e06041 --- /dev/null +++ b/source/test-resources/activiti/test-activiti-component-context.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + admin + + + guest + + + \ No newline at end of file diff --git a/source/test-resources/activiti/testAdhoc.bpmn20.xml b/source/test-resources/activiti/testAdhoc.bpmn20.xml new file mode 100644 index 0000000000..278444077a --- /dev/null +++ b/source/test-resources/activiti/testAdhoc.bpmn20.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/activiti/testJob.bpmn20.xml b/source/test-resources/activiti/testJob.bpmn20.xml new file mode 100644 index 0000000000..081645f776 --- /dev/null +++ b/source/test-resources/activiti/testJob.bpmn20.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + PT12H + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/activiti/testReview.bpmn20.xml b/source/test-resources/activiti/testReview.bpmn20.xml new file mode 100644 index 0000000000..f590b5f034 --- /dev/null +++ b/source/test-resources/activiti/testReview.bpmn20.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + execution.setVariable('wf_reviewOutcome', task.getVariable('wf_reviewOutcome')); + + + + + + + + + + + + ${wf_reviewOutcome == 'Approve'} + + + + + + + The document was reviewed and approved. + + + + + + The document was reviewed and rejected. + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/activiti/testSignalling.bpmn20.xml b/source/test-resources/activiti/testSignalling.bpmn20.xml new file mode 100644 index 0000000000..5568457f67 --- /dev/null +++ b/source/test-resources/activiti/testSignalling.bpmn20.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/activiti/testTimer.bpmn20.xml b/source/test-resources/activiti/testTimer.bpmn20.xml new file mode 100644 index 0000000000..d15f9a1844 --- /dev/null +++ b/source/test-resources/activiti/testTimer.bpmn20.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + PT12H + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/activiti/testTimerTransaction.bpmn20.xml b/source/test-resources/activiti/testTimerTransaction.bpmn20.xml new file mode 100644 index 0000000000..00e820d04e --- /dev/null +++ b/source/test-resources/activiti/testTimerTransaction.bpmn20.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + PT1S + + + + + + + + + + if(error) { + // Manipulate user home, should be rolled back + userhome.name = 'User home changed'; + userhome.save(); + + throw 'Activiti engine rocks!'; + } else { + execution.setVariable('timerExecutedAs', person.properties.userName); + } + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/activiti/testTransaction.bpmn20.xml b/source/test-resources/activiti/testTransaction.bpmn20.xml new file mode 100644 index 0000000000..f7e1d72a30 --- /dev/null +++ b/source/test-resources/activiti/testTransaction.bpmn20.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/jbpmresources/test_simple_processdefinition.xml b/source/test-resources/jbpmresources/test_simple_processdefinition.xml new file mode 100644 index 0000000000..39771948c5 --- /dev/null +++ b/source/test-resources/jbpmresources/test_simple_processdefinition.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/jbpmresources/test_timer.xml b/source/test-resources/jbpmresources/test_timer.xml new file mode 100644 index 0000000000..1f4c51cea5 --- /dev/null +++ b/source/test-resources/jbpmresources/test_timer.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + #{bpm_assignee} + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/test-database-context.xml b/source/test-resources/test-database-context.xml new file mode 100644 index 0000000000..1f229c8ca9 --- /dev/null +++ b/source/test-resources/test-database-context.xml @@ -0,0 +1,80 @@ + + + + + + + + + ${db.driver} + + + ${db.url} + + + ${db.username} + + + ${db.password} + + + ${db.pool.initial} + + + ${db.pool.max} + + + ${db.pool.min} + + + ${db.pool.idle} + + + false + + + ${db.txn.isolation} + + + ${db.pool.wait.max} + + + ${db.pool.validate.query} + + + ${db.pool.evict.interval} + + + ${db.pool.evict.idle.min} + + + ${db.pool.validate.borrow} + + + ${db.pool.validate.return} + + + ${db.pool.evict.validate} + + + ${db.pool.abandoned.detect} + + + ${db.pool.abandoned.time} + + + ${db.pool.statements.enable} + + + ${db.pool.statements.max} + + + + + + + + + \ No newline at end of file