From 47833426ca2ef1654aeed15f6dc7c6e70039fc03 Mon Sep 17 00:00:00 2001 From: David Caruana Date: Tue, 8 Aug 2006 17:35:50 +0000 Subject: [PATCH] Workflow Checkpoint: - BPM Model (xml & QName definitions) - Getting / Setting / Updating Task Properties git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3470 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/model/bpmModel.xml | 280 +++++++++++ config/alfresco/workflow-context.xml | 12 + .../repo/dictionary/M2ClassDefinition.java | 4 +- .../alfresco/repo/workflow/TaskComponent.java | 21 +- .../alfresco/repo/workflow/WorkflowModel.java | 45 ++ .../repo/workflow/jbpm/JBPMEngine.java | 451 ++++++++++++++++-- .../repo/workflow/jbpm/JBPMEngineTest.java | 122 +++++ .../service/cmr/workflow/WorkflowTask.java | 9 +- .../service/namespace/NamespaceService.java | 6 + 9 files changed, 891 insertions(+), 59 deletions(-) create mode 100644 config/alfresco/model/bpmModel.xml create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowModel.java diff --git a/config/alfresco/model/bpmModel.xml b/config/alfresco/model/bpmModel.xml new file mode 100644 index 0000000000..2229575aaa --- /dev/null +++ b/config/alfresco/model/bpmModel.xml @@ -0,0 +1,280 @@ + + + + + Business Process Model + Alfresco + 1.0 + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + + + + + + + + + Not Yet Started + In Progress + On Hold + Cancelled + Completed + + + + + + 0 + 100 + + + + + + + + + + + + Task + Task + cm:content + + + + + + + + + + + + + Task Identifier + d:long + true + + + + + + + Start Date + d:date + true + + + End Date + d:date + true + + + Due Date + d:date + + + + + + + Status + d:text + true + + + + + + Priority + d:int + + + + + + Percentage Complete + d:int + + + + + + + + + + + + + + + + + Pooled Users + + false + false + + + cm:person + false + true + + + + + + + cm:ownable + + + + + + + + + + Ad-hoc Task + Task assigned by User + bpm:task + + + cm:attachable + + + + + + + + + + Workflow Task + Task assigned via Workflow + bpm:task + + + + + + + + + Task Context + d:noderef + + + + + + + + Workflow Package + + false + false + + + bpm:workflowPackage + true + false + + + + + + + + + + + + + + Task Space + cm:folder + + + + + + + + + + + + + + + Workflow Package + + + + + + d:noderef + true + + + + + + + + + + + + + + + + + + + + + + + + false + false + + + bpm:taskspace + true + false + + false + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index 010c82854e..4ce9da7ac3 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -3,6 +3,18 @@ + + + + + + + + alfresco/model/bpmModel.xml + + + + diff --git a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java index 0fd69bcda6..538c72cfbe 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java @@ -162,8 +162,8 @@ import org.alfresco.service.namespace.QName; public String toString() { StringBuilder sb = new StringBuilder(120); - sb.append("ClassDef ") - .append("[ name=").append(name) + sb.append("ClassDef") + .append("[name=").append(name) .append("]"); return sb.toString(); } diff --git a/source/java/org/alfresco/repo/workflow/TaskComponent.java b/source/java/org/alfresco/repo/workflow/TaskComponent.java index 8fabe54b78..8d5aac2b57 100644 --- a/source/java/org/alfresco/repo/workflow/TaskComponent.java +++ b/source/java/org/alfresco/repo/workflow/TaskComponent.java @@ -70,6 +70,25 @@ public interface TaskComponent */ public WorkflowTask updateTask(String taskId, Map properties, Map> add, Map> remove); + /** + * Start the specified Task + * + * Note: this is an optional task operation. It may be used to track + * when work started on a task as well as resume a suspended task. + * + * @param taskId the task to start + * @return the updated task + */ + public WorkflowTask startTask(String taskId); + + /** + * Suspend the specified Task + * + * @param taskId + * @return the update task + */ + public WorkflowTask suspendTask(String taskId); + /** * End the Task (i.e. complete the task) * @@ -78,6 +97,6 @@ public interface TaskComponent * @return the updated task */ public WorkflowTask endTask(String taskId, String transition); - + } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowModel.java b/source/java/org/alfresco/repo/workflow/WorkflowModel.java new file mode 100644 index 0000000000..1b3b5de626 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowModel.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + + +/** + * Workflow Model Constants + */ +public interface WorkflowModel +{ + + // task constants + static final QName TYPE_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "task"); + static final QName PROP_TASK_ID = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "taskId"); + static final QName PROP_START_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "startDate"); + static final QName PROP_DUE_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "dueDate"); + static final QName PROP_COMPLETION_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "completionDate"); + static final QName PROP_PRIORITY = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "priority"); + static final QName PROP_STATUS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "status"); + static final QName PROP_PERCENT_COMPLETE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "percentComplete"); + static final QName ASSOC_POOLED_ACTORS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "pooledActors"); + + // workflow task contstants + static final QName TYPE_WORKFLOW_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowTask"); + static final QName PROP_CONTEXT = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "context"); + static final QName ASSOC_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package"); + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 8f58734b9c..8ef77d2a9d 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -19,17 +19,28 @@ package org.alfresco.repo.workflow.jbpm; import java.io.InputStream; 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 java.util.Set; import java.util.Map.Entry; +import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.workflow.BPMEngine; import org.alfresco.repo.workflow.TaskComponent; import org.alfresco.repo.workflow.WorkflowComponent; import org.alfresco.repo.workflow.WorkflowDefinitionComponent; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; 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.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowInstance; @@ -43,7 +54,6 @@ import org.alfresco.service.namespace.QName; import org.hibernate.proxy.HibernateProxy; import org.jbpm.JbpmContext; import org.jbpm.JbpmException; -import org.jbpm.context.exe.ContextInstance; import org.jbpm.db.GraphSession; import org.jbpm.db.TaskMgmtSession; import org.jbpm.graph.def.Node; @@ -72,6 +82,8 @@ public class JBPMEngine extends BPMEngine // Implementation dependencies protected DictionaryService dictionaryService; protected NamespaceService namespaceService; + protected NodeService nodeService; + protected PersonService personService; private JbpmTemplate jbpmTemplate; /** @@ -104,6 +116,26 @@ public class JBPMEngine extends BPMEngine this.namespaceService = namespaceService; } + /** + * Sets the Node Service + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the Person Service + * + * @param personService + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + // // Workflow Definition... @@ -114,8 +146,8 @@ public class JBPMEngine extends BPMEngine */ public WorkflowDefinition deployDefinition(InputStream workflowDefinition) { - // TODO Auto-generated method stub - return null; + // TODO + throw new UnsupportedOperationException(); } /* (non-Javadoc) @@ -123,8 +155,8 @@ public class JBPMEngine extends BPMEngine */ public void undeployDefinition(String workflowDefinitionId) { - // TODO Auto-generated method stub - + // TODO + throw new UnsupportedOperationException(); } /* (non-Javadoc) @@ -465,10 +497,121 @@ public class JBPMEngine extends BPMEngine /* (non-Javadoc) * @see org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, java.util.Map, java.util.Map, java.util.Map) */ - public WorkflowTask updateTask(String taskId, Map properties, Map> add, Map> remove) + public WorkflowTask updateTask(final String taskId, final Map properties, final Map> add, final Map> remove) { - // TODO - throw new UnsupportedOperationException(); + try + { + return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve task + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + TaskInstance taskInstance = taskSession.loadTaskInstance(getJbpmId(taskId)); + + // create properties to set on task instance + Map newProperties = properties; + if (newProperties == null && (add != null || remove != null)) + { + newProperties = new HashMap(10); + } + + if (add != null || remove != null) + { + Map existingProperties = getTaskProperties(taskInstance); + + 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 : (List)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 : (List)toRemove.getValue()) + { + existingRemove.remove(nodeRef); + } + } + } + } + } + + // update the task + if (newProperties != null) + { + setTaskProperties(taskInstance, newProperties); + + // save + 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 + // this node) + return createWorkflowTask(taskInstance); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to update workflow task '" + taskId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#startTask(java.lang.String) + */ + public WorkflowTask startTask(String taskId) + { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#suspendTask(java.lang.String) + */ + public WorkflowTask suspendTask(String taskId) + { + // TODO Auto-generated method stub + return null; } /* (non-Javadoc) @@ -526,45 +669,51 @@ public class JBPMEngine extends BPMEngine return null; } - /** - * Sets Properties of Task - * - * @param instance task instance - * @param properties properties to set - */ - protected void setTaskProperties(TaskInstance instance, Map properties) - { - if (properties == null) - { - return; - } - - // TODO: Use Dictionary to drive mapping - - // TODO: Determine if NodeRefs and collection of NodeRefs need to be converted to String - - ContextInstance context = instance.getContextInstance(); - for (Entry entry : properties.entrySet()) - { - String name = null; - QName qname = entry.getKey(); - if (qname.getNamespaceURI().equals(NamespaceService.DEFAULT_URI)) - { - name = qname.getLocalName(); - } - else - { - name = qname.toPrefixString(namespaceService); - } - context.setVariable(name, entry.getValue()); - } - } - // // Helpers... // + /** + * 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(WorkflowModel.TYPE_WORKFLOW_TASK); + if (typeDef == null) + { + throw new WorkflowException("Failed to find type definition '" + WorkflowModel.TYPE_WORKFLOW_TASK + "'"); + } + } + 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()); + for (AspectDefinition aspect : aspects) + { + aspectNames.add(aspect.getName()); + } + return dictionaryService.getAnonymousType(typeDef.getName(), aspectNames); + } /** * Get JBoss JBPM Id from Engine Global Id @@ -612,6 +761,214 @@ public class JBPMEngine extends BPMEngine return token; } + /** + * Sets Properties of Task + * + * @param instance task instance + * @param properties properties to set + */ + @SuppressWarnings("unchecked") + protected Map getTaskProperties(TaskInstance instance) + { + // establish task definition + TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + Map taskAssocs = taskDef.getAssociations(); + + // map arbitrary task variables + Map properties = new HashMap(10); + Map vars = instance.getVariablesLocally(); + for (Entry entry : vars.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + + // perform data conversions + // NOTE: Only convert Authority name to NodeRef for now + QName qname = QName.createQName(key); + AssociationDefinition assocDef = taskAssocs.get(qname); + if (assocDef != null && assocDef.getTargetClass().equals(ContentModel.TYPE_PERSON)) + { + // TODO: Also support group authorities + if (!(value instanceof String[])) + { + throw new WorkflowException("Task variable '" + qname + "' value is invalid format"); + } + value = mapNameToAuthority((String[])value); + } + + // place task variable in map to return + properties.put(qname, (Serializable)value); + } + + // map jBPM task instance fields to properties + properties.put(WorkflowModel.PROP_TASK_ID, instance.getId()); + properties.put(WorkflowModel.PROP_START_DATE, instance.getStart()); + properties.put(WorkflowModel.PROP_DUE_DATE, instance.getDueDate()); + properties.put(WorkflowModel.PROP_COMPLETION_DATE, instance.getEnd()); + properties.put(WorkflowModel.PROP_PRIORITY, instance.getPriority()); + properties.put(ContentModel.PROP_OWNER, instance.getActorId()); + + // map jBPM task instance collections to associations + Set pooledActors = instance.getPooledActors(); + if (pooledActors != null) + { + String[] pooledActorIds = new String[pooledActors.size()]; + pooledActors.toArray(pooledActorIds); + List pooledActorNodeRefs = mapNameToAuthority(pooledActorIds); + properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable)pooledActorNodeRefs); + } + + return properties; + } + + /** + * Sets Properties of Task + * + * @param instance task instance + * @param properties properties to set + */ + protected void setTaskProperties(TaskInstance instance, Map properties) + { + if (properties == null) + { + return; + } + + // establish task definition + TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask())); + 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(); + + // determine if writing property + // NOTE: some properties map to fields on jBPM task instance whilst + // others are set in the general variable bag on the task + PropertyDefinition propDef = taskProperties.get(key); + if (propDef != null) + { + if (propDef.isProtected()) + { + // NOTE: only write non-protected properties + continue; + } + + // map property to specific jBPM task instance field + if (key.equals(WorkflowModel.PROP_DUE_DATE)) + { + if (!(value instanceof Date)) + { + throw new WorkflowException("Task due date '" + value + "' is invalid"); + } + instance.setDueDate((Date)value); + continue; + } + else if (key.equals(WorkflowModel.PROP_PRIORITY)) + { + if (!(value instanceof Integer)) + { + throw new WorkflowException("Task priority '" + value + "' is invalid"); + } + instance.setPriority((Integer)value); + continue; + } + else if (key.equals(ContentModel.PROP_OWNER)) + { + if (!(value instanceof String)) + { + throw new WorkflowException("Task owner '" + value + "' is invalid"); + } + instance.setActorId((String)value); + continue; + } + } + else + { + // determine if writing association + AssociationDefinition assocDef = taskAssocs.get(key); + if (assocDef != null) + { + // if association is to people, map them to authority names + // TODO: support group authorities + if (assocDef.getTargetClass().equals(ContentModel.TYPE_PERSON)) + { + value = mapAuthorityToName((List)value); + } + + // map association to specific jBPM task instance field + if (key.equals(WorkflowModel.ASSOC_POOLED_ACTORS)) + { + instance.setPooledActors((String[])value); + continue; + } + } + } + + // no specific mapping to jBPM task has been established, so place into + // the generic task variable bag + String name = null; + if (key.getNamespaceURI().equals(NamespaceService.DEFAULT_URI)) + { + name = key.getLocalName(); + } + else + { + name = key.toString(); + } + instance.setVariableLocally(name, value); + } + } + + /** + * Convert a list of Alfresco Authorities to a list of authority Names + * + * @param authorities the authorities to convert + * @return the authority names + */ + private String[] mapAuthorityToName(List authorities) + { + String[] names = null; + if (authorities != null) + { + names = new String[authorities.size()]; + int i = 0; + for (NodeRef person : authorities) + { + String name = (String)nodeService.getProperty(person, ContentModel.PROP_USERNAME); + names[i++] = name; + } + } + return names; + } + + /** + * Convert a list of authority Names to Alfresco Authorities + * + * @param names the authority names to convert + * @return the Alfresco authorities + */ + private List mapNameToAuthority(String[] names) + { + List authorities = null; + if (names != null) + { + authorities = new ArrayList(names.length); + for (String name : names) + { + // TODO: Should this be an exception? + if (personService.personExists(name)) + { + authorities.add(personService.getPerson(name)); + } + } + } + return authorities; + } + // // Workflow Data Object Creation... @@ -714,12 +1071,10 @@ public class JBPMEngine extends BPMEngine workflowTask.path = createWorkflowPath(task.getToken()); workflowTask.state = getWorkflowTaskState(task); workflowTask.definition = createWorkflowTaskDefinition(task.getTask()); - - // TODO: Properties and Associations - + workflowTask.properties = getTaskProperties(task); return workflowTask; } - + /** * Creates a Workflow Task Definition * @@ -728,11 +1083,9 @@ public class JBPMEngine extends BPMEngine */ protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task) { - // TODO: Extend jBPM task instance to include dictionary definition qname WorkflowTaskDefinition taskDef = new WorkflowTaskDefinition(); taskDef.id = task.getName(); - QName typeName = QName.createQName(taskDef.id, namespaceService); - taskDef.metadata = dictionaryService.getType(typeName); + taskDef.metadata = getTaskDefinition(task); return taskDef; } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java index 2145f4b3ea..36fcf875e2 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java @@ -17,14 +17,19 @@ package org.alfresco.repo.workflow.jbpm; 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.alfresco.model.ContentModel; import org.alfresco.repo.workflow.BPMEngineRegistry; import org.alfresco.repo.workflow.TaskComponent; import org.alfresco.repo.workflow.WorkflowComponent; import org.alfresco.repo.workflow.WorkflowDefinitionComponent; +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.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowInstance; @@ -55,6 +60,8 @@ public class JBPMEngineTest extends BaseSpringTest workflowDefinitionComponent = registry.getWorkflowDefinitionComponent("jbpm"); workflowComponent = registry.getWorkflowComponent("jbpm"); taskComponent = registry.getTaskComponent("jbpm"); + + } @@ -97,6 +104,121 @@ public class JBPMEngineTest extends BaseSpringTest } + public void testStartWorkflowParameters() + { + WorkflowDefinition workflowDef = getTestDefinition(); + + Map params = new HashMap(); + params.put(WorkflowModel.PROP_TASK_ID, 3); // protected - shouldn't be written + params.put(WorkflowModel.PROP_DUE_DATE, new Date()); // task instance field + params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field + params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable + 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")); // context variable outside of task definition + params.put(ContentModel.PROP_OWNER, "admin"); // task assignment + + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, 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); + 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)); + } + + + public void testUpdateTask() + { + WorkflowDefinition workflowDef = getTestDefinition(); + + Map params = new HashMap(); + params.put(WorkflowModel.PROP_TASK_ID, 3); // protected - shouldn't be written + params.put(WorkflowModel.PROP_DUE_DATE, new Date()); // task instance field + params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field + params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable + 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")); // context variable outside of task definition + params.put(ContentModel.PROP_OWNER, "admin"); // task assignment + + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, 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); + 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 = taskComponent.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 = taskComponent.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 = taskComponent.updateTask(task.id, null, addAssocs, null); + assertNotNull(taskU3.properties.get(assocName)); + assertEquals(3, ((List)taskU3.properties.get(assocName)).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 = taskComponent.updateTask(task.id, null, addAssocsAgain, null); + assertNotNull(taskU4.properties.get(assocName)); + assertEquals(5, ((List)taskU4.properties.get(assocName)).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 = taskComponent.updateTask(task.id, null, null, removeAssocs); + assertNotNull(taskU5.properties.get(assocName)); + assertEquals(3, ((List)taskU5.properties.get(assocName)).size()); + } + + public void testGetWorkflowInstances() { WorkflowDefinition workflowDef = getTestDefinition(); diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java index 07fe5f2411..068044a66b 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java @@ -17,10 +17,8 @@ package org.alfresco.service.cmr.workflow; import java.io.Serializable; -import java.util.List; import java.util.Map; -import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -51,16 +49,13 @@ public class WorkflowTask /** Task Properties as described by Task Definition */ public Map properties; - /** Task Associations as described by Task Definition */ - public Map> associations; - - /* (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { - return "WorkflowTask[id=" + id + ",name=" + name + ",state=" + state + ",def=" + definition + ",path=" + path.toString() + "]"; + String propCount = (properties == null) ? "null" : "" + properties.size(); + return "WorkflowTask[id=" + id + ",name=" + name + ",state=" + state + ",props=" + propCount + ",def=" + definition + ",path=" + path.toString() + "]"; } } diff --git a/source/java/org/alfresco/service/namespace/NamespaceService.java b/source/java/org/alfresco/service/namespace/NamespaceService.java index 9ef59abf26..cfd2c7a350 100644 --- a/source/java/org/alfresco/service/namespace/NamespaceService.java +++ b/source/java/org/alfresco/service/namespace/NamespaceService.java @@ -66,6 +66,12 @@ public interface NamespaceService extends NamespacePrefixResolver /** Application Model Prefix */ public static final String APP_MODEL_PREFIX = "app"; + /** Business Process Model URI */ + public static final String BPM_MODEL_1_0_URI = "http://www.alfresco.org/model/bpm/1.0"; + + /** Business Process Model Prefix */ + public static final String BPM_MODEL_PREFIX = "bpm"; + /** Alfresco View Namespace URI */ public static final String REPOSITORY_VIEW_1_0_URI = "http://www.alfresco.org/view/repository/1.0";