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
This commit is contained in:
David Caruana
2006-08-08 17:35:50 +00:00
parent 499367c4e9
commit 47833426ca
9 changed files with 891 additions and 59 deletions

View File

@@ -70,6 +70,25 @@ public interface TaskComponent
*/
public WorkflowTask updateTask(String taskId, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add, Map<QName, List<NodeRef>> 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);
}

View File

@@ -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");
}

View File

@@ -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<QName, Serializable> properties, Map<QName, List<NodeRef>> add, Map<QName, List<NodeRef>> remove)
public WorkflowTask updateTask(final String taskId, final Map<QName, Serializable> properties, final Map<QName, List<NodeRef>> add, final Map<QName, List<NodeRef>> 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<QName, Serializable> newProperties = properties;
if (newProperties == null && (add != null || remove != null))
{
newProperties = new HashMap<QName, Serializable>(10);
}
if (add != null || remove != null)
{
Map<QName, Serializable> existingProperties = getTaskProperties(taskInstance);
if (add != null)
{
// add new associations
for (Entry<QName, List<NodeRef>> toAdd : add.entrySet())
{
// retrieve existing list of noderefs for association
List<NodeRef> existingAdd = (List<NodeRef>)newProperties.get(toAdd.getKey());
if (existingAdd == null)
{
existingAdd = (List<NodeRef>)existingProperties.get(toAdd.getKey());
}
// make the additions
if (existingAdd == null)
{
newProperties.put(toAdd.getKey(), (Serializable)toAdd.getValue());
}
else
{
for (NodeRef nodeRef : (List<NodeRef>)toAdd.getValue())
{
if (!(existingAdd.contains(nodeRef)))
{
existingAdd.add(nodeRef);
}
}
}
}
}
if (remove != null)
{
// add new associations
for (Entry<QName, List<NodeRef>> toRemove: remove.entrySet())
{
// retrieve existing list of noderefs for association
List<NodeRef> existingRemove = (List<NodeRef>)newProperties.get(toRemove.getKey());
if (existingRemove == null)
{
existingRemove = (List<NodeRef>)existingProperties.get(toRemove.getKey());
}
// make the subtractions
if (existingRemove != null)
{
for (NodeRef nodeRef : (List<NodeRef>)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<QName, Serializable> 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<QName, Serializable> 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<AspectDefinition> aspects = typeDef.getDefaultAspects();
List<QName> aspectNames = new ArrayList<QName>(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<QName, Serializable> getTaskProperties(TaskInstance instance)
{
// establish task definition
TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask()));
Map<QName, AssociationDefinition> taskAssocs = taskDef.getAssociations();
// map arbitrary task variables
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(10);
Map<String, Object> vars = instance.getVariablesLocally();
for (Entry<String, Object> 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<NodeRef> 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<QName, Serializable> properties)
{
if (properties == null)
{
return;
}
// establish task definition
TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask()));
Map<QName, PropertyDefinition> taskProperties = taskDef.getProperties();
Map<QName, AssociationDefinition> taskAssocs = taskDef.getAssociations();
// map each parameter to task
for (Entry<QName, Serializable> 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<NodeRef>)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<NodeRef> 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<NodeRef> mapNameToAuthority(String[] names)
{
List<NodeRef> authorities = null;
if (names != null)
{
authorities = new ArrayList<NodeRef>(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;
}

View File

@@ -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<QName, Serializable> params = new HashMap<QName, Serializable>();
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<WorkflowTask> 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<QName, Serializable> params = new HashMap<QName, Serializable>();
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<WorkflowTask> 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<QName, Serializable> updateProperties2 = new HashMap<QName, Serializable>();
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<NodeRef> toAdd = new ArrayList<NodeRef>();
toAdd.add(new NodeRef("workspace://1/1001"));
toAdd.add(new NodeRef("workspace://1/1002"));
toAdd.add(new NodeRef("workspace://1/1003"));
Map<QName, List<NodeRef>> addAssocs = new HashMap<QName, List<NodeRef>>();
addAssocs.put(assocName, toAdd);
WorkflowTask taskU3 = taskComponent.updateTask(task.id, null, addAssocs, null);
assertNotNull(taskU3.properties.get(assocName));
assertEquals(3, ((List<NodeRef>)taskU3.properties.get(assocName)).size());
// add to assocation again
List<NodeRef> toAddAgain = new ArrayList<NodeRef>();
toAddAgain.add(new NodeRef("workspace://1/1004"));
toAddAgain.add(new NodeRef("workspace://1/1005"));
Map<QName, List<NodeRef>> addAssocsAgain = new HashMap<QName, List<NodeRef>>();
addAssocsAgain.put(assocName, toAddAgain);
WorkflowTask taskU4 = taskComponent.updateTask(task.id, null, addAssocsAgain, null);
assertNotNull(taskU4.properties.get(assocName));
assertEquals(5, ((List<NodeRef>)taskU4.properties.get(assocName)).size());
// remove assocation
List<NodeRef> toRemove = new ArrayList<NodeRef>();
toRemove.add(new NodeRef("workspace://1/1002"));
toRemove.add(new NodeRef("workspace://1/1003"));
Map<QName, List<NodeRef>> removeAssocs = new HashMap<QName, List<NodeRef>>();
removeAssocs.put(assocName, toRemove);
WorkflowTask taskU5 = taskComponent.updateTask(task.id, null, null, removeAssocs);
assertNotNull(taskU5.properties.get(assocName));
assertEquals(3, ((List<NodeRef>)taskU5.properties.get(assocName)).size());
}
public void testGetWorkflowInstances()
{
WorkflowDefinition workflowDef = getTestDefinition();