/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.taskmgmt.exe;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmException;
import org.jbpm.calendar.BusinessCalendar;
import org.jbpm.calendar.Duration;
import org.jbpm.graph.def.DelegationException;
import org.jbpm.graph.def.GraphElement;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.graph.exe.Token;
import org.jbpm.instantiation.Delegation;
import org.jbpm.instantiation.UserCodeInterceptorConfig;
import org.jbpm.jpdl.el.impl.JbpmExpressionEvaluator;
import org.jbpm.module.exe.ModuleInstance;
import org.jbpm.security.SecurityHelper;
import org.jbpm.svc.Services;
import org.jbpm.taskmgmt.TaskInstanceFactory;
import org.jbpm.taskmgmt.def.AssignmentHandler;
import org.jbpm.taskmgmt.def.Swimlane;
import org.jbpm.taskmgmt.def.Task;
import org.jbpm.taskmgmt.def.TaskMgmtDefinition;
import org.jbpm.taskmgmt.log.TaskCreateLog;
import org.jbpm.util.Clock;
/**
* process instance extension for managing tasks on a process instance.
*/
public class TaskMgmtInstance extends ModuleInstance
{
private static final long serialVersionUID = 1L;
TaskMgmtDefinition taskMgmtDefinition = null;
Map<String, SwimlaneInstance> swimlaneInstances = null;
Set<TaskInstance> taskInstances = null;
/**
* non persistent collection that stores all the task instances that have variable updates
*/
Collection<TaskInstance> taskInstanceVariableUpdates = null;
public TaskMgmtInstance()
{
}
public TaskMgmtInstance(TaskMgmtDefinition taskMgmtDefinition)
{
this.taskMgmtDefinition = taskMgmtDefinition;
}
// task instances ///////////////////////////////////////////////////////////
public TaskInstance createTaskInstance()
{
return createTaskInstance(null, (ExecutionContext)null);
}
public TaskInstance createTaskInstance(Task task)
{
return createTaskInstance(task, (ExecutionContext)null);
}
public TaskInstance createTaskInstance(Token token)
{
return createTaskInstance(null, new ExecutionContext(token));
}
/**
* creates a new task instance on the given token, for the given task.
*/
public TaskInstance createTaskInstance(Task task, Token token)
{
ExecutionContext executionContext = new ExecutionContext(token);
executionContext.setTask(task);
return createTaskInstance(task, executionContext);
}
/**
* creates a new task instance on the given task, in the given execution context.
*/
public TaskInstance createTaskInstance(Task task, ExecutionContext executionContext)
{
// instantiate the new task instance
TaskInstance taskInstance = instantiateNewTaskInstance(executionContext);
// bind the task instance to the TaskMgmtInstance
addTaskInstance(taskInstance);
// initialize the task instance
if (task != null)
taskInstance.setTask(task);
// assign an id to the task instance
Services.assignId(taskInstance);
// copy the task properties
/*
* XXX property initialization was already done in taskInstance.setTask(task) String description = null; if
* (task!=null) { description = task.getDescription(); taskInstance.setDescription(description);
* taskInstance.setBlocking(task.isBlocking()); taskInstance.setSignalling(task.isSignalling()); }
*/
if (executionContext != null)
{
Token token = executionContext.getToken();
taskInstance.setToken(token);
taskInstance.setProcessInstance(token.getProcessInstance());
taskInstance.initializeVariables();
if (task != null && task.getDueDate() != null)
{
Date baseDate;
String dueDateString = task.getDueDate();
String durationString = null;
if (dueDateString.startsWith("#"))
{
String baseDateEL = dueDateString.substring(0, dueDateString.indexOf("}") + 1);
Object result = JbpmExpressionEvaluator.evaluate(baseDateEL, executionContext);
if (result instanceof Date)
{
baseDate = (Date)result;
}
else if (result instanceof Calendar)
{
baseDate = ((Calendar)result).getTime();
}
else
{
throw new JbpmException("Invalid basedate type: " + baseDateEL + " is of type " + result.getClass().getName()
+ ". Only Date and Calendar are supported");
}
int endOfELIndex = dueDateString.indexOf("}");
if (endOfELIndex < (dueDateString.length() - 1))
{
char durationSeparator = dueDateString.substring(endOfELIndex + 1).trim().charAt(0);
if (durationSeparator != '+' && durationSeparator != '-')
{
throw new JbpmException("Invalid duedate, + or - missing after EL");
}
durationString = dueDateString.substring(endOfELIndex + 1).trim();
}
}
else
{
baseDate = Clock.getCurrentTime();
durationString = dueDateString;
}
Date dueDate;
if (durationString == null || durationString.length() == 0)
{
dueDate = baseDate;
}
else
{
BusinessCalendar businessCalendar = new BusinessCalendar();
dueDate = businessCalendar.add(baseDate, new Duration(durationString));
}
taskInstance.setDueDate(dueDate);
}
try
{
// update the executionContext
executionContext.setTask(task);
executionContext.setTaskInstance(taskInstance);
executionContext.setEventSource(task);
// evaluate the description
if (task != null)
{
String description = task.getDescription();
if ((description != null) && (description.indexOf("#{") != -1))
{
Object result = JbpmExpressionEvaluator.evaluate(description, executionContext);
if (result != null)
{
taskInstance.setDescription(result.toString());
}
}
}
// create the task instance
taskInstance.create(executionContext);
// if this task instance is created for a task, perform assignment
if (task != null)
{
taskInstance.assign(executionContext);
}
}
finally
{
// clean the executionContext
executionContext.setTask(null);
executionContext.setTaskInstance(null);
executionContext.setEventSource(null);
}
// log this creation
// WARNING: The events create and assign are fired in the right order, but
// the logs are still not ordered properly.
token.addLog(new TaskCreateLog(taskInstance, taskInstance.getActorId()));
}
else
{
taskInstance.create();
}
return taskInstance;
}
public SwimlaneInstance getInitializedSwimlaneInstance(ExecutionContext executionContext, Swimlane swimlane)
{
// initialize the swimlane
if (swimlaneInstances == null)
swimlaneInstances = new HashMap<String, SwimlaneInstance>();
SwimlaneInstance swimlaneInstance = swimlaneInstances.get(swimlane.getName());
if (swimlaneInstance == null)
{
swimlaneInstance = new SwimlaneInstance(swimlane);
addSwimlaneInstance(swimlaneInstance);
// assign the swimlaneInstance
performAssignment(swimlane.getAssignmentDelegation(), swimlane.getActorIdExpression(), swimlane.getPooledActorsExpression(), swimlaneInstance,
executionContext);
}
return swimlaneInstance;
}
public void performAssignment(Delegation assignmentDelegation, String actorIdExpression, String pooledActorsExpression, Assignable assignable,
ExecutionContext executionContext)
{
try
{
if (assignmentDelegation != null)
{
performAssignmentDelegation(assignmentDelegation, assignable, executionContext);
}
else
{
if (actorIdExpression != null)
{
performAssignmentActorIdExpr(actorIdExpression, assignable, executionContext);
}
if (pooledActorsExpression != null)
{
performAssignmentPooledActorsExpr(pooledActorsExpression, assignable, executionContext);
}
}
}
catch (Exception exception)
{
GraphElement graphElement = executionContext.getEventSource();
if (graphElement != null)
{
graphElement.raiseException(exception, executionContext);
}
else
{
throw new DelegationException(exception, executionContext);
}
}
}
void performAssignmentDelegation(Delegation assignmentDelegation, Assignable assignable, ExecutionContext executionContext) throws Exception
{
ClassLoader surroundingClassLoader = Thread.currentThread().getContextClassLoader();
try
{
// set context class loader correctly for delegation class (https://jira.jboss.org/jira/browse/JBPM-1448)
Thread.currentThread().setContextClassLoader(JbpmConfiguration.getProcessClassLoader(executionContext.getProcessDefinition()));
// instantiate the assignment handler
AssignmentHandler assignmentHandler = (AssignmentHandler)assignmentDelegation.instantiate();
// invoke the assignment handler
if (UserCodeInterceptorConfig.userCodeInterceptor != null)
{
UserCodeInterceptorConfig.userCodeInterceptor.executeAssignment(assignmentHandler, assignable, executionContext);
}
else
{
assignmentHandler.assign(assignable, executionContext);
}
}
finally
{
Thread.currentThread().setContextClassLoader(surroundingClassLoader);
}
}
void performAssignmentActorIdExpr(String actorIdExpression, Assignable assignable, ExecutionContext executionContext)
{
Object result = null;
String actorId = null;
try
{
result = JbpmExpressionEvaluator.evaluate(actorIdExpression, executionContext);
if (result == null)
{
throw new JbpmException("actor-id expression '" + actorIdExpression + "' returned null");
}
actorId = (String)result;
}
catch (ClassCastException e)
{
throw new JbpmException("actor-id expression '" + actorIdExpression + "' didn't resolve to a java.lang.String: '" + result + "' ("
+ result.getClass().getName() + ")");
}
assignable.setActorId(actorId);
}
void performAssignmentPooledActorsExpr(String pooledActorsExpression, Assignable assignable, ExecutionContext executionContext)
{
String[] pooledActors = null;
Object result = JbpmExpressionEvaluator.evaluate(pooledActorsExpression, executionContext);
if (result == null)
{
throw new JbpmException("pooled-actors expression '" + pooledActorsExpression + "' returned null");
}
if (result instanceof String[])
{
pooledActors = (String[])result;
}
else if (result instanceof Collection)
{
Collection collection = (Collection)result;
pooledActors = (String[])collection.toArray(new String[collection.size()]);
}
else if (result instanceof String)
{
List pooledActorList = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer((String)result, ",");
while (tokenizer.hasMoreTokens())
{
pooledActorList.add(tokenizer.nextToken().trim());
}
pooledActors = (String[])pooledActorList.toArray(new String[pooledActorList.size()]);
}
else
{
throw new JbpmException("pooled-actors expression '" + pooledActorsExpression + "' didn't resolve to a comma separated String, a Collection or a String[]: '"
+ result + "' (" + result.getClass().getName() + ")");
}
assignable.setPooledActors(pooledActors);
}
/**
* creates a task instance on the rootToken, and assigns it to the currently authenticated user.
*/
public TaskInstance createStartTaskInstance()
{
TaskInstance taskInstance = null;
Task startTask = taskMgmtDefinition.getStartTask();
if (startTask != null)
{
Token rootToken = processInstance.getRootToken();
ExecutionContext executionContext = new ExecutionContext(rootToken);
taskInstance = createTaskInstance(startTask, executionContext);
taskInstance.setActorId(SecurityHelper.getAuthenticatedActorId());
}
return taskInstance;
}
TaskInstance instantiateNewTaskInstance(ExecutionContext executionContext)
{
TaskInstanceFactory taskInstanceFactory = (TaskInstanceFactory)JbpmConfiguration.Configs.getObject("jbpm.task.instance.factory");
if (taskInstanceFactory == null)
{
throw new JbpmException("jbpm.task.instance.factory was not configured in jbpm.cfg.xml");
}
return taskInstanceFactory.createTaskInstance(executionContext);
}
/**
* is true if the given token has task instances that keep the token from leaving the current node.
*/
public boolean hasBlockingTaskInstances(Token token)
{
boolean hasBlockingTasks = false;
if (taskInstances != null)
{
Iterator<TaskInstance> iter = taskInstances.iterator();
while (iter.hasNext() && !hasBlockingTasks)
{
TaskInstance taskInstance = iter.next();
if ((!taskInstance.hasEnded()) && (taskInstance.isBlocking()) && (token != null) && (token.equals(taskInstance.getToken())))
{
hasBlockingTasks = true;
}
}
}
return hasBlockingTasks;
}
/**
* is true if the given token has task instances that are not yet ended.
*/
public boolean hasUnfinishedTasks(Token token)
{
return (getUnfinishedTasks(token).size() > 0);
}
/**
* is the collection of {@link TaskInstance}s on the given token that are not ended.
*/
public Collection<TaskInstance> getUnfinishedTasks(Token token)
{
Collection<TaskInstance> unfinishedTasks = new ArrayList<TaskInstance>();
if (taskInstances != null)
{
Iterator<TaskInstance> iter = taskInstances.iterator();
while (iter.hasNext())
{
TaskInstance task = iter.next();
if ((!task.hasEnded()) && (token != null) && (token.equals(task.getToken())))
{
unfinishedTasks.add(task);
}
}
}
return unfinishedTasks;
}
/**
* is true if there are {@link TaskInstance}s on the given token that can trigger the token to continue.
*/
public boolean hasSignallingTasks(ExecutionContext executionContext)
{
return (getSignallingTasks(executionContext).size() > 0);
}
/**
* is the collection of {@link TaskInstance}s for the given token that can trigger the token to continue.
*/
public Collection<TaskInstance> getSignallingTasks(ExecutionContext executionContext)
{
Collection<TaskInstance> signallingTasks = new ArrayList<TaskInstance>();
if (taskInstances != null)
{
Iterator<TaskInstance> iter = taskInstances.iterator();
while (iter.hasNext())
{
TaskInstance taskInstance = iter.next();
if (taskInstance.isSignalling() && (executionContext.getToken().equals(taskInstance.getToken())))
{
signallingTasks.add(taskInstance);
}
}
}
return signallingTasks;
}
/**
* returns all the taskInstances for the this process instance. This includes task instances that have been completed
* previously.
*/
public Collection<TaskInstance> getTaskInstances()
{
return taskInstances;
}
public void addTaskInstance(TaskInstance taskInstance)
{
if (taskInstances == null)
taskInstances = new HashSet<TaskInstance>();
taskInstances.add(taskInstance);
taskInstance.setTaskMgmtInstance(this);
}
public void removeTaskInstance(TaskInstance taskInstance)
{
if (taskInstances != null)
{
taskInstances.remove(taskInstance);
}
}
// swimlane instances ///////////////////////////////////////////////////////
public Map<String, SwimlaneInstance> getSwimlaneInstances()
{
return swimlaneInstances;
}
public void addSwimlaneInstance(SwimlaneInstance swimlaneInstance)
{
if (swimlaneInstances == null)
swimlaneInstances = new HashMap<String, SwimlaneInstance>();
swimlaneInstances.put(swimlaneInstance.getName(), swimlaneInstance);
swimlaneInstance.setTaskMgmtInstance(this);
}
public SwimlaneInstance getSwimlaneInstance(String swimlaneName)
{
return swimlaneInstances != null ? swimlaneInstances.get(swimlaneName) : null;
}
public SwimlaneInstance createSwimlaneInstance(String swimlaneName)
{
Swimlane swimlane = (taskMgmtDefinition != null ? taskMgmtDefinition.getSwimlane(swimlaneName) : null);
if (swimlane != null)
{
return createSwimlaneInstance(swimlane);
}
throw new JbpmException("couldn't create swimlane instance for non-existing swimlane " + swimlaneName);
}
public SwimlaneInstance createSwimlaneInstance(Swimlane swimlane)
{
if (swimlaneInstances == null)
swimlaneInstances = new HashMap<String, SwimlaneInstance>();
SwimlaneInstance swimlaneInstance = new SwimlaneInstance(swimlane);
try
{
swimlaneInstance.setTaskMgmtInstance(this);
Class<?> persistentMapClass = swimlaneInstances.getClass();
Field mapField = persistentMapClass.getDeclaredField("map");
mapField.setAccessible(true);
// TODO remove the size when we switch to hibernate 3.2.1 (it's a workaround for a bug)
swimlaneInstances.size();
swimlaneInstances.put(swimlaneInstance.getName(), swimlaneInstance);
}
catch (Exception e)
{
e.printStackTrace();
}
return swimlaneInstance;
}
// getters and setters //////////////////////////////////////////////////////
public TaskMgmtDefinition getTaskMgmtDefinition()
{
return taskMgmtDefinition;
}
// Afegit per a modificar quan s'elimina la definició de procés o canvia versió de procés
public void setTaskMgmtDefinition(TaskMgmtDefinition taskMgmtDefinition) {
this.taskMgmtDefinition = taskMgmtDefinition;
}
/**
* suspends all task instances for this process instance.
*/
public void suspend(Token token)
{
if (token == null)
{
throw new JbpmException("can't suspend task instances for token null");
}
if (taskInstances != null)
{
Iterator<TaskInstance> iter = taskInstances.iterator();
while (iter.hasNext())
{
TaskInstance taskInstance = iter.next();
if ((token.equals(taskInstance.getToken())) && (taskInstance.isOpen()))
{
taskInstance.suspend();
}
}
}
}
/**
* resumes all task instances for this process instance.
*/
public void resume(Token token)
{
if (token == null)
{
throw new JbpmException("can't suspend task instances for token null");
}
if (taskInstances != null)
{
Iterator<TaskInstance> iter = taskInstances.iterator();
while (iter.hasNext())
{
TaskInstance taskInstance = iter.next();
if ((token.equals(taskInstance.getToken())) && (taskInstance.isOpen()))
{
taskInstance.resume();
}
}
}
}
void notifyVariableUpdate(TaskInstance taskInstance)
{
if (taskInstanceVariableUpdates == null)
{
taskInstanceVariableUpdates = new HashSet<TaskInstance>();
}
taskInstanceVariableUpdates.add(taskInstance);
}
/**
* returns the collection of task instance with variable updates.
*/
public Collection<TaskInstance> getTaskInstancesWithVariableUpdates()
{
return taskInstanceVariableUpdates;
}
/**
* convenience method to end all tasks related to a given process instance.
*/
public void endAll()
{
if (taskInstances != null)
{
Iterator<TaskInstance> iter = taskInstances.iterator();
while (iter.hasNext())
{
TaskInstance taskInstance = iter.next();
if (!taskInstance.hasEnded())
{
taskInstance.end();
}
}
}
}
/**
* removes signalling capabilities from all task instances related to the given token.
*/
public void removeSignalling(Token token)
{
if (taskInstances != null)
{
Iterator<TaskInstance> iter = taskInstances.iterator();
while (iter.hasNext())
{
TaskInstance taskInstance = iter.next();
if ((token != null) && (token.equals(taskInstance.getToken())))
{
taskInstance.setSignalling(false);
}
}
}
}
}