/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.jboss.processFlow.tasks;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Remote;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;
import javax.transaction.UserTransaction;
import javax.transaction.TransactionManager;
import javax.transaction.Transaction;
import org.apache.log4j.Logger;
import org.drools.SystemEventListener;
import org.drools.SystemEventListenerFactory;
import org.jbpm.task.*;
import org.jbpm.task.service.UserGroupCallbackManager;
import org.jbpm.task.event.TaskEventListener;
import org.jbpm.task.query.TaskSummary;
import org.jbpm.task.service.CannotAddTaskException;
import org.jbpm.task.service.ContentData;
import org.jbpm.task.service.EscalatedDeadlineHandler;
import org.jbpm.task.service.FaultData;
import org.jbpm.task.service.Operation;
import org.jbpm.task.service.PermissionDeniedException;
import org.jbpm.task.service.TaskException;
import org.jbpm.task.service.TaskService;
import org.jbpm.task.service.TaskServiceSession;
import org.jboss.processFlow.knowledgeService.IBaseKnowledgeSession;
import org.jboss.processFlow.PFPBaseService;
import org.jboss.processFlow.tasks.event.PfpTaskEventSupport;
/**
* JA Bride
* 23 March 2011
* Purpose : synchronous API to various human task functions
*/
@Remote(ITaskService.class)
@Singleton(name="taskProxy")
@Startup
@Lock(LockType.READ)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class HumanTaskService extends PFPBaseService implements ITaskService {
public static final String JBPM_TASK_EMF_RESOURCE_LOCAL = "org.jbpm.task.resourceLocal";
public static final String JBPM_TASK_EMF = "org.jbpm.task";
public static final String TASK_BY_TASK_ID = "TaskByTaskId";
public static final String GROUP="group";
public static final String USER="user";
private static final Logger log = Logger.getLogger("HumanTaskService");
private boolean enableIntelligentMapping = false;
private @PersistenceUnit(unitName=JBPM_TASK_EMF_RESOURCE_LOCAL) EntityManagerFactory humanTaskEMF;
private @PersistenceUnit(unitName=JBPM_TASK_EMF) EntityManagerFactory jtaHumanTaskEMF;
private @Resource UserTransaction uTrnx;
private @Resource(name="java:/TransactionManager") TransactionManager tMgr;
// https://issues.jboss.org/browse/AS7-4567
//@EJB(name="kSessionProxy", beanName="ejb/prodKSessionProxy")
@EJB(name="kSessionProxy", lookup=IBaseKnowledgeSession.BASE_JNDI)
private IBaseKnowledgeSession kSessionProxy;
private PfpTaskEventSupport eventSupport;
private TaskService taskService;
private Set<String> tempOrgEntitySet = new HashSet<String>();
private boolean enableLog = true;
// brms5.3.1 bombs and does not recover from concurrent clients attempting to add tasks with same OrganizationalEntity objects at the same time
// this functionality prevents the following error from occuring:
// ERROR: duplicate key value violates unique constraint "organizationalentity_pkey"
// by: org.jbpm.task.service.TaskServiceSession.addUserFromCallbackOperation(TaskServiceSession.java:1512) [jbpm-human-task-5.3.1.BRMS.jar:5.3.1.BRMS]
private void checkAndSetTempOrgEntitySet(OrganizationalEntity orgObj) {
String id = orgObj.getId();
if(tempOrgEntitySet.contains(id))
return;
synchronized(tempOrgEntitySet){
if(!tempOrgEntitySet.contains(id)){
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
if(orgObj instanceof User)
taskSession.addUser((User)orgObj);
else
taskSession.addGroup((Group)orgObj);
}catch (Exception e) {
throw new RuntimeException(e);
}finally {
if(taskSession != null)
taskSession.dispose();
}
log.info("checkAndSetTempOrgEntitySet() adding following userId to tempOrgEntitySet to avoid possible duplicate entry complaints: "+id);
try{Thread.sleep(1000);}catch(Exception x){x.printStackTrace();}
tempOrgEntitySet.add(id);
}
}
}
private void checkAndSetTempOrgEntitySet(String id, String type) {
if(!tempOrgEntitySet.contains(id)){
OrganizationalEntity orgObj = null;
if(GROUP.equals(type)){
orgObj = new Group(id);
}else {
orgObj = new User(id);
}
checkAndSetTempOrgEntitySet(orgObj);
}
}
private void checkAndSetListOfOrgEntities(List<String> ids, String type){
for(String id : ids){
checkAndSetTempOrgEntitySet(id, type);
}
}
private void checkAndSetListOfOrgEntities(List<OrganizationalEntity> orgObjs){
for(OrganizationalEntity orgObj : orgObjs){
checkAndSetTempOrgEntitySet(orgObj);
}
}
/**
- creates task with status of Ready
this method could throw the following RuntimeExceptions :
1) org.hibernate.exception.ConstraintViolationException
-- can occur if Potential Owner of task is assigned a userId or groupId that has not been previously loaded into the organizationalentity table
-- note that apparently a task will still be persisted in database, however its status will be 'Created' and not 'Ready'
*/
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public long addTask(Task taskObj, ContentData cData) throws CannotAddTaskException {
OrganizationalEntity orgOBj = taskObj.getTaskData().getCreatedBy();
checkAndSetTempOrgEntitySet(orgOBj);
checkAndSetListOfOrgEntities(taskObj.getPeopleAssignments().getPotentialOwners());
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
taskSession.addTask(taskObj, cData);
int sessionId = taskObj.getTaskData().getProcessSessionId();
eventSupport.fireTaskAdded(taskObj, cData);
return taskObj.getId();
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
/*
- changes task status from : Ready --> Reserved
- TaskServiceSession set to 'local-JTA'
- will set container managed trnx demarcation to NOT_SUPPORTED so as to leverage bean-managed trnx demarcation in TaskServiceSession
- using BMT in TaskServiceSession will force a synchroneous trnx commit (and subsequent database flush)
* - http://en.wikibooks.org/wiki/Java_Persistence/Locking#Not_sending_version_to_client.2C_only_locking_on_the_server
*/
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void claimTask(Long taskId, String idRef, String userId, List<String> roles) throws TaskException {
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
taskSession.taskOperation(Operation.Claim, taskId, userId, null, null, roles);
Task taskObj = taskSession.getTask(taskId);
int sessionId = taskObj.getTaskData().getProcessSessionId();
eventSupport.fireTaskClaimed(taskId, userId);
} catch(javax.persistence.RollbackException x) {
Throwable firstCause = x.getCause();
Throwable secondCause = null;
if(firstCause != null) {
secondCause = firstCause.getCause();
}
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("claimTask() 1. exception = : "+x);
sBuilder.append(taskId);
sBuilder.append("\n\tcause(s) = "+x.getCause()+"\n\t"+secondCause);
log.error(sBuilder.toString());
throw new PermissionDeniedException(sBuilder.toString());
}catch(RuntimeException x) {
Throwable firstCause = x.getCause();
Throwable secondCause = null;
if(firstCause != null) {
secondCause = firstCause.getCause();
}
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("claimTask() 2. exception = : "+x);
sBuilder.append(taskId);
sBuilder.append("\n\tcause(s) = "+x.getCause()+"\n\t"+secondCause);
log.error(sBuilder.toString());
throw new PermissionDeniedException(sBuilder.toString());
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public TaskSummary guaranteedClaimTaskAssignedAsPotentialOwnerByStatusByGroup(String userId, List<String> groupIds, List<Status> statuses, String language, Integer firstResult, Integer maxResults){
List<TaskSummary> taskList = this.getTasksAssignedAsPotentialOwnerByStatusByGroup(userId, groupIds, statuses, language, firstResult, maxResults);
TaskSummary claimedTask = null;
for(TaskSummary tObj : taskList){
try {
this.claimTask(tObj.getId(), userId, userId, groupIds);
claimedTask = tObj;
break;
}catch(org.jbpm.task.service.PermissionDeniedException x){
if(enableLog){
log.error("guaranteedClaimTaskAssignedAsPotentialOwnerByStatusByGroup() PermissionDeniedException for taskId = "+tObj.getId());
}
}
}
return claimedTask;
}
/**
- changes task status from : InProgress --> Completed
- this method blocks until process instance arrives at next 'safe point'
- this method uses BMT to define the scope of the transaction , boolean disposeKsession)
*/
public void completeTask(Long taskId, Map<String, Object> outboundTaskVars, String userId) {
TaskServiceSession taskSession = null;
try {
taskSession = jtaTaskService.createSession();
Task taskObj = taskSession.getTask(taskId);
int sessionId = taskObj.getTaskData().getProcessSessionId();
long pInstanceId = taskObj.getTaskData().getProcessInstanceId();
if(taskObj.getTaskData().getStatus() != Status.InProgress) {
log.warn("completeTask() task with following id will be changed to status of InProgress: "+taskId);
taskSession.taskOperation(Operation.Start, taskId, userId, null, null, null);
eventSupport.fireTaskStarted(taskId, userId);
}
if(outboundTaskVars == null)
outboundTaskVars = new HashMap<String, Object>();
//~~ Nick: intelligent mapping the task input parameters as the results map
// that said, copy the input parameters to the result map
Map<String, Object> newOutboundTaskVarMap = null;
if (isEnableIntelligentMapping()) {
long documentContentId = taskObj.getTaskData().getDocumentContentId();
if(documentContentId == -1)
throw new RuntimeException("completeTask() documentContent must be created with addTask invocation");
// 1) constructure a purely empty new HashMap, as the final results map, which will be mapped out to the process instance
newOutboundTaskVarMap = new HashMap<String, Object>();
// 2) put the original input parameters first
newOutboundTaskVarMap.putAll(populateHashWithTaskContent(taskSession.getContent(documentContentId), "documentContent"));
// 3) put the user modified into the final result map, replace the original values with the user modified ones if any
newOutboundTaskVarMap.putAll(outboundTaskVars);
} //~~ intelligent mapping
else {
newOutboundTaskVarMap = outboundTaskVars;
}
ContentData contentData = this.convertTaskVarsToContentData(newOutboundTaskVarMap, null);
taskSession.taskOperation(Operation.Complete, taskId, userId, null, contentData, null);
eventSupport.fireTaskCompleted(taskId, userId);
StringBuilder sBuilder = new StringBuilder("completeTask()");
this.dumpTaskDetails(taskObj, sBuilder);
// add TaskChangeDetails to outbound variables so that downstream branches can route accordingly
TaskChangeDetails changeDetails = new TaskChangeDetails();
changeDetails.setNewStatus(Status.Completed);
changeDetails.setReason(TaskChangeDetails.NORMAL_COMPLETION_REASON);
changeDetails.setTaskId(taskId);
newOutboundTaskVarMap.put(TaskChangeDetails.TASK_CHANGE_DETAILS, changeDetails);
kSessionProxy.completeWorkItem(taskObj.getTaskData().getWorkItemId(), newOutboundTaskVarMap, pInstanceId, sessionId);
}catch(org.jbpm.task.service.PermissionDeniedException x) {
throw x;
}catch(RuntimeException x) {
if(x.getCause() != null && (x.getCause() instanceof javax.transaction.RollbackException) && (x.getMessage().indexOf("Could not commit transaction") != -1)) {
String message = "completeTask() RollbackException thrown most likely because the following task object is dirty : "+taskId;
log.error(message);
throw new PermissionDeniedException(message);
}else {
throw x;
}
}catch(Exception x) {
throw new RuntimeException(x);
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void delegateTask(Long taskId, String userId, String targetUserId) {
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
taskSession.taskOperation(Operation.Delegate, taskId, userId, targetUserId, null, null);
//eventSupport.fireTaskDelegated(taskId, userId, targetUserId);
}catch(RuntimeException x) {
throw x;
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
/**
* NOTE: allows for a null userId ... in which case this implementation will use the actual owner of the task to execute the fail task operation
*/
public void failTask(Long taskId, Map<String, Object> outboundTaskVars, String userId, String faultName) {
TaskServiceSession taskSession = null;
try {
taskSession = jtaTaskService.createSession();
Task taskObj = taskSession.getTask(taskId);
int sessionId = taskObj.getTaskData().getProcessSessionId();
long pInstanceId = taskObj.getTaskData().getProcessInstanceId();
if(taskObj.getTaskData().getStatus() != Status.InProgress) {
throw new PermissionDeniedException("failTask() will not attempt operation due to incorrect existing status of : "+taskObj.getTaskData().getStatus());
}
if(userId == null){
userId = taskObj.getTaskData().getActualOwner().getId();
}
FaultData contentData = (FaultData)this.convertTaskVarsToContentData(outboundTaskVars, faultName);
taskSession.taskOperation(Operation.Fail, taskId, userId, null, contentData, null);
eventSupport.fireTaskFailed(taskId, userId);
StringBuilder sBuilder = new StringBuilder("failTask()");
this.dumpTaskDetails(taskObj, sBuilder);
kSessionProxy.completeWorkItem(taskObj.getTaskData().getWorkItemId(), outboundTaskVars, pInstanceId, sessionId);
}catch(PermissionDeniedException x) {
throw new RuntimeException(x);
}
}
/**
* NOTE: allows for a null userId ... in which case this implementation will use the actual owner of the task to execute the fail task operation
*/
@Override
public void exitTask(Long taskId, String userId) {
TaskServiceSession taskSession = null;
try {
taskSession = jtaTaskService.createSession();
Task taskObj = taskSession.getTask(taskId);
int sessionId = taskObj.getTaskData().getProcessSessionId();
long pInstanceId = taskObj.getTaskData().getProcessInstanceId();
Status taskStatus = taskObj.getTaskData().getStatus();
if (taskStatus != Status.Created || taskStatus != Status.Ready || taskStatus != Status.Reserved || taskStatus != Status.InProgress) {
throw new PermissionDeniedException("exitTask() will not attempt operation due to incorrect existing status of: " + taskStatus);
}
if (userId == null) {
userId = taskObj.getTaskData().getActualOwner().getId();
}
taskSession.taskOperation(Operation.Exit, taskId, userId, null, null, null);
eventSupport.fireTaskExited(taskId, userId);
StringBuilder sBuilder = new StringBuilder("exitTask()");
this.dumpTaskDetails(taskObj, sBuilder);
kSessionProxy.completeWorkItem(taskObj.getTaskData().getWorkItemId(), null, pInstanceId, sessionId);
} catch(PermissionDeniedException x) {
throw new RuntimeException(x);
}
}
public void nominateTask(final long taskId, String userId, final List<OrganizationalEntity> potentialOwners){
throw new RuntimeException("nominateTask() PLEASE IMPLEMENT ME");
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void releaseTask(Long taskId, String userId){
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
Task taskObj = taskSession.getTask(taskId);
int sessionId = taskObj.getTaskData().getProcessSessionId();
taskSession.taskOperation(Operation.Release, taskId, userId, null, null, null);
eventSupport.fireTaskReleased(taskId, userId);
} catch(Exception x) {
throw new RuntimeException(x);
} finally {
if (taskSession != null)
taskSession.dispose();
}
}
/**
* skipTask
* <pre>
* NOTE: underlying jbpm5 TaskServiceSession does not allow for outbound task variables with Operation.Skip
* </pre>
*/
public void skipTask(Long taskId, String userId, Map<String, Object> outboundTaskVars) {
TaskServiceSession taskSession = null;
try {
taskSession = jtaTaskService.createSession();
Task taskObj = taskSession.getTask(taskId);
int sessionId = taskObj.getTaskData().getProcessSessionId();
long pInstanceId = taskObj.getTaskData().getProcessInstanceId();
taskSession.taskOperation(Operation.Skip, taskId, userId, null, null, null);
eventSupport.fireTaskSkipped(taskId, userId);
StringBuilder sBuilder = new StringBuilder("skipTask()");
this.dumpTaskDetails(taskObj, sBuilder);
kSessionProxy.completeWorkItem(taskObj.getTaskData().getWorkItemId(), outboundTaskVars, pInstanceId, sessionId);
}catch(org.jbpm.task.service.PermissionDeniedException x) {
throw x;
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void skipTaskByWorkItemId(Long workItemId){
TaskServiceSession taskSession = null;
try {
Task taskObj = getTaskByWorkItemId(workItemId);
Status tStatus = taskObj.getTaskData().getStatus();
int sessionId = taskObj.getTaskData().getProcessSessionId();
if(tStatus == Status.Obsolete || tStatus == Status.Error || tStatus == Status.Failed) {
log.error("skipTaskByWorkItemId() can not skip task since status is : "+tStatus.name());
return;
}
taskSession = taskService.createSession();
String userId = ITaskService.ADMINISTRATOR;
User actualOwner = taskObj.getTaskData().getActualOwner();
if(actualOwner != null)
userId = actualOwner.getId();
taskSession.taskOperation(Operation.Skip, taskObj.getId(), userId, null, null, null);
eventSupport.fireTaskSkipped(taskObj.getId(), userId);
}catch(RuntimeException x) {
throw x;
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
/**
- changes task status from : Reserved --> InProgress
- TaskServiceSession set to 'local-JTA'
- will set container managed trnx demarcation to NOT_SUPPORTED(NEVER) so as to leverage bean-managed trnx demarcation in TaskServiceSession
- using BMT in TaskServiceSession will force a synchroneous trnx commit (and subsequent database flush)
*/
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void startTask(Long taskId, String userId) {
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
Task taskObj = taskSession.getTask(taskId);
int sessionId = taskObj.getTaskData().getProcessSessionId();
taskSession.taskOperation(Operation.Start, taskId, userId, null, null, null);
eventSupport.fireTaskStarted(taskId, userId);
}catch(Exception x) {
throw new RuntimeException("startTask", x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
/*
* NOTE: use with caution
* in particular, this implementation uses a JTA enabled entity manager factory
* the reason is that in postgresql, using a RESOURCE_LOCAL EMF throws a "Large Object exception" (google it)
* so try to avoid taxing the transaction manager with an abusive amount of calls to this function
*/
public TaskSummary getTask(Long taskId){
TaskServiceSession taskSession = null;
try {
taskSession = jtaTaskService.createSession();
Task taskObj = taskSession.getTask(taskId);
TaskSummary tSummary = new TaskSummary();
tSummary.setActivationTime(taskObj.getTaskData().getExpirationTime());
tSummary.setActualOwner(taskObj.getTaskData().getActualOwner());
tSummary.setCreatedBy(taskObj.getTaskData().getCreatedBy());
tSummary.setCreatedOn(taskObj.getTaskData().getCreatedOn());
tSummary.setDescription(taskObj.getDescriptions().get(0).getText());
tSummary.setExpirationTime(taskObj.getTaskData().getExpirationTime());
tSummary.setId(taskObj.getId());
tSummary.setName(taskObj.getNames().get(0).getText());
tSummary.setPriority(taskObj.getPriority());
tSummary.setProcessId(taskObj.getTaskData().getProcessId());
tSummary.setProcessInstanceId(taskObj.getTaskData().getProcessInstanceId());
tSummary.setStatus(taskObj.getTaskData().getStatus());
tSummary.setSubject(taskObj.getSubjects().get(0).getText());
return tSummary;
}catch(RuntimeException x) {
throw x;
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
public Task getTaskByWorkItemId(Long workItemId){
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
return taskSession.getTaskByWorkItemId(workItemId);
}catch(RuntimeException x) {
throw x;
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<TaskSummary> getTasksAssignedAsPotentialOwner(String userId, List<String> groupIds, String language) {
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
return taskSession.getTasksAssignedAsPotentialOwner(userId, groupIds, language);
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
public void dumpTaskDetails(Task taskObj, StringBuilder sBuilder) {
long workItemId = taskObj.getTaskData().getWorkItemId();
int ksessionIdFromTask = taskObj.getTaskData().getProcessSessionId();
long documentContentId = taskObj.getTaskData().getDocumentContentId();
long outputContentId = taskObj.getTaskData().getOutputContentId();
long processInstanceId = taskObj.getTaskData().getProcessInstanceId();
sBuilder.append("\n\ttaskId = ");
sBuilder.append(taskObj.getId());
sBuilder.append("\n\tworkItemId = ");
sBuilder.append(workItemId);
sBuilder.append("\n\tvariables: processInstance --> task : documentContentId = ");
sBuilder.append(documentContentId);
sBuilder.append("\n\tvariables: task --> processInstance : outputContentId = ");
sBuilder.append(outputContentId);
sBuilder.append("\n\tprocessInstanceId = ");
sBuilder.append(processInstanceId);
sBuilder.append("\n\tksessionIdFromTask = ");
sBuilder.append(ksessionIdFromTask);
log.info(sBuilder.toString());
}
/*
* NOTE: use with caution
* in particular, this implementation uses a JTA enabled entity manager factory
* the reason is that in postgresql, using a RESOURCE_LOCAL EMF throws a "Large Object exception" (google it)
* so try to avoid taxing the transaction manager with an abusive amount of calls to this function
*/
public String getTaskName(Long taskId, String language) {
TaskServiceSession taskSession = null;
try {
taskSession = jtaTaskService.createSession();
Task taskObj = taskSession.getTask(taskId);
Iterator iTasks = taskObj.getNames().iterator();
while(iTasks.hasNext()){
I18NText iObj = (I18NText)iTasks.next();
if(iObj.getLanguage().equals(language))
return iObj.getText();
}
throw new RuntimeException("getTaskName() can not find taskName for taskId = "+taskId+" : language = "+language);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public Map<String,Object> getTaskContent(Long taskId, Boolean inbound) {
StringBuilder qBuilder = new StringBuilder();
qBuilder.append("select c.content from Content c, Task t where c.id = ");
if(inbound)
qBuilder.append("t.taskData.documentContentId ");
else
qBuilder.append("t.taskData.outputContentId ");
qBuilder.append("and t.id = ");
qBuilder.append(taskId);
EntityManager eManager = null;
ObjectInputStream in = null;
try {
eManager = humanTaskEMF.createEntityManager();
uTrnx.begin();
Query queryObj = eManager.createQuery(qBuilder.toString());
byte[] contentBytes = (byte[])queryObj.getSingleResult();
uTrnx.commit();
log.info("getTaskContent() taskId = "+taskId+" : inbound = "+inbound+" contentBytes size = "+contentBytes.length);
ByteArrayInputStream bis = new ByteArrayInputStream(contentBytes);
in = new ObjectInputStream(bis);
return (Map<String,Object>)in.readObject();
} catch(Exception x) {
throw new RuntimeException(x);
} finally {
if(eManager != null)
eManager.close();
try {
if(in != null)
in.close();
}catch(Exception x){x.printStackTrace();}
}
}
// should consider using taskServiceSession.setOutput(final long taskId, final String userId, final ContentData outputContentData) directly
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void setTaskContent(Task taskObj, Boolean inbound, Map<String, Object> taskContent) {
EntityManager eManager = null;
ObjectOutputStream out = null;
try {
eManager = humanTaskEMF.createEntityManager();
uTrnx.begin();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
out = new ObjectOutputStream(bos);
out.writeObject(taskContent);
out.close();
byte[] byteResults = bos.toByteArray();
// persist the serialized results map into Content table
Content content = new Content();
content.setContent(byteResults);
eManager.persist(content); // will generate a unique id for this content by jpa
ContentData contentData = new ContentData();
contentData.setContent(byteResults);
contentData.setAccessType(org.jbpm.task.AccessType.Inline);
taskObj.getTaskData().setOutput(content.getId(), contentData);
eManager.merge(taskObj);
uTrnx.commit();
log.info("setTaskContent() taskId = "+taskObj.getId()+" : inbound = "+inbound+" contentBytes size = "+byteResults.length);
} catch(Exception x) {
throw new RuntimeException(x);
} finally {
if(eManager != null)
eManager.close();
try {
if(out != null)
out.close();
}catch(Exception x){x.printStackTrace();}
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void setTaskContent(Long taskId, String userId, Boolean inbound, Map<String, Object> taskContent) {
TaskServiceSession taskSession = null;
ObjectOutputStream out = null;
try {
if (inbound) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
out = new ObjectOutputStream(bos);
out.writeObject(taskContent);
out.close();
byte[] byteResults = bos.toByteArray();
Content content = new Content();
content.setContent(byteResults);
// Don't need to explicitly enable define transaction demarcation as this is done in the taskSession.
taskSession = taskService.createSession();
taskSession.setDocumentContent(taskId, content);
} else {
ContentData contentData = convertTaskVarsToContentData(taskContent, null);
taskSession = taskService.createSession();
taskSession.setOutput(taskId, userId, contentData);
}
} catch (Exception x) {
throw new RuntimeException(x);
} finally {
if (taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String printTaskContent(Long taskId, Boolean inbound) {
Map<?,?> contentHash = getTaskContent(taskId, inbound);
StringBuilder sBuilder = new StringBuilder("taskContent taskId = ");
sBuilder.append(taskId);
sBuilder.append("\t: inbound = ");
sBuilder.append(inbound);
for (Map.Entry<?, ?> entry: contentHash.entrySet()) {
sBuilder.append("\n\tkey ="+entry.getKey()+"\t: value = "+entry.getValue());
}
return sBuilder.toString();
}
// NOTE: implementation delegates transaction management to BMT capabilities of jbpm5 TaskServiceSession
// JA Bride: 15 May 2012: looks like taskServiceSession.query(....) no longer manages its own trnxs ... will do so now
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<TaskSummary> getTasksByProcessInstance(Long processInstanceId, Status taskStatus) {
TaskServiceSession taskSession = null;
try {
StringBuilder qBuilder = new StringBuilder("select new org.jbpm.task.query.TaskSummary(t.id, t.taskData.processInstanceId, name.text, subject.text, description.text, t.taskData.status, t.priority, t.taskData.skipable, t.taskData.actualOwner, t.taskData.createdBy, t.taskData.createdOn, t.taskData.activationTime, t.taskData.expirationTime, t.taskData.processId, t.taskData.processSessionId) ");
qBuilder.append("from Task t ");
qBuilder.append("left join t.taskData.createdBy ");
qBuilder.append("left join t.taskData.actualOwner ");
qBuilder.append("left join t.subjects as subject ");
qBuilder.append("left join t.descriptions as description ");
qBuilder.append("left join t.names as name ");
qBuilder.append("where t.taskData.processInstanceId = ");
qBuilder.append(processInstanceId);
if(taskStatus != null) {
qBuilder.append(" and t.taskData.status = '");
qBuilder.append(taskStatus.name());
qBuilder.append("'");
}
taskSession = taskService.createSession();
uTrnx.begin();
List<TaskSummary> taskSummaryList = (List<TaskSummary>)taskSession.query(qBuilder.toString(), 10, 0);
uTrnx.commit();
return taskSummaryList;
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<TaskSummary> getTasksAssignedAsPotentialOwner(String userId, List<String> groupIds, String language, Integer firstResult, Integer maxResults) {
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
return taskSession.getTasksAssignedAsPotentialOwner(userId, groupIds, language, firstResult, maxResults);
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<TaskSummary> getTasksAssignedAsPotentialOwnerByStatusByGroup(String userId, List<String> groupIds, List<Status> statuses, String language, Integer firstResult, Integer maxResults){
this.checkAndSetListOfOrgEntities(groupIds, GROUP);
this.checkAndSetTempOrgEntitySet(userId, USER);
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
return taskSession.getTasksAssignedAsPotentialOwnerByStatusByGroup(userId, groupIds, statuses, language);
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<TaskSummary> getTasksAssignedAsPotentialOwnerByStatus(String userId, List<Status> statuses, String language, Integer firstResult, Integer maxResults){
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
return taskSession.getTasksAssignedAsPotentialOwnerByStatus(userId, statuses, language);
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<TaskSummary> getAssignedTasks(String userId, List<Status> statuses, String language) {
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
//return taskSession.getTasksOwned(userId, statuses, language);
return taskSession.getTasksOwned(userId, language);
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<?> query(String qlString, Integer size, Integer offset) {
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
return taskSession.query(qlString, size, offset);
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
@PostConstruct
public void start() throws Exception {
// 0) set properties for UserGroupCallbackManager
UserGroupCallbackManager callbackMgr = UserGroupCallbackManager.getInstance();
callbackMgr.setCallback(new org.jboss.processFlow.tasks.identity.PFPUserGroupCallback());
callbackMgr.setProperty("disable.all.groups", "true");
log.info("start() just set UserGroupCallbackManager with properties");
// 1) instantiate a deadline handler
EscalatedDeadlineHandler deadlineHandler = null;
if (System.getProperty(ITaskService.DEADLINE_HANDLER) != null) {
deadlineHandler = (EscalatedDeadlineHandler) Class.forName(System.getProperty(ITaskService.DEADLINE_HANDLER)).newInstance();
} else {
throw new RuntimeException("start() need to pass system property = "+ITaskService.DEADLINE_HANDLER);
}
SystemEventListener sEventListener = SystemEventListenerFactory.getSystemEventListener();
// 2) turn on/off the intelligent human task input/output mapping
if (System.getProperty("org.jboss.processFlow.task.enableIntelligentMapping") != null)
enableIntelligentMapping = Boolean.parseBoolean(System.getProperty("org.jboss.processFlow.task.enableIntelligentMapping"));
log.info("start() enableIntelligentMapping = " + enableIntelligentMapping);
// 3) instantiate TaskEventListeners
log.info("TaskEventListeners: " + System.getProperty("org.jboss.processFlow.tasks.TaskEventListeners"));
eventSupport = new PfpTaskEventSupport();
if (System.getProperty("org.jboss.processFlow.tasks.TaskEventListeners") != null) {
String[] listenerClasses = System.getProperty("org.jboss.processFlow.tasks.TaskEventListeners").split("\\s");
for (String lcn : listenerClasses) {
eventSupport.addEventListener((TaskEventListener) Class.forName(lcn).newInstance());
}
}
EntityManager eManager = null;
try {
eManager = humanTaskEMF.createEntityManager();
Query queryObj = eManager.createQuery("from User");
List<User> userList = queryObj.getResultList();
for(User userObj : userList){
this.tempOrgEntitySet.add(userObj.getId());
}
log.info("start() just populated tempOrgEntitySet with following # of orgentity users found in DB: "+userList.size());
}finally {
}
jtaTaskService = new TaskService(jtaHumanTaskEMF, sEventListener, deadlineHandler);
/** 1) since TaskService is using a RESOURCE-LOCAL EMF (for performance reasons), jbpm assumes it can define its own trnx boundaries
* 2) in particular, jbpm human task impl defines its own trnx boundaries when this function creates a jbpm TaskService
* 3) not sure how to disable a JTA trnx in this @PostConstruct function other than to suspend the trnx using the TransactionManager
*/
try {
Transaction suspendedTrnx = tMgr.suspend();
taskService = new TaskService(humanTaskEMF, sEventListener, deadlineHandler);
tMgr.resume(suspendedTrnx);
} catch(Exception x) {
throw new RuntimeException(x);
}
}
@PreDestroy
public void stop() throws Exception {
log.info("stop()");
}
/*
* if faultName != null, then returns a FaultData object with its faultName property set
* otherwise, returns a ContentData object
*/
private ContentData convertTaskVarsToContentData(Map<String, Object> taskVars, String faultName) {
ContentData contentData = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out;
try {
if(faultName != null){
contentData = new FaultData();
((FaultData)contentData).setFaultName(faultName);
}else {
contentData = new ContentData();
}
out = new ObjectOutputStream(bos);
out.writeObject(taskVars);
contentData.setContent(bos.toByteArray());
contentData.setAccessType(org.jbpm.task.AccessType.Inline);
out.close();
return contentData;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void rollbackTrnx() {
try {
if(uTrnx.getStatus() == javax.transaction.Status.STATUS_ACTIVE)
uTrnx.rollback();
}catch(Exception e) {
e.printStackTrace();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public Content getContent(Long contentId){
TaskServiceSession taskSession = null;
try {
taskSession = taskService.createSession();
return taskSession.getContent(contentId);
}catch(Exception x) {
throw new RuntimeException(x);
}finally {
if(taskSession != null)
taskSession.dispose();
}
}
/* populateHashWithTaskContent
- query database for Content object and stream back as a Map
- Long contentid : contentId to be retrieved from database
- String keyName : name of key in returned Map whose value should be the raw content Object
*/
public Map populateHashWithTaskContent(Content contentObj, String keyName) {
Map<String, Object> returnHash = null;
long contentId = contentObj.getId();
if(contentObj != null) {
returnHash = new HashMap<String, Object>();
ByteArrayInputStream bis = new ByteArrayInputStream(contentObj.getContent());
ObjectInputStream in;
try {
in = new ObjectInputStream(bis);
Map<?, ?> contentHash = (Map<?,?>)in.readObject();
in.close();
if(contentHash != null && contentHash.size() > 0) {
for (Map.Entry<?, ?> entry: contentHash.entrySet()) {
if (entry.getKey() instanceof String) {
returnHash.put((String) entry.getKey(), entry.getValue());
} else {
log.warn("populateHashWithTaskContent() content with id ="+contentId+ " includes non-string variable : "+entry.getKey()+" : "+entry.getValue());
}
}
} else {
log.warn("populateHashWithTaskContent() no content variables found for contentId = "+contentId);
}
returnHash.put(keyName, contentHash);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
log.error("populateHashWithTaskContent() returned null content for contentId = "+contentId);
}
return returnHash;
}
/**
* @return the enableIntelligentMapping
*/
public boolean isEnableIntelligentMapping() {
return enableIntelligentMapping;
}
/**
* Expose this method, so that the user can change the setting at runtime
*
* @param enableIntelligentMapping the enableIntelligentMapping to set
*/
public void setEnableIntelligentMapping(boolean enableIntelligentMapping) {
this.enableIntelligentMapping = enableIntelligentMapping;
}
}