Merging DEV/ACTIVITI_INTEGRATION2 to HEAD

25933: Updated activiti jars to 5.3 release + temporarily disabled query-tests for activtiti
	25932: Fixed failing FormServiceImplTest.
	25930: Fixed ClassCastException and NullPointerException when using JBPM and Activiti tasks in Alfresco Explorer.
	25898: Implemented testGetWorkflows() which tests the methods getWorkflows() getActiveWorkflows() and getCompletedWorkflows() for both workflow engines.
	25888: Fixed failing testOutcome.
	25884: Workflow console fully tested using activiti engine + small bugfixes
	25854: Implemented testGetPathProperties on AbstractWorkflowServiceIntegrationTest. This tests getPathProperties for Activiti and JBPM workflow engines.
	25827: Added lifecycle sample for activiti engine
	25801: Added a new WorkflowRestApiTest testTaskInstancesForWorkflowGet. Also fixed issues with ActivitWorkflowEngine.queryTasks() relating to the start task being returned incorrectly.
	25731: Added timer execution test to verify Authentication and Transactional behavior + added license headers to 2 files
	25682: ALF-6902 delete identitylinks + added timer sample + adhoc notify me feature
	25649: Redeploying of parallel activiti WF's turned of (remainder from development)
	25645: Fixing getWorkflows test, using valid date to check "due before"
	25642: Implemented getPathProperties() on ActivitiWorkflowEngine. Also refactored ActivitiTypeConverter to create ActivitiPropertyConverter which uses a WorkflowPropertyHandlerRegistry.
	25636: Fixes for use of Activiti workflows in JSF client
	25615: Added parallel review and approve process (regular + grouped)
	25520: Added REST-test for review workflow and pooled review workflow for both engines
	25471: Added queryTasks test for both engines
	25397: Testing getTimers() on both engines
	25368: Using authorityName instead of name for candidate-group + fixed priority WF vs. Task mixup in start-task
	25335: F96: A form control for LIST constraint based decision properties in Activiti tasks exists
	25324: Added correct sources for current 5.3-SNAPSHOT
	25323: Implemented TaskQuery further and added extra test-coverage + delete/cancel WF now deletes History as well 
	25318: The signal() method on ActivitiWorkflowEngine now returns a WorkflowPath if the signal ended the WorkflowInstance.
	25300: Fixed start workflow form for Activiti pooled review and approve process definition
	25294: Implemented signal() method on ActivitiWorkflowEngine. Currently it returns null if the signal() ended the Workflowinstance.
	25199: Implemented getTimers() + fixed some issues with the outcome prop name + upgraded to activiti 5.3
	25179: Added support for wf:outcome property on Activiti tasks. Now ian arbitrary property can be set as the property from which outcomes are read.
	25152: Refactored the WorkflowServiceImplTest to create an AbstractWorkflowServiceImplTest. Extended this base test with Jbpm and Activiti implementations.
	25134: Fixed workflowPath node issue on start-task + Task variables are no longer flushed to process-instance variables
	25131: ALF-6901 start task completion date + taskListeners using ScriptService instead of activiti's built-in JSR-223
	25105: Implemented getWorkflows() and getCompletedWorkflows() methods on ActivitiWorkflowEngine.
	25098: Mandatory properties are now checked in TaskCompleteListener to allow setting of mandatory properties in end-task listeners before the check is performed + review-pooled used person to set as bpm_assignee + removed duplicate messageService in ActivitiWE
	25089: Merged ActivitiTaskComponent and ActivitiWorkflowComponent into a single class, ActivitiWorkflowEngine.
	25048: Implemented getStartTask(String) method on WorkflowService interface. This gets the start task instance for a given workflow instance Id. Also, modified the start task returned by the ActivitiTaskComponent so that it has the correct title.
	25028: Added WorkflowDefinitionGet webscript and test + tested WorkflowInstanceDelete and fixed WorkflowTaskDefinition bug
	25003: Added getStartTask() method to WorkflowService. Implemented for JBPMEngine and ActivitiTaskComponent.
	24996: Added activiti process-definition for pooled review/approve workflow + new version of activiti 5.2 to allow releasing task to pool agian
	24972: Extended TaskInstanceGet REST-test + upgraded activiti lib due to bug in HistoricTaskInstance
	24934: Refactored package assignment/creation so that it is now mainly performed by the WOrkflowPackageComponent rather than the WorkflowComponent or TaskComponent. Implemented correct package behaviour for Activiti workflow engine.
	24926: Extended TaskInstancePut REST-test + added extra logic for ignoring tasks for deleted/canceled WF's
	24895: Added new tests to FormServiceImplTest to check TaskFormProcessor correctly transitions tasks with both workflow engines.
	24888: Updated WorkflowRestApiTest, enhanced testing of getTaskInstances
	24861: Updated the FormServiceImplTest so that it includes tests for the TaskFormProcessor using both workflow engines.
	24851: Made WorkflowRestApiTest abstract and created a subtype for activiti and jbpm
	24832: Added integration test to FormServiceImplTest to check WorkflowFormProcessor works correctly with both Activiti and Jbpm engines.
	24829: ALF-6195 Pooled actors are now available on completed tasks
	24806: Added null check for parameters variable
	24805: Cleaned up WorkflowRestAPITest to remove deprecated field access.
	24797: ALF-6016 start-task now contains variables, upgraded to activiti 5.2-SNAPSHOT
	24796: Removed accidentaly checked-in folder
	24795: Share project "Catalina-virtual" into "https://svn.alfresco.com/repos/enterprise/alfresco/BRANCHES/DEV/ACTIVITI_INTEGRATION2/root"
	24780: Created Activiti Review & Approve workflow.
	24702: ALF-6201 cleaned up WOFactory.createInstance + removed candidate group from adhoc WF (pooled task)
	24693: ALF-6438 implemented getWorkflowById for completed workflows + added unittest for getWorkflowById from running and complete processes
	24686: ALF-6195 added formKey (typeDefinition name) to variables to make available in history + added test for getTaskById() for a completed task
	24642: ALF-6003 + ALF-6195 Introduced mapping of WorkfloTask from HistoricTaskInstance and it's variables
	24565: ALF-6016 tested getting start task by id + added initial test for taskQuery
	24560: ALF-6217 implemented and tested pooled actors using activiti-candidate user/group
	24520: ALF-6003 ALF-6014 tested + various minor improvements + TaskTypeEvaluator matchin pattern adjusted to be able to use workflow-detail form for activiti start-tasks
	24417: ALF-6003, ALF-6014 Added tasklisteners to set default task props + flush variables when task ends + running script in tasklistener (to set task-variables in script based on wf-props)
	24399: Merged BRANCHES/V3.4 to BRANCHES/DEV/ACTIVITI_INTEGRATION2:
   24396: Fix for ALF-6126: TaskTypeEvaluator uses task-name instead of taskType-name for node-type matching
	24396: Fix for ALF-6126: TaskTypeEvaluator uses task-name instead of taskType-name for node-type matching
	24334: ALF-6015 ALF-6016: Completing a task implemented + using task-local variables
	24125: ALF-5995 properties passed when starting workflow are stored
	24111: ALF-5974, ALF-5972 Task-update implemented + capturing company-home, initiator and initiator home implemented and tested
	23891: Fixing failing tests in ActivitiWorkflowComponentTest
	23776: Upgraded activiti to 5.0-SNAPSHOT + Introduced error-handling using messages + initial support for HistoricActivity/ProcessInstances
	23600: Removing unneeded folder that was introduced accidentally when reattaching SVN to eclipse
	23599: 
	23298: 
	23250: Rolling back revisions 23212 to 23113 to fix issue with logging in.
	23212: Trying to fix the problem with logging in.
	23200: Added Form config for the Activiti Adhoc Process. Fixed failing tests in ActivitiWorkflowComponentTest.
	23197: Fixed failing tests in WorkflowServiceeImplTest. Updated Activiti jars in 3rdParty.
	23191: Adding Activiti Ahodc Workflow to Workflow Deployer to demonstrate Activiti Workflow/Task..Component integration with UI.
	23129: Copy Activiti JAR files when building WAR and doing exploded deploy
	23113: Implemented ActivitiTaskComponent.getTaskById()
	23079: Created ActivitiTaskComponent and started to implement various task getter methods.
	22890: Added testStartTask to WorkflowServiceImplTests to test that the start task functionality works as required by the Workflow UI.
	22889: Added testStartTask to WorkflowServiceImplTests to test that the start task functionality works as required by the Workflow UI.
	22888: Added testStartTask to WorkflowServiceImplTests to test that the start task functionality works as required by the Workflow UI.
	22887: Added testStartTask to WorkflowServiceImplTests to test that the start task functionality works as required by the Workflow UI.
	22769: Added more functionality to ActivitiWorkflowComponent.startWorkflow.
	22759: Implemented more methods for getting WorkflowDefinitions on ActivitiWorkflowComponent. Also added more functionality to the startWorkflow method.
	22758: Implemented more methods for getting WorkflowDefinitions on ActivitiWorkflowComponent. Also added more functionality to the startWorkflow method.
	22672: Created ActivitiWorkflowComponent and implemented deployDefinition(), undeployDefinition() and isDefinitionDeployed() methods.
	22659: Replacing Activiti Jars
	22359: Added tests to check rollback of transactions in Activiti.
	22358: Got some simple transaction tests working. These test transaction visibility is working properly and also check that rollbacks work correctly.
	22342: Created a patch to add the Activiti tables in.
	22333: Added some simple tests to check if Activiti works using a DataSource and TransactionManager.
	22332: Updating Activiti to the beta release.
	22286: Added Activiti and its dependencies to 3rd Party libraries. Updated JUnit jar to 4.8. Created package org/alfresco/repo/workflow/activiti and added some simple tests to check Activiti integration.
	21879: Branch to develop support for Activiti workflow engine.
	23264: Creating branch ACTIVITI_INTEGRATION2 to merge HEAD back into the existing ACTIVITI_INTEGGRATION branch.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@25984 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
N Smith
2011-03-02 17:12:35 +00:00
parent 8a87e29067
commit 2980cf9f78
130 changed files with 15410 additions and 1988 deletions

View File

@@ -0,0 +1,438 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authority.AuthorityDAO;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.activiti.variable.ScriptNodeVariableType;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowDeployment;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
/**
* @since 4.0
* @author Nick Smith
* @author Frederik Heremans
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:test-database-context.xml",
"classpath:activiti/test-activiti-component-context.xml",
"classpath:alfresco/activiti-context.xml"})
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
@Transactional
public class AbstractActivitiComponentTest
{
protected static final String TEST_GROUP = "GROUP_testGroup";
protected static final String TEST_USER = "testUser";
protected static final String TEST_TASK_DEF = "activiti/testTransaction.bpmn20.xml";
protected static final String TEST_TASK_KEY = "testTask";
protected static final String TEST_ADHOC_DEF = "activiti/testAdhoc.bpmn20.xml";
protected static final String TEST_SIGNALLING_DEF = "activiti/testSignalling.bpmn20.xml";
protected static final String TEST_REVIEW_DEF = "activiti/testReview.bpmn20.xml";
protected static final String TEST_ADHOC_KEY = "testAdhoc";
protected static final String TEST_JOB_KEY = "testAdhoc";
protected static final String TEST_JOB_DEF = "activiti/testJob.bpmn20.xml";
protected static final String XML = MimetypeMap.MIMETYPE_XML;
@Autowired
protected ProcessEngine processEngine;
@Resource(name="activitiWorkflowEngine")
protected ActivitiWorkflowEngine workflowEngine;
@Resource(name="activitiRuntimeService")
protected RuntimeService runtime;
@Resource(name="activitiRepositoryService")
protected RepositoryService repo;
@Resource(name="activitiTaskService")
protected TaskService taskService;
@Resource(name="activitiHistoryService")
protected HistoryService historyService;
@Resource(name="activitiManagementService")
protected ManagementService managementService;
@Resource
protected MessageService messageService;
@Resource
protected TenantService tenantService;
@Resource(name="NamespaceService")
protected NamespaceService namespaceService;
@Resource(name="DictionaryService")
protected DictionaryService dictionaryService;
@Resource(name="NodeService")
protected NodeService nodeService;
@Resource(name="searchService")
protected SearchService unprotectedSearchService;
@Resource
protected PermissionService permissionService;
@Resource(name="PersonService")
protected PersonService personService;
@Resource
protected AuthorityDAO authorityDAO;
@Resource
protected ServiceRegistry serviceRegistry;
protected static final NodeRef rootNode = new NodeRef("workspace://root/");
protected static final NodeRef companyHomeNode = new NodeRef("workspace://companyHome/");
protected static final NodeRef adminPersonNode = new NodeRef("workspace://admin/");
protected static final NodeRef adminHomeNode = new NodeRef("workspace://admin-home/");
protected static final NodeRef testUserNode = new NodeRef("workspace://testUser/");
protected static final NodeRef testGroupNode = new NodeRef("workspace://testGroup/");
protected static final NodeRef testWorkflowPackage = new NodeRef("workspace://testPackage/");
protected static final NodeRef testWorkflowContext = new NodeRef("workspace://testContext/");
protected WorkflowDefinition deployTestTaskDefinition()
{
return deployDefinition(TEST_TASK_DEF);
}
protected WorkflowDefinition deployTestAdhocDefinition()
{
return deployDefinition(TEST_ADHOC_DEF);
}
protected WorkflowDefinition deployTestSignallingDefinition()
{
return deployDefinition(TEST_SIGNALLING_DEF);
}
protected WorkflowDefinition deployTestJobDefinition()
{
return deployDefinition(TEST_JOB_DEF);
}
protected WorkflowDefinition deployDefinition(String resource)
{
InputStream input = getInputStream(resource);
WorkflowDeployment deployment = workflowEngine.deployDefinition(input, XML);
WorkflowDefinition definition = deployment.getDefinition();
return definition;
}
protected InputStream getInputStream(String resource)
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream input= classLoader.getResourceAsStream(resource);
return input;
}
@Before
public void setUp() throws Exception
{
mockTenantService();
mockNamespaceService();
mockDictionaryService();
mockNodeService();
mockSearchService();
mockPermissionService();
mockPersonService();
mockAuthorityDAO();
mockServiceRegistry();
workflowEngine.setCompanyHomeStore("workspace://SpacesStore");
workflowEngine.setCompanyHomePath("spaces.company_home.childname");
// Also add custom type
// TODO: Should come from configuration
ScriptNodeVariableType variableType = new ScriptNodeVariableType();
variableType.setServiceRegistry(serviceRegistry);
// ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getVariableTypes().addType(variableType, 1);
// Use util to set current user to admin
AuthenticationUtil.setFullyAuthenticatedUser("admin");
workflowEngine.afterPropertiesSet();
}
/**
*
*/
private void mockServiceRegistry()
{
// Set all services on the mocked Serviceregistry, injected by spring
when(serviceRegistry.getNodeService()).thenReturn(nodeService);
when(serviceRegistry.getDictionaryService()).thenReturn(dictionaryService);
when(serviceRegistry.getPermissionService()).thenReturn(permissionService);
}
private void mockAuthorityDAO()
{
when(authorityDAO.authorityExists(TEST_USER)).thenReturn(true);
when(authorityDAO.authorityExists(TEST_GROUP)).thenReturn(true);
// We will return dummy node refs to authorities testUser and testGroup
when(authorityDAO.getAuthorityNodeRefOrNull(TEST_USER)).thenReturn(testUserNode);
when(authorityDAO.getAuthorityNodeRefOrNull(TEST_GROUP)).thenReturn(testGroupNode);
}
private void mockPersonService()
{
// Checking if admin exists
when(personService.personExists("admin")).thenReturn(true);
// Return reference to Admin person
when(personService.getPerson("admin")).thenReturn(adminPersonNode);
// Check if test-user exists
when(personService.personExists(TEST_USER)).thenReturn(true);
when(personService.getPerson(TEST_USER)).thenReturn(testUserNode);
}
private void mockPermissionService()
{
// Allow permission on all nodes
when(permissionService.hasPermission((NodeRef) any(), anyString())).thenReturn(AccessStatus.ALLOWED);
}
private void mockNodeService()
{
// Return root store
when(nodeService.getRootNode(new StoreRef("workspace://SpacesStore"))).thenReturn(rootNode);
// Return company home and it's type
when(nodeService.exists(companyHomeNode)).thenReturn(true);
when(nodeService.getType((NodeRef) any())).thenReturn(QName.createQName("cm:folder"));
// Return admin's home property
when(nodeService.getProperty(adminPersonNode, ContentModel.PROP_HOMEFOLDER)).thenReturn(adminHomeNode);
// Return testUser and testGroup types
when(nodeService.getType(testUserNode)).thenReturn(ContentModel.TYPE_PERSON);
when(nodeService.getType(testGroupNode)).thenReturn(ContentModel.TYPE_AUTHORITY);
}
private void mockSearchService()
{
// When searching for company home, return single node
when(unprotectedSearchService.selectNodes(rootNode, "spaces.company_home.childname", null, namespaceService, false)).thenReturn(Arrays.asList(companyHomeNode));
}
/**
* @return
*/
private void mockDictionaryService()
{
Mockito.reset(dictionaryService);
when(dictionaryService.getType((QName)any())).thenAnswer(new Answer<TypeDefinition>()
{
@Override
public TypeDefinition answer(InvocationOnMock invocation) throws Throwable
{
QName name = (QName) invocation.getArguments()[0];
TypeDefinition type = mock(TypeDefinition.class);
when(type.getName()).thenReturn(name);
return type;
}
});
when(dictionaryService.getAnonymousType((QName)any())).thenAnswer(new Answer<TypeDefinition>()
{
@Override
public TypeDefinition answer(InvocationOnMock invocation) throws Throwable
{
QName name = (QName) invocation.getArguments()[0];
TypeDefinition type = mock(TypeDefinition.class);
when(type.getName()).thenReturn(name);
// Add a default value
Map<QName, PropertyDefinition> props = new HashMap<QName, PropertyDefinition>();
QName qname = QName.createQName("http://test", "myProp");
DataTypeDefinition qNameDef = mock(DataTypeDefinition.class);
when(qNameDef.getName()).thenReturn(DataTypeDefinition.QNAME);
when(qNameDef.getJavaClassName()).thenReturn(QName.class.getName());
// Create dummy property type
DataTypeDefinition def = mock(DataTypeDefinition.class);
when(def.getName()).thenReturn(DataTypeDefinition.TEXT);
when(def.getJavaClassName()).thenReturn(String.class.getName());
// Create dummy property definition
PropertyDefinition prop = mock(PropertyDefinition.class);
when(prop.getName()).thenReturn(qname);
when(prop.getDefaultValue()).thenReturn("Default value");
when(prop.getDataType()).thenReturn(def);
// Also add description
PropertyDefinition description = mock(PropertyDefinition.class);
when(description.getName()).thenReturn(WorkflowModel.PROP_DESCRIPTION);
when(description.getDataType()).thenReturn(def);
// Add outcome property name
PropertyDefinition outcomePropertyName = mock(PropertyDefinition.class);
when(outcomePropertyName.getName()).thenReturn(WorkflowModel.PROP_OUTCOME_PROPERTY_NAME);
when(outcomePropertyName.getDataType()).thenReturn(qNameDef);
when(outcomePropertyName.getDefaultValue()).thenReturn("{http://test}testOutcome");
// Add outcome property
PropertyDefinition outcomeProperty = mock(PropertyDefinition.class);
when(outcomeProperty.getName()).thenReturn(QName.createQName("http://test", "testOutcome"));
when(outcomeProperty.getDataType()).thenReturn(def);
props.put(qname, prop);
props.put(WorkflowModel.PROP_DESCRIPTION, description);
props.put(WorkflowModel.PROP_OUTCOME_PROPERTY_NAME, outcomePropertyName);
props.put(QName.createQName("http://test", "testOutcome"), outcomeProperty);
when(type.getProperties()).thenReturn(props);
return type;
}
});
// Mock type inheritance for person nodes
when(dictionaryService.isSubClass(ContentModel.TYPE_PERSON, ContentModel.TYPE_PERSON)).thenReturn(true);
}
private void mockNamespaceService()
{
namespaceService.registerNamespace(NamespaceService.BPM_MODEL_PREFIX, NamespaceService.BPM_MODEL_1_0_URI);
namespaceService.registerNamespace(NamespaceService.DEFAULT_PREFIX, NamespaceService.DEFAULT_URI);
namespaceService.registerNamespace(NamespaceService.WORKFLOW_MODEL_PREFIX, NamespaceService.WORKFLOW_MODEL_1_0_URI);
namespaceService.registerNamespace("test", "http://test");
}
private void mockTenantService()
{
when(tenantService.getBaseName(anyString())).thenAnswer(new Answer<String>()
{
public String answer(InvocationOnMock invocation) throws Throwable
{
Object arg= invocation.getArguments()[0];
return (String) arg;
}
});
}
@After
public void tearDown()
{
List<ProcessDefinition> defs = repo.createProcessDefinitionQuery()
.processDefinitionKey(TEST_TASK_KEY)
.list();
HashSet<String> deployments = new HashSet<String>(defs.size());
for (ProcessDefinition def : defs)
{
deployments.add(def.getDeploymentId());
}
for (String deployment : deployments)
{
List<ProcessDefinition> definitions = repo.createProcessDefinitionQuery()
.deploymentId(deployment)
.list();
for (ProcessDefinition def : definitions)
{
killInstances(def);
}
repo.deleteDeployment(deployment);
}
}
public String mapQNameToName(QName name)
{
// NOTE: Map names using old conversion scheme (i.e. : -> _) as well as
// new scheme (i.e. } -> _)
// NOTE: Use new scheme
String nameStr = name.toPrefixString(this.namespaceService);
if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':'))
{
return nameStr.replace(':', '}');
}
return nameStr.replace(':', '_');
}
private void killInstances(ProcessDefinition def)
{
List<ProcessInstance> instances = runtime.createProcessInstanceQuery()
.processDefinitionId(def.getId())
.list();
for (ProcessInstance instance : instances)
{
runtime.deleteProcessInstance(instance.getId(), "For test");
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
/**
* @since 3.5
* @author Nick Smith
*
*/
public interface ActivitiConstants
{
public static final String ENGINE_ID = "activiti";
public static final String NODE_NAME = "name";
public static final String NODE_DESCRIPTION = "documentation";
public static final String NODE_TYPE= "type";
public static final String PROP_START_TASK_END_DATE = "_startTaskCompleted";
public static final String START_TASK_PREFIX = "start";
public static final String DEFAULT_TRANSITION_NAME = "Next";
public static final String START_TASK_PROPERTY_PREFIX = "_start_";
public static final String USER_TASK_NODE_TYPE = "userTask";
public static final String PROP_TASK_FORM_KEY = "taskFormKey";
public static final String PROP_POOLED_ACTORS_HISTORY = "pooledActorsHistory";
public static final String DELETE_REASON_DELETED = "deleted";
public static final String DELETE_REASON_CANCELLED = "cancelled";
public static final String SERVICE_REGISTRY_BEAN_KEY = "services";
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.util.Collection;
import java.util.List;
import org.alfresco.repo.jscript.ScriptNode;
import org.alfresco.repo.workflow.AbstractWorkflowNodeConverter;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* @author Nick Smith
*
*/
public class ActivitiNodeConverter extends AbstractWorkflowNodeConverter
{
private final ServiceRegistry serviceRegistry;
public ActivitiNodeConverter(ServiceRegistry serviceRegistry)
{
this.serviceRegistry = serviceRegistry;
}
/**
* {@inheritDoc}
*/
@Override
public Object convertNode(NodeRef node)
{
return new ActivitiScriptNode(node, serviceRegistry);
}
/**
* {@inheritDoc}
*/
@Override
public List<? extends Object> convertNodes(Collection<NodeRef> values)
{
ActivitiScriptNodeList results = new ActivitiScriptNodeList();
for (NodeRef node : values)
{
results.add(new ActivitiScriptNode(node, serviceRegistry));
}
return results;
}
/**
* {@inheritDoc}
*/
@Override
public NodeRef convertToNode(Object toConvert)
{
return ((ScriptNode)toConvert).getNodeRef();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSupported(Object object)
{
if(object == null)
{
return false;
}
if(object instanceof ActivitiScriptNode)
{
return true;
}
if(object instanceof ActivitiScriptNodeList)
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.Serializable;
import java.util.Date;
import org.alfresco.repo.jscript.ScriptNode;
import org.alfresco.repo.workflow.jbpm.JBPMNode;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.mozilla.javascript.Scriptable;
/**
* Scriptable Node suitable for Activti Beanshell access
*
* @author Frederik Heremans
*/
public class ActivitiScriptNode extends ScriptNode
{
private static final long serialVersionUID = -826970280203254365L;
/**
* Construct
*
* @param nodeRef node reference
* @param services services
*/
public ActivitiScriptNode(NodeRef nodeRef, ServiceRegistry services)
{
super(nodeRef, services, null);
}
/**
* {@inheritDoc}
*/
@Override
protected NodeValueConverter createValueConverter()
{
return new JBPMNodeConverter();
}
/**
* Value converter for beanshell. Dates should be handled differenty since
* default conversion uses top-level scope which is sometimes missing.
*/
private class JBPMNodeConverter extends NodeValueConverter
{
@Override
public Serializable convertValueForRepo(Serializable value)
{
if (value instanceof Date)
{
return value;
}
else
{
return super.convertValueForRepo(value);
}
}
@Override
public Serializable convertValueForScript(ServiceRegistry serviceRegistry, Scriptable theScope, QName qname, Serializable value)
{
if (value instanceof NodeRef)
{
return new JBPMNode(((NodeRef)value), serviceRegistry);
}
else if (value instanceof Date)
{
return value;
}
else
{
return super.convertValueForScript(serviceRegistry, theScope, qname, value);
}
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.util.ArrayList;
import java.util.List;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* List of {@link ActivitiScriptNode}s.
*
* @author Frederik Heremans
*/
public class ActivitiScriptNodeList extends ArrayList<ActivitiScriptNode>
{
private static final long serialVersionUID = 5177463364573735290L;
public List<NodeRef> getNodeReferences()
{
// Extract all node references
List<NodeRef> nodeRefs = new ArrayList<NodeRef>();
for(ActivitiScriptNode scriptNode : this)
{
nodeRefs.add(scriptNode.getNodeRef());
}
return nodeRefs;
}
@Override
public int size() {
return super.size();
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.IOException;
import junit.framework.TestCase;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.springframework.core.io.ClassPathResource;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiSmokeTest extends TestCase
{
public void testDeploy() throws Exception
{
ProcessEngine engine = buildProcessEngine();
RepositoryService repoService = engine.getRepositoryService();
Deployment deployment = deployDefinition(repoService);
assertNotNull(deployment);
RuntimeService runtimeService = engine.getRuntimeService();
try
{
ProcessInstance instance = runtimeService.startProcessInstanceByKey("testTask");
assertNotNull(instance);
String instanceId = instance.getId();
ProcessInstance instanceInDb = findProcessInstance(runtimeService, instanceId);
assertNotNull(instanceInDb);
runtimeService.deleteProcessInstance(instanceId, "");
}
finally
{
// List<Deployment> deployments = repoService.createDeploymentQuery().list();
// for (Deployment deployment2 : deployments)
// {
// repoService.deleteDeployment(deployment2.getId());
// }
repoService.deleteDeployment(deployment.getId());
}
}
private Deployment deployDefinition(RepositoryService repoService) throws IOException
{
ClassPathResource resource = new ClassPathResource("org/alfresco/repo/workflow/activiti/testTransaction.bpmn20.xml");
Deployment deployment = repoService.createDeployment()
.addInputStream(resource.getFilename(), resource.getInputStream())
.deploy();
return deployment;
}
private ProcessEngine buildProcessEngine()
{
String properties = "org/alfresco/repo/workflow/activiti/activiti.cfg.xml";
ProcessEngine engine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(properties).buildProcessEngine();
return engine;
}
private ProcessInstance findProcessInstance(RuntimeService runtimeService, String instanceId)
{
ProcessInstance instanceInDb = runtimeService.createProcessInstanceQuery()
.processInstanceId(instanceId)
.singleResult();
return instanceInDb;
}
}

View File

@@ -0,0 +1,260 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.InputStream;
import junit.framework.TestCase;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @since 3.4
* @author Nick Smith
*
*/
public class ActivitiSpringTest extends TestCase
{
private static final QName PROP_CHECK_VALUE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "check_value");
private static final String PROC_DEF_KEY = "testTask";
private static final String ACTIVITI_CONTEXT = "classpath:alfresco/activiti-context.xml";
private static final QName ASPECT = ContentModel.ASPECT_ATTACHABLE;
private RuntimeService runtime;
private RepositoryService repo;
private Deployment deployment;
private AuthenticationComponent authenticationComponent;
private NodeService nodeService;
private RetryingTransactionHelper txnHelper;
private NodeRef workingNodeRef;
public void testSmoke() throws Exception
{
assertNotNull(runtime);
ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY);
assertNotNull(instance);
String instanceId = instance.getId();
ProcessInstance instanceInDb = findProcessInstance(instanceId);
assertNotNull(instanceInDb);
runtime.deleteProcessInstance(instance.getId(), "");
assertNotNull(instance);
}
/**
* Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService.
* Check that the process instance was rolled back.
*/
public void testRollbackFromAlfresco()
{
RetryingTransactionCallback<String> callback = new RetryingTransactionCallback<String>()
{
public String execute() throws Throwable
{
ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY);
String id = instance.getId();
try
{
blowUp();
}
catch (InvalidNodeRefException e)
{
// Expected, but absorbed
}
return id;
}
};
String id = txnHelper.doInTransaction(callback);
ProcessInstance instance = findProcessInstance(id);
if(instance!=null)
{
runtime.deleteProcessInstance(id, "For test");
fail("The process instance creation should have been rolled back!");
}
}
/**
* Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService.
* Check that the process instance was rolled back.
*/
public void testRollbackFromActiviti()
{
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
nodeService.addAspect(workingNodeRef, ASPECT, null);
assertTrue("The node should have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT));
try
{
runtime.signal("Fake Id");
fail("Should throw an Exception here!");
}
catch (ActivitiException e)
{
// Expected, but absorbed
}
return null;
}
};
txnHelper.doInTransaction(callback);
assertFalse("The node should not have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT));
}
/**
* Checks nesting of two transactions with <code>requiresNew == true</code>
*/
public void testNestedWithoutPropogation()
{
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY);
final String id = instance.getId();
ProcessInstance instanceInDb = findProcessInstance(id);
assertNotNull("Can't read process instance in same transaction!", instanceInDb);
RetryingTransactionCallback<Void> callbackInner = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
ProcessInstance instanceInDb2 = findProcessInstance(id);
assertNull("Should not be able to read process instance in inner transaction!", instanceInDb2);
return null;
}
};
try
{
txnHelper.doInTransaction(callbackInner, false, true);
return null;
}
finally
{
runtime.deleteProcessInstance(id, "FOr test");
}
}
};
txnHelper.doInTransaction(callback);
}
private Long blowUp()
{
NodeRef invalidNodeRef = new NodeRef(workingNodeRef.getStoreRef(), "BOGUS");
nodeService.setProperty(invalidNodeRef, PROP_CHECK_VALUE, null);
fail("Expected to generate an InvalidNodeRefException");
return null;
}
private ProcessInstance findProcessInstance(String instanceId)
{
return runtime.createProcessInstanceQuery()
.processInstanceId(instanceId)
.singleResult();
}
/**
* {@inheritDoc}
*/
@Override
protected void setUp() throws Exception
{
ConfigurableApplicationContext appContext = loadContext();
this.repo = (RepositoryService) appContext.getBean("activitiRepositoryService");
this.runtime = (RuntimeService) appContext.getBean("activitiRuntimeService");
ServiceRegistry serviceRegistry = (ServiceRegistry) appContext.getBean(ServiceRegistry.SERVICE_REGISTRY);
authenticationComponent = (AuthenticationComponent) appContext.getBean("authenticationComponent");
TransactionService transactionService = serviceRegistry.getTransactionService();
nodeService = serviceRegistry.getNodeService();
txnHelper = transactionService.getRetryingTransactionHelper();
// authenticate
authenticationComponent.setSystemUserAsCurrentUser();
StoreRef storeRef = nodeService.createStore(
StoreRef.PROTOCOL_WORKSPACE,
"test-" + getName() + "-" + System.currentTimeMillis());
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
// Create a node to work on
workingNodeRef = nodeService.createNode(
rootNodeRef,
ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()),
ContentModel.TYPE_CMOBJECT).getChildRef();
String resource = "org/alfresco/repo/workflow/activiti/testTransaction.bpmn20.xml";
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream input= classLoader.getResourceAsStream(resource);
this.deployment = repo.createDeployment()
.addInputStream(resource, input)
.deploy();
}
private String[] getConfigLocations()
{
String[] defaultLocations = ApplicationContextHelper.CONFIG_LOCATIONS;
Object[] locations = ArrayUtils.add(defaultLocations, ACTIVITI_CONTEXT);
return (String[]) locations;
}
private ConfigurableApplicationContext loadContext() throws Exception
{
String[] locations = getConfigLocations();
return new ClassPathXmlApplicationContext(locations);
}
/**
* {@inheritDoc}
*/
@Override
protected void tearDown() throws Exception
{
try{
repo.deleteDeployment(deployment.getId());
authenticationComponent.clearCurrentSecurityContext();
}
catch (Exception e)
{
// Do Nothing }
}
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.InputStream;
import junit.framework.TestCase;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
/**
* @since 3.4
* @author Nick Smith
*
*/
public class ActivitiSpringTransactionTest extends TestCase
{
private static final QName PROP_CHECK_VALUE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "check_value");
private static final String PROC_DEF_KEY = "testTask";
private static final QName ASPECT = ContentModel.ASPECT_ATTACHABLE;
private RuntimeService runtime;
private RepositoryService repo;
private Deployment deployment;
private AuthenticationComponent authenticationComponent;
private NodeService nodeService;
private RetryingTransactionHelper txnHelper;
private NodeRef workingNodeRef;
public void testSmoke() throws Exception
{
assertNotNull(runtime);
ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY);
assertNotNull(instance);
String instanceId = instance.getId();
ProcessInstance instanceInDb = findProcessInstance(instanceId);
assertNotNull(instanceInDb);
runtime.deleteProcessInstance(instance.getId(), "");
assertNotNull(instance);
}
/**
* Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService.
* Check that the process instance was rolled back.
*/
public void testRollbackFromAlfresco()
{
RetryingTransactionCallback<String> callback = new RetryingTransactionCallback<String>()
{
public String execute() throws Throwable
{
ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY);
String id = instance.getId();
try
{
blowUp();
}
catch (InvalidNodeRefException e)
{
// Expected, but absorbed
}
return id;
}
};
String id = txnHelper.doInTransaction(callback);
ProcessInstance instance = findProcessInstance(id);
if(instance!=null)
{
runtime.deleteProcessInstance(id, "For test");
fail("The process instance creation should have been rolled back!");
}
}
/**
* Start a process and then trigger a rollback by throwing an exception in Alfresco NodeService.
* Check that the process instance was rolled back.
*/
public void testRollbackFromActiviti()
{
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
nodeService.addAspect(workingNodeRef, ASPECT, null);
assertTrue("The node should have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT));
try
{
runtime.signal("Fake Id");
fail("Should throw an Exception here!");
}
catch (ActivitiException e)
{
// Expected, but absorbed
}
return null;
}
};
txnHelper.doInTransaction(callback);
assertFalse("The node should not have the aspect!", nodeService.hasAspect(workingNodeRef, ASPECT));
}
/**
* Checks nesting of two transactions with <code>requiresNew == true</code>
*/
public void testNestedWithoutPropogation()
{
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
ProcessInstance instance = runtime.startProcessInstanceByKey(PROC_DEF_KEY);
final String id = instance.getId();
ProcessInstance instanceInDb = findProcessInstance(id);
assertNotNull("Can't read process instance in same transaction!", instanceInDb);
RetryingTransactionCallback<Void> callbackInner = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
ProcessInstance instanceInDb2 = findProcessInstance(id);
assertNull("Should not be able to read process instance in inner transaction!", instanceInDb2);
return null;
}
};
try
{
txnHelper.doInTransaction(callbackInner, false, true);
return null;
}
finally
{
runtime.deleteProcessInstance(id, "FOr test");
}
}
};
txnHelper.doInTransaction(callback);
}
private Long blowUp()
{
NodeRef invalidNodeRef = new NodeRef(workingNodeRef.getStoreRef(), "BOGUS");
nodeService.setProperty(invalidNodeRef, PROP_CHECK_VALUE, null);
fail("Expected to generate an InvalidNodeRefException");
return null;
}
private ProcessInstance findProcessInstance(String instanceId)
{
return runtime.createProcessInstanceQuery()
.processInstanceId(instanceId)
.singleResult();
}
/**
* {@inheritDoc}
*/
@Override
protected void setUp() throws Exception
{
ApplicationContext appContext = ApplicationContextHelper.getApplicationContext();
this.repo = (RepositoryService) appContext.getBean("activitiRepositoryService");
this.runtime = (RuntimeService) appContext.getBean("activitiRuntimeService");
ServiceRegistry serviceRegistry = (ServiceRegistry) appContext.getBean(ServiceRegistry.SERVICE_REGISTRY);
authenticationComponent = (AuthenticationComponent) appContext.getBean("authenticationComponent");
TransactionService transactionService = serviceRegistry.getTransactionService();
nodeService = serviceRegistry.getNodeService();
txnHelper = transactionService.getRetryingTransactionHelper();
// authenticate
authenticationComponent.setSystemUserAsCurrentUser();
StoreRef storeRef = nodeService.createStore(
StoreRef.PROTOCOL_WORKSPACE,
"test-" + getName() + "-" + System.currentTimeMillis());
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
// Create a node to work on
workingNodeRef = nodeService.createNode(
rootNodeRef,
ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()),
ContentModel.TYPE_CMOBJECT).getChildRef();
String resource = "activiti/testTransaction.bpmn20.xml";
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream input= classLoader.getResourceAsStream(resource);
this.deployment = repo.createDeployment()
.addInputStream(resource, input)
.deploy();
}
/**
* {@inheritDoc}
*/
@Override
protected void tearDown() throws Exception
{
try{
repo.deleteDeployment(deployment.getId());
authenticationComponent.clearCurrentSecurityContext();
}
catch (Exception e)
{
// Do Nothing }
}
}
}

View File

@@ -0,0 +1,652 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.history.HistoricVariableUpdate;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskQuery;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.QName;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* @since 4.0
* @author Nick Smith
* @author Frederik Heremans
*/
public class ActivitiTaskComponentTest extends AbstractActivitiComponentTest
{
private WorkflowDefinition workflowDef;
@Test
public void testGetStartTask()
{
try
{
workflowEngine.getStartTask("Foo");
fail("Should blow up if Id is wrong format!");
}
catch(WorkflowException e)
{
// Do Nothing
}
WorkflowTask result = workflowEngine.getStartTask(ActivitiConstants.ENGINE_ID + "$Foo");
assertNull("Should not find any result for fake (but valid) Id.", result);
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
String comment = "Start task description";
params.put(WorkflowModel.PROP_COMMENT, comment);
params.put(WorkflowModel.PROP_PRIORITY, 1 );
Date dueDate = new Date();
params.put(WorkflowModel.PROP_DUE_DATE, dueDate );
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), params);
String instanceId = path.getInstance().getId();
WorkflowTask task = workflowEngine.getStartTask(instanceId);
assertNotNull("Task shoudl exist!", task);
String localId = ActivitiConstants.START_TASK_PREFIX+BPMEngineRegistry.getLocalId(instanceId);
String taskId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, localId);
assertEquals("Start Task Id is wrong", taskId, task.getId());
assertEquals("The start task path is wrong!", path.getId(), task.getPath().getId());
TypeDefinition definition = task.getDefinition().getMetadata();
assertNotNull(definition);
String name = definition.getName().toPrefixString(namespaceService).replace(':', '_');
assertEquals("bpm_foo", name);
assertEquals(name, task.getName());
assertEquals(name, task.getTitle());
assertEquals(name, task.getDescription());
assertEquals(WorkflowTaskState.IN_PROGRESS, task.getState());
assertEquals(name, task.getDescription());
// Check start task properties populated.
Map<QName, Serializable> properties = task.getProperties();
assertEquals(comment, properties.get(WorkflowModel.PROP_COMMENT));
assertEquals(1, properties.get(WorkflowModel.PROP_PRIORITY));
assertEquals(dueDate, properties.get(WorkflowModel.PROP_DUE_DATE));
// Check start task after task is completed.
task = workflowEngine.endTask(task.getId(), null);
assertEquals("Start Task Id is wrong", taskId, task.getId());
assertEquals("The start task path is wrong!", path.getId(), task.getPath().getId());
definition = task.getDefinition().getMetadata();
assertNotNull(definition);
name = definition.getName().toPrefixString(namespaceService).replace(':', '_');
assertEquals("bpm_foo", name);
assertEquals(name, task.getName());
assertEquals(name, task.getTitle());
assertEquals(name, task.getDescription());
assertEquals(WorkflowTaskState.COMPLETED, task.getState());
assertEquals(name, task.getDescription());
// Check start task properties populated.
properties = task.getProperties();
assertEquals(comment, properties.get(WorkflowModel.PROP_COMMENT));
assertEquals(1, properties.get(WorkflowModel.PROP_PRIORITY));
assertEquals(dueDate, properties.get(WorkflowModel.PROP_DUE_DATE));
// Check start task for historic process.
workflowEngine.cancelWorkflow(instanceId);
task = workflowEngine.getStartTask(instanceId);
assertNull(task);
// assertEquals("Start Task Id is wrong", taskId, task.getId());
//
// assertEquals("The start task path is wrong!", path.getId(), task.getPath().getId());
// definition = task.getDefinition().getMetadata();
// assertNotNull(definition);
// name = definition.getName().toPrefixString(namespaceService).replace(':', '_');
// assertEquals("bpm_foo", name);
//
// assertEquals(name, task.getName());
// assertEquals(name, task.getTitle());
// assertEquals(name, task.getDescription());
// assertEquals(WorkflowTaskState.COMPLETED, task.getState());
// assertEquals(name, task.getDescription());
//
// // Check start task properties populated.
// properties = task.getProperties();
// assertEquals(comment, properties.get(WorkflowModel.PROP_COMMENT));
// assertEquals(1, properties.get(WorkflowModel.PROP_PRIORITY));
// assertEquals(dueDate, properties.get(WorkflowModel.PROP_DUE_DATE));
}
@Test
public void testGetTaskById() throws Exception
{
try
{
workflowEngine.getTaskById("Foo");
fail("Should blow up if Id is wrong format!");
}
catch(WorkflowException e)
{
// Do Nothing
}
WorkflowTask result = workflowEngine.getTaskById(ActivitiConstants.ENGINE_ID + "$Foo");
assertNull("Should not find any result for fake (but valid) Id.", result);
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap<QName, Serializable>());
Task task = taskService.createTaskQuery()
.executionId(BPMEngineRegistry.getLocalId(path.getId()))
.singleResult();
assertNotNull("Task shoudl exist!", task);
String taskId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, task.getId());
WorkflowTask wfTask = workflowEngine.getTaskById(taskId);
assertNotNull(wfTask);
}
@Test
public void testGetStartTaskById() throws Exception
{
WorkflowTask result = workflowEngine.getTaskById(ActivitiConstants.ENGINE_ID + "$Foo");
assertNull("Should not find any result for fake (but valid) Id.", result);
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap<QName, Serializable>());
Task task = taskService.createTaskQuery()
.executionId(BPMEngineRegistry.getLocalId(path.getId()))
.singleResult();
// A start task should be available for the process instance
String startTaskId = ActivitiConstants.START_TASK_PREFIX + task.getProcessInstanceId();
String taskId = createGlobalId(startTaskId);
WorkflowTask wfTask = workflowEngine.getTaskById(taskId);
assertNotNull(wfTask);
assertEquals(createGlobalId(task.getProcessInstanceId()), wfTask.getPath().getId());
}
@SuppressWarnings("unchecked")
@Test
public void testGetFinishedTaskById() throws Exception
{
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap<QName, Serializable>());
// Finish the start-task
WorkflowTask startTask = workflowEngine.getStartTask(path.getInstance().getId());
workflowEngine.endTask(startTask.getId(), null);
// Set some task properties on the first task, different types
List<WorkflowTask> tasks = workflowEngine.getTasksForWorkflowPath(path.getId());
String finishedTaskId = tasks.get(0).getId();
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(WorkflowModel.PROP_DESCRIPTION, "Task description");
props.put(WorkflowModel.PROP_PRIORITY, 1234);
props.put(QName.createQName("myprop1"), "Property value");
props.put(QName.createQName("myprop2"), Boolean.TRUE);
props.put(QName.createQName("myprop3"), 12345);
props.put(QName.createQName("myprop4"), 45678L);
workflowEngine.updateTask(finishedTaskId, props, null, null);
// Finish the first task, this task will be used in this test
workflowEngine.endTask(finishedTaskId, null);
// Get the finished task
WorkflowTask finishedTask = workflowEngine.getTaskById(finishedTaskId);
assertNotNull(finishedTask);
// TODO: revive once http://jira.codehaus.org/browse/ACT-485 is fixed
// Assert.assertEquals("Task description", finishedTask.getDescription());
Assert.assertEquals(finishedTaskId, finishedTask.getId());
Assert.assertEquals("bpm_foo_task", finishedTask.getName());
Assert.assertEquals("Task", finishedTask.getTitle());
Assert.assertEquals(WorkflowTaskState.COMPLETED, finishedTask.getState());
// Check if typeDefinition (formKey) is preserved on finished tasks
Assert.assertEquals("task name", finishedTask.getDefinition().getId(), "bpm_foo_task");
// Check workflowpath
Assert.assertEquals(path.getId(), finishedTask.getPath().getId());
Assert.assertEquals(path.getInstance().getId(), finishedTask.getPath().getInstance().getId());
// Check variables
Assert.assertEquals("Property value", finishedTask.getProperties().get(QName.createQName("myprop1")));
Assert.assertEquals(Boolean.TRUE, finishedTask.getProperties().get(QName.createQName("myprop2")));
Assert.assertEquals(12345, finishedTask.getProperties().get(QName.createQName("myprop3")));
Assert.assertEquals(45678L, finishedTask.getProperties().get(QName.createQName("myprop4")));
// Check pooled actors, should be one user and one group
List<NodeRef> pooledActors = (List<NodeRef>) finishedTask.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS);
Assert.assertNotNull(pooledActors);
Assert.assertEquals(2, pooledActors.size());
Assert.assertTrue(pooledActors.contains(testGroupNode));
Assert.assertTrue(pooledActors.contains(testUserNode));
}
@Test
public void testEndTask() throws Exception
{
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap<QName, Serializable>());
Task task = taskService.createTaskQuery()
.executionId(BPMEngineRegistry.getLocalId(path.getId()))
.singleResult();
assertNotNull("Task should exist!", task);
String globalTaskId = createGlobalId(task.getId());
// Set a custom property on the task, this will be flushed
// to process-instance once task is completed
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(QName.createQName("http://test", "myVar"), "test123");
workflowEngine.updateTask(globalTaskId, props, null, null);
// Now end the task
workflowEngine.endTask(globalTaskId, null);
// Check if process-instance now contains the variable of the finished task
List<HistoricDetail> updates = historyService.createHistoricDetailQuery()
.variableUpdates()
.processInstanceId(task.getProcessInstanceId())
.list();
boolean found = false;
for(HistoricDetail detail : updates)
{
HistoricVariableUpdate update = (HistoricVariableUpdate) detail;
if(update.getVariableName().equals("test_myVar"))
{
Assert.assertEquals("test123", update.getValue());
found = true;
}
}
Assert.assertTrue("Task variables are not flushed to process-instance", found);
}
@SuppressWarnings("unchecked")
@Test
public void testGetPooledTasks() throws Exception
{
// The first task in the TestTaskDefinition has candidate group 'testGroup'
// and candidate-user 'testUser'
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap<QName, Serializable>());
// Get start task
WorkflowTask startTask = workflowEngine.getStartTask(path.getInstance().getId());
assertNotNull(startTask);
// Finish the start task
workflowEngine.endTask(startTask.getId(), null);
List<WorkflowTask> tasks = workflowEngine.getTasksForWorkflowPath(path.getId());
assertNotNull(tasks);
Assert.assertEquals(1, tasks.size());
// Check if the ASSOC_POOLED_ACTORS is set on the task, to be sure
// pooled actors are used on task
WorkflowTask theTask = tasks.get(0);
Serializable pooledActors = theTask.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS);
assertNotNull(pooledActors);
// Group and user should be present
List<NodeRef> pooledActorNodes = (List<NodeRef>) pooledActors;
Assert.assertEquals(2, pooledActorNodes.size());
Assert.assertTrue(pooledActorNodes.contains(testUserNode));
Assert.assertTrue(pooledActorNodes.contains(testGroupNode));
// The task should be found when pooled tasks are requested
List<WorkflowTask> pooledUserTasks = workflowEngine.getPooledTasks(Arrays.asList(TEST_USER));
assertNotNull(pooledUserTasks);
Assert.assertEquals(1, pooledUserTasks.size());
Assert.assertEquals(theTask.getId(), pooledUserTasks.get(0).getId());
// The task should be found when pooled taskes are requested
List<WorkflowTask> pooledGroupTasks = workflowEngine.getPooledTasks(Arrays.asList(TEST_GROUP));
assertNotNull(pooledGroupTasks);
Assert.assertEquals(1, pooledGroupTasks.size());
Assert.assertEquals(theTask.getId(), pooledGroupTasks.get(0).getId());
// Only a single task should be found when task is both pooled for testUser and testGroup
List<WorkflowTask> pooledTasks = workflowEngine.getPooledTasks(Arrays.asList(TEST_USER, TEST_GROUP));
assertNotNull(pooledTasks);
Assert.assertEquals(1, pooledTasks.size());
Assert.assertEquals(theTask.getId(), pooledTasks.get(0).getId());
// No tasks should be found
List<WorkflowTask> unexistingPooledTasks = workflowEngine.getPooledTasks(Arrays.asList("unexisting"));
assertNotNull(unexistingPooledTasks);
Assert.assertEquals(0, unexistingPooledTasks.size());
// If one authority matches, task should be returned
pooledGroupTasks = workflowEngine.getPooledTasks(Arrays.asList("unexistinggroup",TEST_GROUP));
assertNotNull(pooledGroupTasks);
Assert.assertEquals(1, pooledGroupTasks.size());
Assert.assertEquals(theTask.getId(), pooledGroupTasks.get(0).getId());
}
@Test
public void testQueryTasksInProgress() throws Exception {
// Testing all query functionality for WorkflowTaskState.IN_PROGRESS
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap<QName, Serializable>());
Task task = taskService.createTaskQuery()
.executionId(BPMEngineRegistry.getLocalId(path.getId()))
.singleResult();
assertNotNull("Task should exist!", task);
String globalTaskId = createGlobalId(task.getId());
// Test query by taskId
WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS);
taskQuery.setTaskId(globalTaskId);
List<WorkflowTask> tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(1, tasks.size());
Assert.assertEquals(globalTaskId, tasks.get(0).getId());
// Test query by nonexistent taskId
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS);
taskQuery.setTaskId(createGlobalId("nonexistentTask"));
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
// Test query by process ID
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS);
taskQuery.setProcessId(createGlobalId(task.getProcessInstanceId()));
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(1, tasks.size());
Assert.assertEquals(globalTaskId, tasks.get(0).getId());
// Test query by nonexistent processId
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS);
taskQuery.setProcessId(createGlobalId("nonexistentProcess"));
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
// Test query by actor ID
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS);
taskQuery.setActorId(TEST_USER);
tasks = workflowEngine.queryTasks(taskQuery);
// No tasks should be assigned to testUser
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
// Assign the task
taskService.setAssignee(task.getId(), TEST_USER);
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS);
taskQuery.setActorId(TEST_USER);
tasks = workflowEngine.queryTasks(taskQuery);
// Task is assigned to testUser
Assert.assertNotNull(tasks);
Assert.assertEquals(1, tasks.size());
Assert.assertEquals(globalTaskId, tasks.get(0).getId());
// Test by nonexistent actor ID
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.IN_PROGRESS);
taskQuery.setActorId("nonexistentUser");
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
// TODO: test using process-name, not yet implemented now
// TODO: test using task-name
// Test querying task variables, using all possible (and allowed) types of variables
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("longVar", 928374L);
variables.put("shortVar", (short) 123);
variables.put("integerVar", 1234);
variables.put("stringVar", "stringValue");
variables.put("booleanVar", true);
Date date = Calendar.getInstance().getTime();
variables.put("dateVar", date);
variables.put("nullVar", null);
ActivitiScriptNode scriptNode = new ActivitiScriptNode(testGroupNode, serviceRegistry);
variables.put("scriptNodeVar", scriptNode);
taskService.setVariablesLocal(task.getId(), variables);
// Query long variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("longVar"), 928374L, globalTaskId);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("longVar"), 444444L);
// Query short variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("shortVar"), (short) 123, globalTaskId);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("shortVar"), (short) 456);
// Query integer variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("integerVar"), 1234, globalTaskId);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("integerVar"), 5678);
// Query string variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("stringVar"), "stringValue", globalTaskId);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("stringVar"), "noMatchString");
// Query string variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("booleanVar"), true, globalTaskId);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("booleanVar"), false);
// Query date variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("dateVar"), date, globalTaskId);
Calendar otherDate = Calendar.getInstance();
otherDate.add(Calendar.YEAR, 1);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("dateVar"), otherDate.getTime());
// Query null variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("nullVar"), null, globalTaskId);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("nullVar"), "notNull");
// Query script-node variable
checkTaskVariableTaskPresent(WorkflowTaskState.IN_PROGRESS, QName.createQName("scriptNodeVar"), scriptNode, globalTaskId);
ActivitiScriptNode otherNode = new ActivitiScriptNode(testUserNode, serviceRegistry);
checkTaskVariableNoMatch(WorkflowTaskState.IN_PROGRESS, QName.createQName("scriptNodeVar"), otherNode);
// TODO: test using process variables
}
@Test
public void testQueryTasksCompleted() throws Exception {
// Testing all query functionality for WorkflowTaskState.IN_PROGRESS
WorkflowPath path = workflowEngine.startWorkflow(workflowDef.getId(), new HashMap<QName, Serializable>());
Task task = taskService.createTaskQuery()
.executionId(BPMEngineRegistry.getLocalId(path.getId()))
.singleResult();
assertNotNull("Task should exist!", task);
String globalTaskId = createGlobalId(task.getId());
// Set the actor
taskService.setAssignee(task.getId(), TEST_USER);
// End the task
workflowEngine.endTask(globalTaskId, null);
// Test query by taskId
WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED);
taskQuery.setTaskId(globalTaskId);
List<WorkflowTask> tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(1, tasks.size());
Assert.assertEquals(globalTaskId, tasks.get(0).getId());
// Test query by nonexistent task ID
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED);
taskQuery.setTaskId(createGlobalId("nonexistantTask"));
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
// Test query by process ID, this should also return the start-task
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED);
taskQuery.setProcessId(createGlobalId(task.getProcessInstanceId()));
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(2, tasks.size());
boolean taskFound = false;
boolean startTaskFound = false;
for(WorkflowTask wfTask : tasks)
{
if(wfTask.getId().equals(globalTaskId))
{
taskFound = true;
}
if(wfTask.getId().contains(ActivitiConstants.START_TASK_PREFIX))
{
startTaskFound = true;
}
}
Assert.assertTrue("Task should have been returned", taskFound);
Assert.assertTrue("Start-task should have been returned", startTaskFound);
// Test query by nonexistent process ID
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED);
taskQuery.setProcessId(createGlobalId("nonexistantProcess"));
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
// Test query by actor
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED);
taskQuery.setActorId(TEST_USER);
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(1, tasks.size());
// Test by nonexistent actor
taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED);
taskQuery.setActorId("unexistingUser");
tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
// TODO: test using process-name, not yet implemented now
// TODO: test using task-name
// TODO: test using task variables
// TODO: test using process variables
}
private void checkTaskVariableTaskPresent(WorkflowTaskState state,
QName varName, Object varValue, String expectedTask) {
WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state);
Map<QName, Object> customProperties = new HashMap<QName, Object>();
customProperties.put(varName, varValue);
taskQuery.setTaskCustomProps(customProperties);
assertTaskPresent(taskQuery, expectedTask);
}
private void checkTaskVariableNoMatch(WorkflowTaskState state,
QName varName, Object varValue) {
WorkflowTaskQuery taskQuery = createWorkflowTaskQuery(state);
Map<QName, Object> customProperties = new HashMap<QName, Object>();
customProperties.put(varName, varValue);
taskQuery.setTaskCustomProps(customProperties);
assertNoTaskPresent(taskQuery);
}
private WorkflowTaskQuery createWorkflowTaskQuery(WorkflowTaskState state)
{
WorkflowTaskQuery taskQuery = new WorkflowTaskQuery();
taskQuery.setTaskState(state);
return taskQuery;
}
private void assertTaskPresent(WorkflowTaskQuery taskQuery,
String taskId)
{
List<WorkflowTask> tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(1, tasks.size());
Assert.assertEquals(taskId, tasks.get(0).getId());
}
private void assertNoTaskPresent(WorkflowTaskQuery taskQuery)
{
List<WorkflowTask> tasks = workflowEngine.queryTasks(taskQuery);
Assert.assertNotNull(tasks);
Assert.assertEquals(0, tasks.size());
}
private String createGlobalId(String id)
{
return BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, id);
}
@Override
@Before
public void setUp() throws Exception
{
super.setUp();
this.workflowDef = deployTestTaskDefinition();
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.Serializable;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.AbstractWorkflowPropertyHandler;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.namespace.QName;
/**
* @since 4.0
* @author Nick Smith
*
*/
public abstract class ActivitiTaskPropertyHandler extends AbstractWorkflowPropertyHandler
{
/**
* {@inheritDoc}
*/
public Object handleProperty(QName key, Serializable value, TypeDefinition type, Object object, Class<?> objectType)
{
if(DelegateTask.class.equals(objectType))
{
return handleDelegateTaskProperty((DelegateTask)object, type, key, value);
}
else if(Task.class.equals(objectType))
{
return handleTaskProperty((Task)object, type, key, value);
}
return handleProcessPropert(null, type, key, value);
}
/**
* @param type
* @param key
* @param value
* @return
*/
private Object handleProcessPropert(Object process, TypeDefinition type, QName key, Serializable value)
{
return handleDefaultProperty(process, type, key, value);
}
/**
* Handles the property for a {@link Task}.
* @param task
* @param type
* @param key
* @param value
* @return
*/
protected abstract Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value);
/**
* Handles the property for a {@link DelegateTask}.
* @param task
* @param value
* @param key
* @param type
* @return
*/
protected abstract Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value);
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import org.activiti.engine.FormService;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.form.FormData;
import org.activiti.engine.form.TaskFormData;
import org.activiti.engine.impl.form.TaskFormHandler;
import org.activiti.engine.impl.task.TaskEntity;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.WorkflowObjectFactory;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiTaskTypeManager
{
private final WorkflowObjectFactory factory;
private final FormService formService;
public ActivitiTaskTypeManager(WorkflowObjectFactory factory, FormService formService)
{
this.factory = factory;
this.formService = formService;
}
public TypeDefinition getStartTaskDefinition(String taskTypeName)
{
return factory.getTaskFullTypeDefinition(taskTypeName, true);
}
public TypeDefinition getFullTaskDefinition(Task task)
{
TaskFormData taskFormData = formService.getTaskFormData(task.getId());
return getFullTaskDefinition(task.getId(), taskFormData);
}
public TypeDefinition getFullTaskDefinition(DelegateTask delegateTask)
{
FormData formData = null;
TaskEntity taskEntity = (TaskEntity) delegateTask;
TaskFormHandler taskFormHandler = taskEntity.getTaskDefinition().getTaskFormHandler();
if (taskFormHandler != null)
{
formData = taskFormHandler.createTaskForm(taskEntity);
}
return getFullTaskDefinition(delegateTask.getId(), formData);
}
public TypeDefinition getFullTaskDefinition(String typeName)
{
return getFullTaskDefinition(typeName, null);
}
private TypeDefinition getFullTaskDefinition(String taskDefinitionKey, FormData taskFormData)
{
String formKey = null;
if(taskFormData != null)
{
formKey = taskFormData.getFormKey();
}
else
{
// Revert to task definition key
formKey = taskDefinitionKey;
}
// Since Task instances are never the start-task, it's safe to always be false
return factory.getTaskFullTypeDefinition(formKey, false);
}
}

View File

@@ -0,0 +1,320 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.impl.runtime.TimerEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.person.TestPersonManager;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowDeployment;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTimer;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.GUID;
/**
* Test to verify timer execution autentication and transaction behaviour.
*
* @author Frederik Heremans
*/
public class ActivitiTimerExecutionTest extends BaseSpringTest {
private static final String USER1 = "User1" + GUID.generate();
private RetryingTransactionHelper transactionHelper;
private WorkflowService workflowService;
private AuthenticationComponent authenticationComponent;
private NodeService nodeService;
private ProcessEngine activitiProcessEngine;
private TestPersonManager personManager;
@SuppressWarnings("deprecation")
public void testTimerExecutionAuthentication() throws Exception
{
this.setComplete();
this.endTransaction();
try
{
WorkflowInstance taskAssigneeWorkflowInstance = transactionHelper
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<WorkflowInstance>()
{
public WorkflowInstance execute() throws Throwable
{
// Create test person
personManager.createPerson(USER1);
WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml");
// Start the test timer transaction process, with 'error' = false, expecting a timer job
// to be executed without an error, with task timer is assigned to assigned to USER1
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
params.put(QName.createQName("error"), Boolean.FALSE);
params.put(QName.createQName("theTaskAssignee"), USER1);
WorkflowPath path = workflowService.startWorkflow(definition.getId(), params);
// End start-task
workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null);
return path.getInstance();
}
});
final String definitionId = taskAssigneeWorkflowInstance.getDefinition().getId();
WorkflowInstance unassignedWorkflowInstance = transactionHelper
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<WorkflowInstance>()
{
public WorkflowInstance execute() throws Throwable
{
// Start the test timer transaction process, with 'error' = false, expecting a timer job
// to be executed without an error, with task timer is unassigned
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
params.put(QName.createQName("error"), Boolean.FALSE);
params.put(QName.createQName("theTaskAssignee"), null);
WorkflowPath path = workflowService.startWorkflow(definitionId, params);
// End start-task
workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null);
return path.getInstance();
}
});
// No timers should be available after a while they should have been executed, otherwise test fails
waitForTimersToBeExecuted(taskAssigneeWorkflowInstance.getId());
waitForTimersToBeExecuted(unassignedWorkflowInstance.getId());
// Test assigned task
WorkflowPath path = workflowService.getWorkflowPaths(taskAssigneeWorkflowInstance.getId()).get(0);
// Check if job executed without exception, process should be waiting in "waitTask"
List<WorkflowTask> tasks = workflowService.getTasksForWorkflowPath(path.getId());
assertNotNull(tasks);
assertEquals(1, tasks.size());
assertEquals("waitTask", tasks.get(0).getDefinition().getNode().getName());
// Check if timer was executed as task assignee, was set while executing timer
Map<QName, Serializable> pathProps = workflowService.getPathProperties(path.getId());
assertEquals(USER1, pathProps.get(QName.createQName("timerExecutedAs")));
// Test unassigned task, should be executed as admin-user
path = workflowService.getWorkflowPaths(unassignedWorkflowInstance.getId()).get(0);
// Check if job did executed without exception, process should be waiting in "waitTask"
tasks = workflowService.getTasksForWorkflowPath(path.getId());
assertNotNull(tasks);
assertEquals(1, tasks.size());
assertEquals("waitTask", tasks.get(0).getDefinition().getNode().getName());
// Check if timer was executed as system
pathProps = workflowService.getPathProperties(path.getId());
assertEquals(AuthenticationUtil.getSystemUserName(), pathProps.get(QName.createQName("timerExecutedAs")));
}
finally
{
cleanUp();
}
}
@SuppressWarnings("deprecation")
public void testTimerExecutionTransactionRollback() throws Exception
{
this.setComplete();
this.endTransaction();
try
{
WorkflowInstance workflowInstance = transactionHelper
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<WorkflowInstance>()
{
public WorkflowInstance execute() throws Throwable
{
// Create test person
personManager.createPerson(USER1);
WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml");
// Start the test timer transaction process, with 'error' = false, expecting a timer job
// to be executed without an error, with task timer is assigned to assigned to USER1
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
params.put(QName.createQName("error"), Boolean.TRUE);
params.put(QName.createQName("theTaskAssignee"), USER1);
WorkflowPath path = workflowService.startWorkflow(definition.getId(), params);
// End start-task
workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null);
return path.getInstance();
}
});
String processInstanceId = BPMEngineRegistry.getLocalId(workflowInstance.getId());
// Check the timer, should have "error" set in it
TimerEntity timer = (TimerEntity) activitiProcessEngine.getManagementService()
.createJobQuery().timers()
.processInstanceId(processInstanceId).singleResult();
int numberOfRetries = 5;
for(int i = 0; i < numberOfRetries; i++)
{
if(timer.getExceptionMessage() != null && timer.getRetries() == 0)
{
break;
}
Thread.sleep(1000);
timer = (TimerEntity) activitiProcessEngine.getManagementService()
.createJobQuery().timers()
.processInstanceId(processInstanceId).singleResult();
}
assertNotNull("Job should have exception message set", timer.getExceptionMessage());
assertEquals(0, timer.getRetries());
// Check if exception is the one we deliberately caused
String fullExceptionStacktrace = activitiProcessEngine.getManagementService().getJobExceptionStacktrace(timer.getId());
assertTrue(fullExceptionStacktrace.contains("Activiti engine rocks!"));
// Check if alfresco-changes that were performed are rolled back
NodeRef personNode = personManager.get(USER1);
NodeRef userHomeNode = (NodeRef)nodeService.getProperty(personNode, ContentModel.PROP_HOMEFOLDER);
String homeFolderName = (String) nodeService.getProperty(userHomeNode, ContentModel.PROP_NAME);
assertNotSame("User home changed", homeFolderName);
}
finally
{
cleanUp();
}
}
/**
* Delete the deployment, cascading all related processes/history
*/
private void cleanUp()
{
transactionHelper .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
try
{
personManager.clearPeople();
}
finally
{
// Make sure process-definition is still deleted, even when clearing people fails.
ProcessDefinition procDef = activitiProcessEngine.getRepositoryService()
.createProcessDefinitionQuery()
.processDefinitionKey("testTimerTransaction")
.singleResult();
if(procDef != null)
{
activitiProcessEngine.getRepositoryService().deleteDeployment(procDef.getDeploymentId(), true);
}
}
return null;
}
});
}
@SuppressWarnings("deprecation")
@Override
protected void onSetUpInTransaction() throws Exception
{
ServiceRegistry registry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY);
this.workflowService = registry.getWorkflowService();
this.authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent");
this.nodeService = registry.getNodeService();
this.transactionHelper = (RetryingTransactionHelper) this.applicationContext
.getBean("retryingTransactionHelper");
this.activitiProcessEngine = (ProcessEngine) this.applicationContext.getBean("activitiProcessEngine");
MutableAuthenticationService authenticationService = registry.getAuthenticationService();
PersonService personService = registry.getPersonService();
this.personManager = new TestPersonManager(authenticationService, personService, nodeService);
authenticationComponent.setSystemUserAsCurrentUser();
}
private void waitForTimersToBeExecuted(String workflowInstanceId) throws Exception
{
// Job-executor should finish the job, no timers should be available for WF
List<WorkflowTimer> timers = workflowService.getTimers(workflowInstanceId);
int numberOfRetries = 5;
for(int i=0; i< numberOfRetries; i++)
{
if(timers.size() == 0)
{
break;
}
Thread.sleep(1000);
timers = workflowService.getTimers(workflowInstanceId);
}
}
protected WorkflowDefinition deployDefinition(String resource)
{
InputStream input = getInputStream(resource);
WorkflowDeployment deployment = workflowService.deployDefinition(ActivitiConstants.ENGINE_ID, input, MimetypeMap.MIMETYPE_XML);
WorkflowDefinition definition = deployment.getDefinition();
return definition;
}
private InputStream getInputStream(String resource)
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream input= classLoader.getResourceAsStream(resource);
return input;
}
}

View File

@@ -0,0 +1,557 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.form.StartFormData;
import org.activiti.engine.form.TaskFormData;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.WorkflowObjectFactory;
import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowDeployment;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowNode;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.cmr.workflow.WorkflowTransition;
import org.alfresco.service.namespace.QName;
/**
* @since 3.5
* @author Nick Smith
* @author Frederik Heremans
*
*/
public class ActivitiTypeConverter
{
/**
* Default transition provided for all Nodes when using Activiti engine.
*/
private static final WorkflowTransition NEXT_TRANSITION = new WorkflowTransition(ActivitiConstants.DEFAULT_TRANSITION_NAME,
ActivitiConstants.DEFAULT_TRANSITION_NAME, "Default Transition", true);
private final RepositoryService repoService;
private final RuntimeService runtimeService;
private final FormService formService;
private final HistoryService historyService;
private final ActivitiPropertyConverter propertyConverter;
private final WorkflowObjectFactory factory;
private final ActivitiUtil activitiUtil;
public ActivitiTypeConverter(ProcessEngine processEngine,
WorkflowObjectFactory factory,
ActivitiPropertyConverter propertyConverter)
{
this.repoService = processEngine.getRepositoryService();
this.runtimeService = processEngine.getRuntimeService();
this.formService = processEngine.getFormService();
this.historyService = processEngine.getHistoryService();
this.factory = factory;
this.propertyConverter =propertyConverter;
this.activitiUtil = new ActivitiUtil(processEngine);
}
/**
* Convert a {@link Deployment} into a {@link WorkflowDeployment}.
* @param deployment
* @return
*/
public WorkflowDeployment convert(Deployment deployment)
{
if(deployment == null)
return null;
List<ProcessDefinition> processDefs = repoService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.list();
ProcessDefinition processDef = processDefs.get(0);
WorkflowDefinition wfDef = convert(processDef);
return factory.createDeployment(wfDef);
}
/**
* Convert a {@link ProcessDefinition} into a {@link WorkflowDefinition}.
* @param processDef
* @return
*/
public WorkflowDefinition convert(ProcessDefinition definition)
{
if(definition==null)
return null;
String defId = definition.getId();
String defName = definition.getKey();
int version = definition.getVersion();
String defaultTitle = definition.getName();
String startTaskName = null;
StartFormData startFormData = formService.getStartFormData(definition.getId());
if(startFormData != null)
{
startTaskName = startFormData.getFormKey();
}
ReadOnlyProcessDefinition def = activitiUtil.getDeployedProcessDefinition(defId);
PvmActivity startEvent = def.getInitial();
WorkflowTaskDefinition startTask = getTaskDefinition(startEvent, startTaskName, definition.getKey());
return factory.createDefinition(defId,
defName, version, defaultTitle,
null, startTask);
}
public WorkflowTaskDefinition getTaskDefinition(PvmActivity activity, String taskFormKey, String processDefinitionName)
{
String startId = activity.getId();
String startTitle = (String) activity.getProperty(ActivitiConstants.NODE_NAME);
String startDescription= (String) activity.getProperty(ActivitiConstants.NODE_DESCRIPTION);
String startType = (String) activity.getProperty(ActivitiConstants.NODE_TYPE);
if(taskFormKey == null)
{
taskFormKey = startId;
}
WorkflowNode node = factory.createNode(startId, processDefinitionName, startTitle, startDescription, startType, true, NEXT_TRANSITION);
WorkflowTaskDefinition startTask = factory.createTaskDefinition(taskFormKey, node, taskFormKey, true);
return startTask;
}
public WorkflowInstance convert(ProcessInstance instance)
{
return convertAndSetVariables(instance, (Map<String, Object>) null);
}
public WorkflowInstance convertAndSetVariables(ProcessInstance instance, Map<String, Object> collectedvariables)
{
if(instance == null)
return null;
HistoricProcessInstance historicInstance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(instance.getId())
.singleResult();
return convertToInstanceAndSetVariables(historicInstance, collectedvariables);
}
public WorkflowInstance convert(HistoricProcessInstance instance, Map<String, Object> collectedvariables)
{
if(instance == null)
return null;
HistoricProcessInstance historicInstance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(instance.getId())
.singleResult();
return convertToInstanceAndSetVariables(historicInstance, collectedvariables);
}
public WorkflowPath convert(Execution execution)
{
String instanceId = execution.getProcessInstanceId();
ProcessInstance instance = activitiUtil.getProcessInstance(instanceId);
return convert(execution, instance);
}
public WorkflowPath convert(Execution execution, ProcessInstance instance)
{
if(execution == null)
return null;
boolean isActive = !execution.isEnded();
// Convert workflow and collect variables
Map<String, Object> workflowInstanceVariables = new HashMap<String, Object>();
WorkflowInstance wfInstance = convertAndSetVariables(instance, workflowInstanceVariables);
WorkflowNode node = null;
// Get active node on execution
List<String> nodeIds = runtimeService.getActiveActivityIds(execution.getId());
if (nodeIds != null && nodeIds.size() >= 1)
{
ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(instance.getProcessDefinitionId());
PvmActivity activity = procDef.findActivity(nodeIds.get(0));
node = convert(activity);
}
return factory.createPath(execution.getId(), wfInstance, node, isActive);
}
public WorkflowNode convert(PvmActivity activity, boolean forceIsTaskNode)
{
String procDefId = activity.getProcessDefinition().getId();
String key = activitiUtil.getProcessDefinition(procDefId).getKey();
String name = activity.getId();
String defaultTitle = (String) activity.getProperty(ActivitiConstants.NODE_NAME);
String defaultDescription = (String) activity.getProperty(ActivitiConstants.NODE_DESCRIPTION);
String type = (String) activity.getProperty(ActivitiConstants.NODE_TYPE);
boolean isTaskNode = forceIsTaskNode || ActivitiConstants.USER_TASK_NODE_TYPE.equals(type);
if(defaultTitle == null)
{
defaultTitle = name;
}
if(defaultDescription == null)
{
defaultDescription = name;
}
return factory.createNode(name, key, defaultTitle, defaultDescription, type, isTaskNode,
NEXT_TRANSITION);
}
public WorkflowNode convert(PvmActivity activity)
{
return convert(activity, false);
}
public List<WorkflowPath> convertExecution(List<Execution> executions)
{
ArrayList<WorkflowPath> results = new ArrayList<WorkflowPath>(executions.size());
for (Execution execution : executions)
{
results.add(convert(execution));
}
return results;
}
@SuppressWarnings("unchecked")
public <T> List<T> convert(List<?> inputs)
{
ArrayList<T> results = new ArrayList<T>(inputs.size());
for (Object in : inputs)
{
T out = (T) convert(in);
if(out != null)
{
results.add(out);
}
}
return results;
}
/**
* Converts an Activiti object to an Alresco Workflow object.
* Determines the exact conversion method to use by checking the class of object.
* @param obj The object to be converted.
* @return the converted object.
*/
private Object convert(Object obj)
{
if(obj == null)
return null;
if (obj instanceof Deployment)
{
return convert( (Deployment) obj);
}
if (obj instanceof ProcessDefinition)
{
return convert( (ProcessDefinition) obj);
}
if (obj instanceof ProcessInstance)
{
return convert( (ProcessInstance) obj);
}
if (obj instanceof Execution)
{
return convert( (Execution) obj);
}
if (obj instanceof ActivityImpl)
{
return convert( (ActivityImpl) obj);
}
if (obj instanceof Task)
{
return convert( (Task) obj);
}
if(obj instanceof HistoricTaskInstance)
{
return convert((HistoricTaskInstance) obj);
}
if(obj instanceof HistoricProcessInstance)
{
return convert((HistoricProcessInstance) obj);
}
throw new WorkflowException("Cannot convert object: " + obj + " of type: " + obj.getClass());
}
public WorkflowTask convert(Task task)
{
if(task == null)
return null;
String id = task.getId();
String defaultTitle = task.getName();
String defaultDescription = task.getDescription();
WorkflowTaskState state = WorkflowTaskState.IN_PROGRESS;
Execution execution = activitiUtil.getExecution(task.getExecutionId());
WorkflowPath path = convert(execution);
// Since the task is active, it's safe to use the active node on
// the execution path
WorkflowNode node = path.getNode();
TaskFormData taskFormData =formService.getTaskFormData(task.getId());
String taskDefId = null;
if(taskFormData != null)
{
taskDefId = taskFormData.getFormKey();
}
WorkflowTaskDefinition taskDef = factory.createTaskDefinition(taskDefId, node, taskDefId, false);
// All task-properties should be fetched, not only local
Map<QName, Serializable> properties = propertyConverter.getTaskProperties(task, false);
return factory.createTask(id,
taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties);
}
public WorkflowTask getVirtualStartTask(String executionId, boolean inProgress)
{
Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(execution.getProcessInstanceId())
.singleResult();
String id = ActivitiConstants.START_TASK_PREFIX + execution.getProcessInstanceId();
WorkflowTaskState state = null;
if(inProgress)
{
state = WorkflowTaskState.IN_PROGRESS;
}
else
{
state = WorkflowTaskState.COMPLETED;
}
WorkflowPath path = convert(execution);
// Convert start-event to start-task Node
ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
WorkflowNode startNode = convert(procDef.getInitial(), true);
StartFormData startFormData = formService.getStartFormData(processInstance.getProcessDefinitionId());
String taskDefId = null;
if(startFormData != null)
{
taskDefId = startFormData.getFormKey();
}
WorkflowTaskDefinition taskDef = factory.createTaskDefinition(taskDefId, startNode, taskDefId, true);
// Add properties based on HistoricProcessInstance
HistoricProcessInstance historicProcessInstance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(execution.getProcessInstanceId())
.singleResult();
Map<QName, Serializable> properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, !inProgress);
// TODO: Figure out what name/description should be used for the start-task, start event's name?
String defaultTitle = null;
String defaultDescription = null;
return factory.createTask(id,
taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties);
}
public WorkflowTask getVirtualStartTask(HistoricProcessInstance historicProcessInstance)
{
if(historicProcessInstance == null)
{
return null;
}
String processInstanceId = historicProcessInstance.getId();
String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId;
WorkflowTaskState state = null;
boolean completed = historicProcessInstance.getEndTime() != null;
if(completed)
{
state = WorkflowTaskState.COMPLETED;
}
else
{
state = WorkflowTaskState.IN_PROGRESS;
}
// We use the process-instance ID as execution-id. It's ended anyway
WorkflowPath path = buildCompletedPath(processInstanceId, processInstanceId);
if(path == null)
{
return null;
}
// Convert start-event to start-task Node
ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId());
WorkflowNode startNode = convert(procDef.getInitial(), true);
String taskDefId = activitiUtil.getStartFormKey(historicProcessInstance.getProcessDefinitionId());
WorkflowTaskDefinition taskDef = factory.createTaskDefinition(taskDefId, startNode, taskDefId, true);
Map<QName, Serializable> properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, completed);
// TODO: Figure out what name/description should be used for the start-task, start event's name?
String defaultTitle = null;
String defaultDescription = null;
return factory.createTask(id,
taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties);
}
public WorkflowTask convert(HistoricTaskInstance historicTaskInstance) {
if(historicTaskInstance == null)
{
return null;
}
WorkflowPath path = null;
// Check to see if the instance is still running
Execution execution = activitiUtil.getExecution(historicTaskInstance.getExecutionId());
if(execution != null)
{
// Process execution still running
path = convert(execution);
}
else
{
// Process execution is historic
path = buildCompletedPath(historicTaskInstance.getExecutionId(), historicTaskInstance.getProcessInstanceId());
}
if(path == null)
{
// When path is null, workflow is deleted or cancelled. Task should
// not be used
return null;
}
// Extract node from historic task
WorkflowNode node = buildHistoricTaskWorkflowNode(historicTaskInstance);
WorkflowTaskState state= WorkflowTaskState.COMPLETED;
String taskId = historicTaskInstance.getId();
// Get the local task variables from the history
Map<String, Object> variables = propertyConverter.getHistoricTaskVariables(taskId);
Map<QName, Serializable> historicTaskProperties = propertyConverter.getTaskProperties(historicTaskInstance, variables);
// Get task definition from historic variable
String formKey = (String) variables.get(ActivitiConstants.PROP_TASK_FORM_KEY);
WorkflowTaskDefinition taskDef = factory.createTaskDefinition(formKey, node, formKey, false);
String title = historicTaskInstance.getName();
String description = historicTaskInstance.getDescription();
String taskName = taskDef.getId();
return factory.createTask(taskId, taskDef, taskName,
title, description, state, path, historicTaskProperties);
}
private WorkflowNode buildHistoricTaskWorkflowNode(HistoricTaskInstance historicTaskInstance)
{
ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(historicTaskInstance.getProcessDefinitionId());
PvmActivity taskActivity = procDef.findActivity(historicTaskInstance.getTaskDefinitionKey());
return convert(taskActivity);
}
public WorkflowPath buildCompletedPath(String executionId, String processInstanceId)
{
WorkflowInstance wfInstance = null;
ProcessInstance processInstance = activitiUtil.getProcessInstance(processInstanceId);
if(processInstance != null)
{
wfInstance = convert(processInstance);
}
else
{
HistoricProcessInstance historicProcessInstance = activitiUtil.getHistoricProcessInstance(processInstanceId);
if(historicProcessInstance!= null)
wfInstance = convert(historicProcessInstance);
}
if(wfInstance == null)
{
// When workflow is cancelled or deleted, WorkflowPath should not be returned
return null;
}
WorkflowNode node = null;
return factory.createPath(executionId, wfInstance, node, false);
}
public WorkflowInstance convertToInstanceAndSetVariables(HistoricProcessInstance historicProcessInstance, Map<String, Object> collectedVariables)
{
String processInstanceId = historicProcessInstance.getId();
String id = processInstanceId;
ProcessDefinition procDef = activitiUtil.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
WorkflowDefinition definition = convert(procDef);
// Set process variables based on historic detail query
Map<String, Object> variables = propertyConverter.getHistoricProcessVariables(processInstanceId);
Date startDate = historicProcessInstance.getStartTime();
Date endDate = historicProcessInstance.getEndTime();
// Copy all variables to map, if not null
if(collectedVariables != null)
{
collectedVariables.putAll(variables);
}
boolean isActive = historicProcessInstance.getEndTime() == null;
return factory.createInstance(
id, definition, variables, isActive, startDate, endDate);
}
public WorkflowInstance convert(HistoricProcessInstance historicProcessInstance)
{
return convertToInstanceAndSetVariables(historicProcessInstance, null);
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.util.Map;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.form.StartFormData;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition;
import org.activiti.engine.impl.repository.ProcessDefinitionEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiUtil
{
private final RepositoryService repoService;
private final RuntimeService runtimeService;
private final HistoryService historyService;
private final TaskService taskService;
private final FormService formService;
private final ManagementService managementService;
public ActivitiUtil(ProcessEngine engine)
{
this.repoService = engine.getRepositoryService();
this.runtimeService = engine.getRuntimeService();
this.taskService = engine.getTaskService();
this.historyService = engine.getHistoryService();
this.formService = engine.getFormService();
this.managementService = engine.getManagementService();
}
public ProcessDefinition getProcessDefinition(String definitionId)
{
ProcessDefinition procDef = repoService.createProcessDefinitionQuery()
.processDefinitionId(definitionId)
.singleResult();
return procDef;
}
public ProcessInstance getProcessInstance(String id)
{
return runtimeService.createProcessInstanceQuery()
.processInstanceId(id)
.singleResult();
}
public Task getTaskInstance(String taskId)
{
return taskService.createTaskQuery().taskId(taskId).singleResult();
}
public HistoricProcessInstance getHistoricProcessInstance(String id)
{
return historyService.createHistoricProcessInstanceQuery()
.processInstanceId(id)
.singleResult();
}
public Execution getExecution(String id)
{
return runtimeService.createExecutionQuery()
.executionId(id)
.singleResult();
}
public ReadOnlyProcessDefinition getDeployedProcessDefinition(String processDefinitionId)
{
// Currently, getDeployedProcessDefinition is still experimental and not exposed on
// RepositoryService interface
return ((RepositoryServiceImpl)repoService).getDeployedProcessDefinition(processDefinitionId);
}
public String getStartFormKey(String processDefinitionId)
{
ProcessDefinitionEntity procDef = (ProcessDefinitionEntity) getDeployedProcessDefinition(processDefinitionId);
if(procDef.getStartFormHandler() == null) {
return null;
}
return procDef.getStartFormHandler().createStartFormData(procDef).getFormKey();
}
public String getStartTaskTypeName(String processDefinitionId)
{
String startTaskName = null;
StartFormData startFormData = formService.getStartFormData(processDefinitionId);
if(startFormData != null)
{
startTaskName = startFormData.getFormKey();
}
return startTaskName;
}
public Map<String, Object> getExecutionVariables(String executionId)
{
return runtimeService.getVariables(executionId);
}
/**
* @return the formService
*/
public FormService getFormService()
{
return formService;
}
/**
* @return the historyService
*/
public HistoryService getHistoryService()
{
return historyService;
}
/**
* @return the repoService
*/
public RepositoryService getRepositoryService()
{
return repoService;
}
/**
* @return the runtimeService
*/
public RuntimeService getRuntimeService()
{
return runtimeService;
}
/**
* @return the taskService
*/
public TaskService getTaskService()
{
return taskService;
}
/**
* @return
*/
public ManagementService getManagementService()
{
return managementService;
}
/**
* @param localId
* @return
*/
public HistoricTaskInstance getHistoricTaskInstance(String localId)
{
return historyService.createHistoricTaskInstanceQuery().taskId(localId).singleResult();
}
}

View File

@@ -0,0 +1,712 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import java.io.InputStream;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.util.ClockUtil;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.alfresco.repo.jscript.ScriptNode;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.repo.workflow.WorkflowConstants;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowNode;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.cmr.workflow.WorkflowTimer;
import org.alfresco.service.namespace.QName;
import org.junit.Test;
/**
* Spring-configured JUnit 4 test case.
* Uses Spring to load up a test context and runs each test case in a transaction which gets rolled back.
* Loads up the activiti-context.xml and test-database-context.xml.
* @since 3.5
* @author Nick Smith
*
*/
public class ActivitiWorkflowComponentTest extends AbstractActivitiComponentTest
{
@Test
public void testDeployDefinition() throws Exception
{
ProcessDefinition defInDB = repo.createProcessDefinitionQuery()
.processDefinitionKey(TEST_TASK_KEY)
.singleResult();
assertNull("The definition is already deployed!", defInDB);
WorkflowDefinition definition = deployTestTaskDefinition();
String localDefId = BPMEngineRegistry.getLocalId(definition.getId());
ProcessDefinition processDef = repo.createProcessDefinitionQuery()
.processDefinitionId(localDefId)
.singleResult();
assertNotNull("Process Definition should have been deployed!", processDef);
ProcessDefinition def2InDB = repo.createProcessDefinitionQuery()
.processDefinitionKey(TEST_ADHOC_KEY)
.singleResult();
assertNull("The definition is already deployed!", def2InDB);
WorkflowDefinition definition2 = deployTestTaskDefinition();
String localDef2Id = BPMEngineRegistry.getLocalId(definition2.getId());
ProcessDefinition processDef2 = repo.createProcessDefinitionQuery()
.processDefinitionId(localDef2Id)
.singleResult();
assertNotNull("Process Definition should have been deployed!", processDef2);
}
@Test
public void testIsDefinitionDeployed() throws Exception
{
InputStream input = getInputStream(TEST_TASK_DEF);
boolean result = workflowEngine.isDefinitionDeployed(input, XML);
assertFalse("Should return false before process def deployed.", result);
deployTestTaskDefinition();
input = getInputStream(TEST_TASK_DEF);
result = workflowEngine.isDefinitionDeployed(input, XML);
assertTrue("Should return true after process def deployed.", result);
// Check doesn't find Adhoc definition.
input = getInputStream(TEST_ADHOC_DEF);
result = workflowEngine.isDefinitionDeployed(input, XML);
assertFalse("Should not find Adhoc definition.", result);
}
@Test
public void testUndeployDefinition() throws Exception
{
WorkflowDefinition definition = deployTestTaskDefinition();
String localId = BPMEngineRegistry.getLocalId(definition.getId());
long defCount = repo.createProcessDefinitionQuery()
.processDefinitionId(localId)
.count();
assertEquals("The deployed process definition should exist!", 1, defCount);
workflowEngine.undeployDefinition(definition.getId());
defCount = repo.createProcessDefinitionQuery()
.processDefinitionId(localId)
.count();
assertEquals("The undeployed process definition should not exist!", 0, defCount);
}
@Test
public void testGetDefinitionById() throws Exception
{
WorkflowDefinition definition = deployTestTaskDefinition();
WorkflowDefinition result = workflowEngine.getDefinitionById(definition.getId());
assertNotNull("The workflow definition was not found!", result);
assertEquals(definition.getId(), result.getId());
assertEquals(definition.getDescription(), result.getDescription());
assertEquals(definition.getName(), result.getName());
assertEquals(definition.getTitle(), result.getTitle());
assertEquals(definition.getVersion(), result.getVersion());
WorkflowTaskDefinition resultStartDef = result.getStartTaskDefinition();
assertNotNull("Start task is null!", resultStartDef);
WorkflowTaskDefinition originalStartDef = definition.getStartTaskDefinition();
assertEquals("Start task Id does not match!", originalStartDef.getId(), resultStartDef.getId());
WorkflowNode resultNode = resultStartDef.getNode();
assertNotNull("Start Task Node is null!", resultNode);
assertEquals("Start Task Node Name does not match!", originalStartDef.getNode().getName(), resultNode.getName());
TypeDefinition metaData = resultStartDef.getMetadata();
assertNotNull("Start Task Metadata is null!", metaData);
assertEquals("Start Task Metadata name does not match!", originalStartDef.getMetadata().getName(), metaData.getName());
workflowEngine.undeployDefinition(definition.getId());
WorkflowDefinition nullResult = workflowEngine.getDefinitionById(definition.getId());
assertNull("The workflow definition was found but should be null!", nullResult);
}
@Test
public void testGetDefinitionByName() throws Exception
{
WorkflowDefinition definition = deployTestTaskDefinition();
WorkflowDefinition result = workflowEngine.getDefinitionByName(definition.getName());
assertNotNull("The workflow definition was not found!", result);
assertEquals(definition.getId(), result.getId());
assertEquals(definition.getDescription(), result.getDescription());
assertEquals(definition.getName(), result.getName());
assertEquals(definition.getTitle(), result.getTitle());
assertEquals(definition.getVersion(), result.getVersion());
workflowEngine.undeployDefinition(definition.getId());
WorkflowDefinition nullResult = workflowEngine.getDefinitionByName(definition.getName());
assertNull("The workflow definition was found but should be null!", nullResult);
}
@Test
public void testGetDefinitions() throws Exception
{
List<WorkflowDefinition> startDefs = workflowEngine.getDefinitions();
WorkflowDefinition defV1 = deployTestTaskDefinition();
List<WorkflowDefinition> definitions = workflowEngine.getDefinitions();
checkDefinitions(definitions, startDefs, defV1);
// Deploy version 2 of testTask def.
WorkflowDefinition defV2 = deployTestTaskDefinition();
// Check new version replaces old version.
definitions = workflowEngine.getDefinitions();
checkDefinitions(definitions, startDefs, defV2);
// Deploy new type of definition.
WorkflowDefinition adhocDef = deployTestAdhocDefinition();
// Check that definitions of a different type are picked up.
definitions = workflowEngine.getDefinitions();
checkDefinitions(definitions, startDefs, defV2, adhocDef);
}
@Test
public void testGetAllDefinitions() throws Exception
{
List<WorkflowDefinition> startDefs = workflowEngine.getAllDefinitions();
WorkflowDefinition defV1 = deployTestTaskDefinition();
List<WorkflowDefinition> definitions = workflowEngine.getAllDefinitions();
checkDefinitions(definitions, startDefs, defV1);
// Deploy version 2 of testTask def.
WorkflowDefinition defV2 = deployTestTaskDefinition();
// Check new version replaces old version.
definitions = workflowEngine.getAllDefinitions();
checkDefinitions(definitions, startDefs, defV1, defV2);
// Deploy new type of definition.
WorkflowDefinition adhocDef = deployTestAdhocDefinition();
// Check that definitions of a different type are picked up.
definitions = workflowEngine.getAllDefinitions();
checkDefinitions(definitions, startDefs, defV1, defV2, adhocDef);
}
@Test
public void testStartWorkflow() throws Exception
{
WorkflowDefinition def = deployTestTaskDefinition();
// Fill a map of default properties to start the workflow with
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
Date dueDate = Calendar.getInstance().getTime();
properties.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "description123");
properties.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate);
properties.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 2);
// properties.put(WorkflowModel.ASSOC_PACKAGE, null);
// properties.put(WorkflowModel.PROP_CONTEXT, null);
// Call the start method
WorkflowPath path = workflowEngine.startWorkflow(def.getId(), properties);
assertNotNull("The workflow path is null!", path);
String executionId = BPMEngineRegistry.getLocalId(path.getId());
Execution execution = runtime.createExecutionQuery()
.executionId(executionId)
.singleResult();
assertNotNull("No execution was created int he DB!", execution);
WorkflowInstance instance = path.getInstance();
assertNotNull("The workflow instance is null!",instance);
String procInstanceId = BPMEngineRegistry.getLocalId(instance.getId());
ProcessInstance procInstance = runtime.createProcessInstanceQuery()
.processInstanceId(procInstanceId)
.singleResult();
assertNotNull("No process instance was created!", procInstance);
WorkflowNode node = path.getNode();
assertNotNull("The workflow node is null!", node);
String nodeName = node.getName();
assertEquals("task", nodeName);
// Check if company home is added as variable and can be fetched
ScriptNode companyHome = (ScriptNode) runtime.getVariable(procInstanceId, "companyhome");
assertNotNull(companyHome);
assertEquals("companyHome", companyHome.getNodeRef().getStoreRef().getIdentifier());
// Check if the initiator is added as variable
ScriptNode initiator = (ScriptNode) runtime.getVariable(procInstanceId, "initiator");
assertNotNull(initiator);
assertEquals("admin", initiator.getNodeRef().getStoreRef().getIdentifier());
// Check if the initiator home is also set as variable
ScriptNode initiatorHome = (ScriptNode) runtime.getVariable(procInstanceId, "initiatorhome");
assertNotNull(initiatorHome);
assertEquals("admin-home", initiatorHome.getNodeRef().getStoreRef().getIdentifier());
// Check if start-date is set and no end-date is set
assertNotNull(path.getInstance().getStartDate());
assertNull(path.getInstance().getEndDate());
// Also check if the task that is created, has all default properties initialised
Task task = taskService.createTaskQuery().processInstanceId(procInstanceId).singleResult();
assertNotNull("Task should have been created", task);
assertEquals("task", task.getTaskDefinitionKey());
String defaultSetVariable = (String) taskService.getVariableLocal(task.getId(), "test_myProp");
assertEquals("Default value", defaultSetVariable);
// Also check default value of task description is taken from WF-porps
assertEquals("description123", task.getDescription());
}
@Test
public void testSignal() throws Exception
{
WorkflowDefinition def = deployTestSignallingDefinition();
ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()));
String procId = processInstance.getId();
List<String> nodeIds = runtime.getActiveActivityIds(procId);
assertEquals(1, nodeIds.size());
assertEquals("task1", nodeIds.get(0));
String pathId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, procId);
WorkflowPath path = workflowEngine.signal(pathId, null);
assertEquals(pathId, path.getId());
assertEquals("task2", path.getNode().getName());
assertEquals(pathId, path.getInstance().getId());
assertTrue(path.isActive());
nodeIds = runtime.getActiveActivityIds(procId);
assertEquals(1, nodeIds.size());
assertEquals("task2", nodeIds.get(0));
// Should end the WorkflowInstance
path = workflowEngine.signal(pathId, null);
assertEquals(pathId, path.getId());
assertNull(path.getNode());
assertEquals(pathId, path.getInstance().getId());
assertFalse(path.isActive());
}
@Test
public void testCancelWorkflow() throws Exception
{
WorkflowDefinition def = deployTestAdhocDefinition();
ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()));
// Validate if a workflow exists
List<WorkflowInstance> instances = workflowEngine.getActiveWorkflows(def.getId());
assertNotNull(instances);
assertEquals(1, instances.size());
assertEquals(processInstance.getId(), BPMEngineRegistry.getLocalId(instances.get(0).getId()));
// Call cancel method on component
WorkflowInstance cancelledWorkflow = workflowEngine.cancelWorkflow(instances.get(0).getId());
assertFalse(cancelledWorkflow.isActive());
instances = workflowEngine.getActiveWorkflows(def.getId());
assertNotNull(instances);
assertEquals(0, instances.size());
// Histrotic process instance shouldn't be present
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.singleResult();
assertNull(historicProcessInstance);
}
@Test
public void testCancelUnexistingWorkflow() throws Exception
{
try
{
String globalId = workflowEngine.createGlobalId("unexistingWorkflowId");
workflowEngine.cancelWorkflow(globalId);
fail("Exception expected");
}
catch(WorkflowException e)
{
// Inore this
}
}
@Test
public void testDeleteWorkflow() throws Exception
{
WorkflowDefinition def = deployTestAdhocDefinition();
ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()));
// Validate if a workflow exists
List<WorkflowInstance> instances = workflowEngine.getActiveWorkflows(def.getId());
assertNotNull(instances);
assertEquals(1, instances.size());
assertEquals(processInstance.getId(), BPMEngineRegistry.getLocalId(instances.get(0).getId()));
// Call delete method on component
workflowEngine.deleteWorkflow(instances.get(0).getId());
instances = workflowEngine.getActiveWorkflows(def.getId());
assertNotNull(instances);
assertEquals(0, instances.size());
// Historic process instance shouldn't be present
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.singleResult();
assertNull(historicProcessInstance);
}
@Test
public void testDeleteUnexistingWorkflow() throws Exception
{
try
{
String globalId = workflowEngine.createGlobalId("unexistingWorkflowId");
workflowEngine.deleteWorkflow(globalId);
fail("Exception expected");
}
catch(WorkflowException e)
{
// Inore this
}
}
@Test
public void testGetActiveWorkflows() throws Exception
{
WorkflowDefinition def = deployTestAdhocDefinition();
String activitiProcessDefinitionId = BPMEngineRegistry.getLocalId(def.getId());
ProcessInstance activeInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance completedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance cancelledInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance deletedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
// Complete completedProcessInstance.
String completedId = completedInstance.getId();
boolean isActive = true;
while (isActive)
{
Execution execution = runtime.createExecutionQuery()
.processInstanceId(completedId)
.singleResult();
runtime.signal(execution.getId());
ProcessInstance instance = runtime.createProcessInstanceQuery()
.processInstanceId(completedId)
.singleResult();
isActive = instance != null;
}
// Deleted and canceled instances shouldn't be returned
workflowEngine.cancelWorkflow(workflowEngine.createGlobalId(cancelledInstance.getId()));
workflowEngine.deleteWorkflow(workflowEngine.createGlobalId(deletedInstance.getId()));
// Validate if a workflow exists
List<WorkflowInstance> instances = workflowEngine.getActiveWorkflows(def.getId());
assertNotNull(instances);
assertEquals(1, instances.size());
String instanceId = instances.get(0).getId();
assertEquals(activeInstance.getId(), BPMEngineRegistry.getLocalId(instanceId));
}
@Test
public void testGetCompletedWorkflows() throws Exception
{
WorkflowDefinition def = deployTestAdhocDefinition();
String activitiProcessDefinitionId = BPMEngineRegistry.getLocalId(def.getId());
runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance completedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance cancelledInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance deletedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
// Complete completedProcessInstance.
String completedId = completedInstance.getId();
boolean isActive = true;
while (isActive)
{
Execution execution = runtime.createExecutionQuery()
.processInstanceId(completedId)
.singleResult();
runtime.signal(execution.getId());
ProcessInstance instance = runtime.createProcessInstanceQuery()
.processInstanceId(completedId)
.singleResult();
isActive = instance != null;
}
// Deleted and canceled instances shouldn't be returned
workflowEngine.cancelWorkflow(workflowEngine.createGlobalId(cancelledInstance.getId()));
workflowEngine.deleteWorkflow(workflowEngine.createGlobalId(deletedInstance.getId()));
// Validate if a workflow exists
List<WorkflowInstance> instances = workflowEngine.getCompletedWorkflows(def.getId());
assertNotNull(instances);
assertEquals(1, instances.size());
String instanceId = instances.get(0).getId();
assertEquals(completedId, BPMEngineRegistry.getLocalId(instanceId));
}
@Test
public void testGetWorkflows() throws Exception
{
WorkflowDefinition def = deployTestAdhocDefinition();
String activitiProcessDefinitionId = BPMEngineRegistry.getLocalId(def.getId());
ProcessInstance activeInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance completedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance cancelledInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
ProcessInstance deletedInstance = runtime.startProcessInstanceById(activitiProcessDefinitionId);
// Complete completedProcessInstance.
String completedId = completedInstance.getId();
boolean isActive = true;
while (isActive)
{
Execution execution = runtime.createExecutionQuery()
.processInstanceId(completedId)
.singleResult();
runtime.signal(execution.getId());
ProcessInstance instance = runtime.createProcessInstanceQuery()
.processInstanceId(completedId)
.singleResult();
isActive = instance != null;
}
// Deleted and canceled instances shouldn't be returned
workflowEngine.cancelWorkflow(workflowEngine.createGlobalId(cancelledInstance.getId()));
workflowEngine.deleteWorkflow(workflowEngine.createGlobalId(deletedInstance.getId()));
// Validate if a workflow exists
List<WorkflowInstance> instances = workflowEngine.getWorkflows(def.getId());
assertNotNull(instances);
assertEquals(2, instances.size());
String instanceId = instances.get(0).getId();
assertEquals(activeInstance.getId(), BPMEngineRegistry.getLocalId(instanceId));
instanceId = instances.get(1).getId();
assertEquals(completedId, BPMEngineRegistry.getLocalId(instanceId));
}
@Test
public void testGetWorkflowById() throws Exception
{
WorkflowDefinition def = deployTestAdhocDefinition();
Date startTime = new SimpleDateFormat("dd-MM-yyy hh:mm:ss").parse("01-01-2011 12:11:10");
ClockUtil.setCurrentTime(startTime);
// Add some variables which should be used in the WorkflowInstance
Map<String, Object> variables = new HashMap<String, Object>();
Date dueDate = Calendar.getInstance().getTime();
putVariable(variables, WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate);
putVariable(variables, WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "I'm the description");
putVariable(variables, WorkflowModel.PROP_CONTEXT, new ActivitiScriptNode(testWorkflowContext, serviceRegistry));
putVariable(variables, WorkflowModel.ASSOC_PACKAGE, new ActivitiScriptNode(testWorkflowPackage, serviceRegistry));
putVariable(variables, WorkflowModel.PROP_WORKFLOW_PRIORITY, 3);
variables.put(WorkflowConstants.PROP_INITIATOR, new ActivitiScriptNode(adminHomeNode, serviceRegistry));
ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()), variables);
String globalProcessInstanceId = BPMEngineRegistry.createGlobalId(
ActivitiConstants.ENGINE_ID, processInstance.getProcessInstanceId());
WorkflowInstance workflowInstance = workflowEngine.getWorkflowById(globalProcessInstanceId);
assertNotNull(workflowInstance);
assertEquals(globalProcessInstanceId, workflowInstance.getId());
assertNull(workflowInstance.getEndDate());
assertTrue(workflowInstance.isActive());
assertEquals("I'm the description", workflowInstance.getDescription());
assertEquals(dueDate, workflowInstance.getDueDate());
assertEquals(def.getId(), workflowInstance.getDefinition().getId());
assertEquals(adminHomeNode, workflowInstance.getInitiator());
assertEquals(testWorkflowContext, workflowInstance.getContext());
assertEquals(testWorkflowPackage, workflowInstance.getWorkflowPackage());
assertNotNull(workflowInstance.getPriority());
assertEquals(3, workflowInstance.getPriority().intValue());
assertEquals(startTime, workflowInstance.getStartDate());
// Reset current time used in activiti
ClockUtil.setCurrentTime(null);
}
@Test
public void testGetCompletedWorkflowById() throws Exception
{
WorkflowDefinition def = deployTestAdhocDefinition();
Date startTime = new SimpleDateFormat("dd-MM-yyy hh:mm:ss").parse("01-01-2011 01:02:03");
ClockUtil.setCurrentTime(startTime);
// Add some variables which should be used in the WorkflowInstance
Map<String, Object> variables = new HashMap<String, Object>();
Date dueDate = Calendar.getInstance().getTime();
putVariable(variables, WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate);
putVariable(variables, WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "I'm the description");
putVariable(variables, WorkflowModel.PROP_CONTEXT, new ActivitiScriptNode(testWorkflowContext, serviceRegistry));
putVariable(variables, WorkflowModel.ASSOC_PACKAGE, new ActivitiScriptNode(testWorkflowPackage, serviceRegistry));
putVariable(variables, WorkflowModel.PROP_WORKFLOW_PRIORITY, 3);
variables.put(WorkflowConstants.PROP_INITIATOR, new ActivitiScriptNode(adminHomeNode, serviceRegistry));
ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()), variables);
String globalProcessInstanceId = BPMEngineRegistry.createGlobalId(
ActivitiConstants.ENGINE_ID, processInstance.getProcessInstanceId());
Date endTime = new SimpleDateFormat("dd-MM-yyy hh:mm:ss").parse("01-01-2011 02:03:04");
ClockUtil.setCurrentTime(endTime);
// Finish the task
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
taskService.complete(task.getId());
WorkflowInstance workflowInstance = workflowEngine.getWorkflowById(globalProcessInstanceId);
assertNotNull(workflowInstance);
assertEquals(globalProcessInstanceId, workflowInstance.getId());
assertEquals(endTime, workflowInstance.getEndDate());
assertFalse(workflowInstance.isActive());
assertEquals("I'm the description", workflowInstance.getDescription());
assertEquals(dueDate, workflowInstance.getDueDate());
assertEquals(def.getId(), workflowInstance.getDefinition().getId());
assertEquals(adminHomeNode, workflowInstance.getInitiator());
assertEquals(testWorkflowContext, workflowInstance.getContext());
assertEquals(testWorkflowPackage, workflowInstance.getWorkflowPackage());
assertNotNull(workflowInstance.getPriority());
assertEquals(3, workflowInstance.getPriority().intValue());
assertEquals(startTime, workflowInstance.getStartDate());
// Reset current time used in activiti
ClockUtil.setCurrentTime(null);
}
@Test
public void testGetTimers() throws Exception
{
WorkflowDefinition def = deployTestJobDefinition();
ProcessInstance processInstance = runtime.startProcessInstanceById(BPMEngineRegistry.getLocalId(def.getId()));
// One timer should be active on workflow
String workflowInstanceId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID,
processInstance.getProcessInstanceId());
// Query the timer in activity to have reference
Job timerJob = managementService.createJobQuery().timers().processInstanceId(processInstance.getId()).singleResult();
String globalJobId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, timerJob.getId());
// Ask workflowEngine for timers
List<WorkflowTimer> timers = workflowEngine.getTimers(workflowInstanceId);
assertNotNull(timers);
assertEquals(1, timers.size());
WorkflowTimer timer = timers.get(0);
assertEquals(globalJobId, timer.getId());
assertEquals(timerJob.getDuedate(), timer.getDueDate());
// Check the path of the timer
String expectedTimerPathId = BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, timerJob.getExecutionId());
assertNotNull(timer.getPath());
assertEquals(expectedTimerPathId, timer.getPath().getId());
// Check the workflow-instance associated with the path
assertEquals(workflowInstanceId, timer.getPath().getInstance().getId());
// Check the task returned by the timer
Task waitingTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
assertNotNull(timer.getTask());
assertEquals(BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, waitingTask.getId()), timer.getTask().getId());
// When task with boundry-timer on it is finished, no timers should be available
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
taskService.complete(task.getId());
timers = workflowEngine.getTimers(workflowInstanceId);
assertNotNull(timers);
assertEquals(0, timers.size());
}
private void putVariable(Map<String, Object> variables, QName varName, Object value)
{
String variableName = mapQNameToName(varName);
variables.put(variableName, value);
}
private void checkDefinitions(List<WorkflowDefinition> actual,List<WorkflowDefinition> startDefs, WorkflowDefinition... expected)
{
assertEquals("The number of process definitions expected does not match the actual number!", startDefs.size() + expected.length, actual.size());
ArrayList<String> ids = new ArrayList<String>(actual.size());
for (WorkflowDefinition def : actual)
{
ids.add(def.getId());
}
for (WorkflowDefinition exp: expected)
{
assertTrue("Results did not contain expected definition: "+exp, ids.contains(exp.getId()));
}
List<String> startIds = new ArrayList<String>(startDefs.size());
for (WorkflowDefinition def : startDefs)
{
startIds.add(def.getId());
}
for (WorkflowDefinition exp: expected)
{
assertFalse("Starting Definitions should not contain expected definition: "+exp, startIds.contains(exp.getId()));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import org.alfresco.repo.workflow.WorkflowNodeConverter;
import org.alfresco.repo.workflow.WorkflowPropertyHandlerRegistry;
import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter;
/**
* @author Nick
*
*/
public class ActivitiWorkflowManager
{
private final ActivitiPropertyConverter propertyConverter;
private final WorkflowNodeConverter nodeConverter;
private final WorkflowPropertyHandlerRegistry handlerRegistry;
private final ActivitiWorkflowEngine workflowEngine;
/**
* @param workflowEngine
* @param propertyConverter
* @param handlerRegistry
* @param nodeConverter
*/
public ActivitiWorkflowManager(ActivitiWorkflowEngine workflowEngine, ActivitiPropertyConverter propertyConverter,
WorkflowPropertyHandlerRegistry handlerRegistry, WorkflowNodeConverter nodeConverter)
{
this.workflowEngine = workflowEngine;
this.propertyConverter = propertyConverter;
this.handlerRegistry = handlerRegistry;
this.nodeConverter = nodeConverter;
}
/**
* @return the propertyConverter
*/
public ActivitiPropertyConverter getPropertyConverter()
{
return propertyConverter;
}
/**
* @return the nodeConverter
*/
public WorkflowNodeConverter getNodeConverter()
{
return nodeConverter;
}
/**
* @return the handlerRegistry
*/
public WorkflowPropertyHandlerRegistry getPropertyHandlerRegistry()
{
return handlerRegistry;
}
/**
* @return the workflowEngine
*/
public ActivitiWorkflowEngine getWorkflowEngine()
{
return workflowEngine;
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import org.activiti.engine.ProcessEngine;
import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.security.authority.AuthorityDAO;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.repo.workflow.DefaultWorkflowPropertyHandler;
import org.alfresco.repo.workflow.WorkflowAuthorityManager;
import org.alfresco.repo.workflow.WorkflowObjectFactory;
import org.alfresco.repo.workflow.WorkflowPropertyHandlerRegistry;
import org.alfresco.repo.workflow.WorkflowQNameConverter;
import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.namespace.NamespaceService;
import org.springframework.beans.factory.FactoryBean;
/**
* @author Nick
*
*/
public class ActivitiWorkflowManagerFactory implements FactoryBean<ActivitiWorkflowManager>
{
// Set fields
private TenantService tenantService;
private MessageService messageService;
private ServiceRegistry serviceRegistry;
private SearchService unprotectedSearchService;
private BPMEngineRegistry bpmEngineRegistry;
private AuthorityDAO authorityDAO;
private NamespaceService namespaceService;
private DictionaryService dictionaryService;
private NodeService nodeService;
private PersonService personService;
private ProcessEngine processEngine;
private String engineId;
private String companyHomePath;
private String companyHomeStore;
/**
* {@inheritDoc}
*/
@Override
public ActivitiWorkflowManager getObject() throws Exception
{
if (messageService ==null)
{
throw new WorkflowException("MessageService not specified");
}
if (serviceRegistry ==null)
{
throw new WorkflowException("ServiceRegistry not specified");
}
if (tenantService ==null)
{
throw new WorkflowException("TenantService not specified");
}
ActivitiNodeConverter nodeConverter = new ActivitiNodeConverter(serviceRegistry);
DefaultWorkflowPropertyHandler defaultPropertyHandler = new DefaultWorkflowPropertyHandler();
defaultPropertyHandler.setMessageService(messageService);
defaultPropertyHandler.setNodeConverter(nodeConverter);
WorkflowQNameConverter qNameConverter = new WorkflowQNameConverter(namespaceService);
WorkflowPropertyHandlerRegistry handlerRegistry = new WorkflowPropertyHandlerRegistry(defaultPropertyHandler, qNameConverter);
WorkflowAuthorityManager authorityManager = new WorkflowAuthorityManager(authorityDAO);
WorkflowObjectFactory factory = new WorkflowObjectFactory(qNameConverter, tenantService, messageService, dictionaryService, engineId);
ActivitiUtil activitiUtil = new ActivitiUtil(processEngine);
ActivitiPropertyConverter propertyConverter = new ActivitiPropertyConverter(activitiUtil, factory, handlerRegistry, authorityManager, messageService, nodeConverter);
ActivitiTypeConverter typeConverter = new ActivitiTypeConverter(processEngine, factory, propertyConverter);
ActivitiWorkflowEngine workflowEngine = new ActivitiWorkflowEngine();
workflowEngine.setActivitiUtil(activitiUtil);
workflowEngine.setAuthorityManager(authorityManager);
workflowEngine.setBPMEngineRegistry(bpmEngineRegistry);
workflowEngine.setCompanyHomePath(companyHomePath);
workflowEngine.setCompanyHomeStore(companyHomeStore);
workflowEngine.setEngineId(engineId);
workflowEngine.setFactory(factory);
workflowEngine.setMessageService(messageService);
workflowEngine.setNamespaceService(namespaceService);
workflowEngine.setNodeConverter(nodeConverter);
workflowEngine.setNodeService(nodeService);
workflowEngine.setPersonService(personService);
workflowEngine.setPropertyConverter(propertyConverter);
workflowEngine.setTenantService(tenantService);
workflowEngine.setTypeConverter(typeConverter);
workflowEngine.setUnprotectedSearchService(unprotectedSearchService);
return new ActivitiWorkflowManager(workflowEngine, propertyConverter, handlerRegistry, nodeConverter);
}
/**
* @param tenantService the tenantService to set
*/
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
/**
* @param messageService the messageService to set
*/
public void setMessageService(MessageService messageService)
{
this.messageService = messageService;
}
/**
* @param serviceRegistry the serviceRegistry to set
*/
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.serviceRegistry = serviceRegistry;
}
/**
* @param unprotectedSearchService the unprotectedSearchService to set
*/
public void setUnprotectedSearchService(SearchService unprotectedSearchService)
{
this.unprotectedSearchService = unprotectedSearchService;
}
/**
* @param bpmEngineRegistry the bpmEngineRegistry to set
*/
public void setBPMEngineRegistry(BPMEngineRegistry bpmEngineRegistry)
{
this.bpmEngineRegistry = bpmEngineRegistry;
}
/**
* @param processEngine the processEngine to set
*/
public void setProcessEngine(ProcessEngine processEngine)
{
this.processEngine = processEngine;
}
/**
* @param engineId the engineId to set
*/
public void setEngineId(String engineId)
{
this.engineId = engineId;
}
/**
* @param companyHomePath the companyHomePath to set
*/
public void setCompanyHomePath(String companyHomePath)
{
this.companyHomePath = companyHomePath;
}
/**
* @param companyHomeStore the companyHomeStore to set
*/
public void setCompanyHomeStore(String companyHomeStore)
{
this.companyHomeStore = companyHomeStore;
}
/**
* @param authorityDAO
* the authorityDAO to set
*/
public void setAuthorityDAO(AuthorityDAO authorityDAO)
{
this.authorityDAO = authorityDAO;
}
/**
* {@inheritDoc}
*/
@Override
public Class<? extends ActivitiWorkflowManager> getObjectType()
{
return ActivitiWorkflowManager.class;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton()
{
return true;
}
/**
* @param namespaceService the namespaceService to set
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* @param dictionaryService the dictionaryService to set
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* @param nodeService the nodeService to set
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param personService the personService to set
*/
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.workflow.AbstractWorkflowServiceIntegrationTest;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiWorkflowServiceIntegrationTest extends AbstractWorkflowServiceIntegrationTest
{
public void testOutcome() throws Exception
{
WorkflowDefinition definition = deployDefinition("alfresco/workflow/review.bpmn20.xml");
personManager.setUser(USER1);
// Create workflow parameters
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
Serializable wfPackage = workflowService.createPackage(null);
params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage);
NodeRef assignee = personManager.get(USER2);
params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); // task instance field
WorkflowPath path = workflowService.startWorkflow(definition.getId(), params);
String instanceId = path.getInstance().getId();
WorkflowTask startTask = workflowService.getStartTask(instanceId);
workflowService.endTask(startTask.getId(), null);
List<WorkflowPath> paths = workflowService.getWorkflowPaths(instanceId);
assertEquals(1, paths.size());
path = paths.get(0);
List<WorkflowTask> tasks = workflowService.getTasksForWorkflowPath(path.getId());
assertEquals(1, tasks.size());
WorkflowTask reviewTask = tasks.get(0);
// Set the transition property
QName outcomePropName = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "reviewOutcome");
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(outcomePropName, "Approve");
workflowService.updateTask(reviewTask.getId(), props, null, null);
// End task and check outcome property
WorkflowTask result = workflowService.endTask(reviewTask.getId(), null);
Serializable outcome = result.getProperties().get(WorkflowModel.PROP_OUTCOME);
assertEquals("Approve", outcome);
}
@Override
public void testQueryTasks() {
// TODO: Revive test once all pieces of queryTasks() are finished on ActivitiWorkflowEngine
}
@Override
protected String getEngine()
{
return ActivitiConstants.ENGINE_ID;
}
@Override
protected String getTestDefinitionPath()
{
return "activiti/testTransaction.bpmn20.xml";
}
@Override
protected String getAdhocDefinitionPath()
{
return "alfresco/workflow/adhoc.bpmn20.xml";
}
@Override
protected String getPooledReviewDefinitionPath()
{
return "alfresco/workflow/review-pooled.bpmn20.xml";
}
@Override
protected String getTestTimerDefinitionPath()
{
return "activiti/testTimer.bpmn20.xml";
}
@Override
protected QName getAdhocProcessName() {
return QName.createQName("activitiAdhoc");
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.parser.BpmnParseListener;
import org.activiti.engine.impl.pvm.delegate.ActivityBehavior;
import org.activiti.engine.impl.pvm.delegate.TaskListener;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.ScopeImpl;
import org.activiti.engine.impl.pvm.process.TransitionImpl;
import org.activiti.engine.impl.repository.ProcessDefinitionEntity;
import org.activiti.engine.impl.util.xml.Element;
import org.activiti.engine.impl.variable.VariableDeclaration;
/**
* A {@link BpmnParseListener} that adds a start- and endTaskListener to
* all parsed userTasks.
*
* This is used to wire in custom logic when task is created and completed.
*
* @author Frederik Heremans
*/
public class AddTaskListenerParseListener implements BpmnParseListener
{
private TaskListener completeTaskListener;
private TaskListener createTaskListener;
@Override
public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity)
{
ActivityBehavior activitybehaviour = activity.getActivityBehavior();
if(activitybehaviour instanceof UserTaskActivityBehavior)
{
UserTaskActivityBehavior userTaskActivity = (UserTaskActivityBehavior) activitybehaviour;
if(createTaskListener != null)
{
userTaskActivity.getTaskDefinition().addTaskListener(TaskListener.EVENTNAME_CREATE, createTaskListener);
}
if(completeTaskListener != null)
{
userTaskActivity.getTaskDefinition().addTaskListener(TaskListener.EVENTNAME_COMPLETE, completeTaskListener);
}
}
}
@Override
public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition)
{
// Nothing to do here
}
@Override
public void parseStartEvent(Element startEventElement, ScopeImpl scope,
ActivityImpl startEventActivity)
{
// Nothing to do here
}
@Override
public void parseExclusiveGateway(Element exclusiveGwElement, ScopeImpl scope,
ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseParallelGateway(Element parallelGwElement, ScopeImpl scope,
ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseScriptTask(Element scriptTaskElement, ScopeImpl scope, ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseServiceTask(Element serviceTaskElement, ScopeImpl scope, ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseTask(Element taskElement, ScopeImpl scope, ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseManualTask(Element manualTaskElement, ScopeImpl scope, ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseEndEvent(Element endEventElement, ScopeImpl scope, ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseBoundaryTimerEventDefinition(Element timerEventDefinition,
boolean interrupting, ActivityImpl timerActivity)
{
// Nothing to do here
}
@Override
public void parseSubProcess(Element subProcessElement, ScopeImpl scope, ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseCallActivity(Element callActivityElement, ScopeImpl scope,
ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseProperty(Element propertyElement, VariableDeclaration variableDeclaration,
ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseSequenceFlow(Element sequenceFlowElement, ScopeImpl scopeElement,
TransitionImpl transition)
{
// Nothing to do here
}
@Override
public void parseSendTask(Element sendTaskElement, ScopeImpl scope, ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseBusinessRuleTask(Element businessRuleTaskElement, ScopeImpl scope,
ActivityImpl activity)
{
// Nothing to do here
}
@Override
public void parseBoundaryErrorEventDefinition(Element errorEventDefinition,
boolean interrupting, ActivityImpl activity,
ActivityImpl nestedErrorEventActivity)
{
// Nothing to do here
}
@Override
public void parseIntermediateTimerEventDefinition(
Element timerEventDefinition, ActivityImpl timerActivity)
{
// Nothing to do here
}
@Override
public void parseMultiInstanceLoopCharacteristics(Element activityElement,
Element multiInstanceLoopCharacteristicsElement,
ActivityImpl activity) {
// Nothing to do here
}
public void setCompleteTaskListener(TaskListener completeTaskListener)
{
this.completeTaskListener = completeTaskListener;
}
public void setCreateTaskListener(TaskListener createTaskListener)
{
this.createTaskListener = createTaskListener;
}
}

View File

@@ -0,0 +1,46 @@
package org.alfresco.repo.workflow.activiti;
import java.util.List;
import org.activiti.engine.impl.jobexecutor.JobHandler;
import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler;
import org.activiti.engine.impl.variable.SerializableType;
import org.activiti.engine.impl.variable.VariableType;
import org.activiti.spring.SpringProcessEngineConfiguration;
public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfiguration
{
private List<VariableType> customTypes;
@Override
protected void initVariableTypes()
{
super.initVariableTypes();
// Add custom types before SerializableType
if(customTypes != null)
{
int serializableIndex = variableTypes.getTypeIndex(SerializableType.TYPE_NAME);
for(VariableType type : customTypes) {
variableTypes.addType(type, serializableIndex);
}
}
}
@Override
protected void initJobExecutor() {
super.initJobExecutor();
// Get the existing timer-job handler and wrap
// with one that is alfresco-authentication aware
JobHandler jobHandler = jobHandlers.get(TimerExecuteNestedActivityJobHandler.TYPE);
JobHandler wrappingJobHandler = new AuthenticatedTimerJobHandler(jobHandler);
jobHandlers.put(TimerExecuteNestedActivityJobHandler.TYPE, wrappingJobHandler);
}
public void setCustomTypes(List<VariableType> customTypes)
{
this.customTypes = customTypes;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti;
import org.activiti.engine.impl.TaskQueryImpl;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.jobexecutor.JobHandler;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.runtime.ExecutionEntity;
import org.activiti.engine.task.Task;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
/**
* An {@link JobHandler} which executes activiti timer-jobs
* authenticated against Alfresco. It runs the timer execution
* as the task's assignee (if any) when the timer is applied to a
* task. If not, system user is used to execute timer.
*
* It wraps another JobHandler to which the actual execution is delegated to.
*
* @author Frederik Heremans
*/
public class AuthenticatedTimerJobHandler implements JobHandler
{
private JobHandler wrappedHandler;
public AuthenticatedTimerJobHandler(JobHandler jobHandler)
{
if(jobHandler == null)
{
throw new IllegalArgumentException("JobHandler to delegate to is required");
}
this.wrappedHandler = jobHandler;
}
@Override
public void execute(final String configuration, final ExecutionEntity execution,
final CommandContext commandContext)
{
String userName = null;
PvmActivity targetActivity = execution.getActivity();
if(targetActivity != null)
{
// Only try getting active task, if execution timer is waiting on is a userTask
String activityType = (String) targetActivity.getProperty(ActivitiConstants.NODE_TYPE);
if(ActivitiConstants.USER_TASK_NODE_TYPE.equals(activityType))
{
Task task = new TaskQueryImpl(commandContext)
.executionId(execution.getId())
.executeSingleResult(commandContext);
if(task != null && task.getAssignee() != null)
{
userName = task.getAssignee();
}
}
}
// When no task assignee is set, use system user to run job
if(userName == null)
{
userName = AuthenticationUtil.getSystemUserName();
}
// Execute timer
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
@SuppressWarnings("synthetic-access")
public Void doWork() throws Exception
{
wrappedHandler.execute(configuration, execution, commandContext);
return null;
}
}, userName);
}
@Override
public String getType() {
return wrappedHandler.getType();
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
<property name="databaseType" value="mysql" />
<property name="jdbcUrl" value="jdbc:mysql:///actint" />
<property name="jdbcDriver" value="org.gjt.mm.mysql.Driver" />
<property name="jdbcUsername" value="alfresco" />
<property name="jdbcPassword" value="alfresco" />
<!-- Database configurations -->
<property name="databaseSchemaUpdate" value="check-version" />
<!-- job executor configurations -->
<property name="jobExecutorActivate" value="false" />
<!-- mail server configurations -->
<property name="mailServerPort" value="5025" />
</bean>
</beans>

View File

@@ -0,0 +1,235 @@
create table ACT_GE_PROPERTY (
NAME_ varchar(255),
VALUE_ varchar(255),
REV_ integer,
primary key (NAME_)
) TYPE=InnoDB;
insert into ACT_GE_PROPERTY
values ('schema.version', '5.0.beta1', 1);
insert into ACT_GE_PROPERTY
values ('next.dbid', '1', 1);
create table ACT_GE_BYTEARRAY (
ID_ varchar(255),
REV_ integer,
NAME_ varchar(255),
DEPLOYMENT_ID_ varchar(255),
BYTES_ LONGBLOB,
primary key (ID_)
) TYPE=InnoDB;
create table ACT_RE_DEPLOYMENT (
ID_ varchar(255),
NAME_ varchar(255),
DEPLOY_TIME_ timestamp,
primary key (ID_)
) TYPE=InnoDB;
create table ACT_RU_EXECUTION (
ID_ varchar(255),
REV_ integer,
PROC_INST_ID_ varchar(255),
PARENT_ID_ varchar(255),
PROC_DEF_ID_ varchar(255),
SUPER_EXEC_ varchar(255),
ACTIVITY_ID_ varchar(255),
IS_ACTIVE_ TINYINT,
IS_CONCURRENT_ TINYINT,
IS_SCOPE_ TINYINT,
primary key (ID_)
) TYPE=InnoDB;
create table ACT_RU_JOB (
ID_ varchar(255) NOT NULL,
REV_ integer,
TYPE_ varchar(255) NOT NULL,
LOCK_EXP_TIME_ timestamp,
LOCK_OWNER_ varchar(255),
EXCLUSIVE_ boolean,
EXECUTION_ID_ varchar(255),
PROCESS_INSTANCE_ID_ varchar(255),
RETRIES_ integer,
EXCEPTION_ varchar(255),
DUEDATE_ timestamp NULL,
REPEAT_ varchar(255),
HANDLER_TYPE_ varchar(255),
HANDLER_CFG_ varchar(255),
primary key (ID_)
) TYPE=InnoDB;
create table ACT_ID_GROUP (
ID_ varchar(255),
REV_ integer,
NAME_ varchar(255),
TYPE_ varchar(255),
primary key (ID_)
) TYPE=InnoDB;
create table ACT_ID_MEMBERSHIP (
USER_ID_ varchar(255),
GROUP_ID_ varchar(255),
primary key (USER_ID_, GROUP_ID_)
) TYPE=InnoDB;
create table ACT_ID_USER (
ID_ varchar(255),
REV_ integer,
FIRST_ varchar(255),
LAST_ varchar(255),
EMAIL_ varchar(255),
PWD_ varchar(255),
primary key (ID_)
) TYPE=InnoDB;
create table ACT_RE_PROC_DEF (
ID_ varchar(255),
NAME_ varchar(255),
KEY_ varchar(255),
VERSION_ integer,
DEPLOYMENT_ID_ varchar(255),
RESOURCE_NAME_ varchar(255),
primary key (ID_)
) TYPE=InnoDB;
create table ACT_RU_TASK (
ID_ varchar(255),
REV_ integer,
EXECUTION_ID_ varchar(255),
PROC_INST_ID_ varchar(255),
PROC_DEF_ID_ varchar(255),
NAME_ varchar(255),
DESCRIPTION_ varchar(255),
FORM_ varchar(255),
ASSIGNEE_ varchar(255),
PRIORITY_ integer,
CREATE_TIME_ timestamp,
START_DEADLINE_ timestamp,
COMPLETION_DEADLINE_ timestamp,
SKIPPABLE_ TINYINT,
primary key (ID_)
) TYPE=InnoDB;
create table ACT_RU_TASKINVOLVEMENT (
ID_ varchar(255),
REV_ integer,
GROUP_ID_ varchar(255),
TYPE_ varchar(255),
USER_ID_ varchar(255),
TASK_ID_ varchar(255),
primary key (ID_)
) TYPE=InnoDB;
create table ACT_RU_VARIABLE (
ID_ varchar(255) not null,
REV_ integer,
TYPE_ varchar(255) not null,
NAME_ varchar(255) not null,
EXECUTION_ID_ varchar(255),
PROC_INST_ID_ varchar(255),
TASK_ID_ varchar(255),
BYTEARRAY_ID_ varchar(255),
DATE_ timestamp,
DOUBLE_ double,
LONG_ bigint,
TEXT_ varchar(255),
primary key (ID_)
) TYPE=InnoDB;
create table ACT_HI_PROC_INST (
ID_ varchar(255) not null,
PROC_INST_ID_ varchar(255) not null,
PROC_DEF_ID_ varchar(255) not null,
START_TIME_ datetime not null,
END_TIME_ datetime,
DURATION_ bigint,
-- TODO: check endStateName length
END_ACT_ID_ varchar(255),
primary key (ID_),
unique (PROC_INST_ID_)
) TYPE=InnoDB;
create table ACT_HI_ACT_INST (
ID_ varchar(255) not null,
ACT_ID_ varchar(255) not null,
ACT_NAME_ varchar(255),
ACT_TYPE_ varchar(255) not null,
PROC_INST_ID_ varchar(255) not null,
PROC_DEF_ID_ varchar(255) not null,
START_TIME_ datetime not null,
END_TIME_ datetime,
DURATION_ bigint,
primary key (ID_),
unique (ACT_ID_, PROC_INST_ID_)
) TYPE=InnoDB;
alter table ACT_GE_BYTEARRAY
add constraint FK_ACT_BYTEARR_DEPL
foreign key (DEPLOYMENT_ID_)
references ACT_RE_DEPLOYMENT (ID_);
alter table ACT_RU_EXECUTION
add constraint FK_ACT_EXE_PROCINST
foreign key (PROC_INST_ID_)
references ACT_RU_EXECUTION (ID_) on delete cascade on update cascade;
alter table ACT_RU_EXECUTION
add constraint FK_ACT_EXE_PARENT
foreign key (PARENT_ID_)
references ACT_RU_EXECUTION (ID_);
alter table ACT_RU_EXECUTION
add constraint FK_ACT_EXE_SUPER
foreign key (SUPER_EXEC_)
references ACT_RU_EXECUTION (ID_);
alter table ACT_ID_MEMBERSHIP
add constraint FK_ACT_MEMB_GROUP
foreign key (GROUP_ID_)
references ACT_ID_GROUP (ID_);
alter table ACT_ID_MEMBERSHIP
add constraint FK_ACT_MEMB_USER
foreign key (USER_ID_)
references ACT_ID_USER (ID_);
alter table ACT_RU_TASKINVOLVEMENT
add constraint FK_ACT_TSKASS_TASK
foreign key (TASK_ID_)
references ACT_RU_TASK (ID_);
alter table ACT_RU_TASK
add constraint FK_ACT_TASK_EXEC
foreign key (EXECUTION_ID_)
references ACT_RU_EXECUTION (ID_);
alter table ACT_RU_TASK
add constraint FK_ACT_TASK_PROCINST
foreign key (PROC_INST_ID_)
references ACT_RU_EXECUTION (ID_);
alter table ACT_RU_TASK
add constraint FK_ACT_TASK_PROCDEF
foreign key (PROC_DEF_ID_)
references ACT_RE_PROC_DEF (ID_);
alter table ACT_RU_VARIABLE
add constraint FK_ACT_VAR_TASK
foreign key (TASK_ID_)
references ACT_RU_TASK (ID_);
alter table ACT_RU_VARIABLE
add constraint FK_ACT_VAR_EXE
foreign key (EXECUTION_ID_)
references ACT_RU_EXECUTION (ID_);
alter table ACT_RU_VARIABLE
add constraint FK_ACT_VAR_PROCINST
foreign key (PROC_INST_ID_)
references ACT_RU_EXECUTION(ID_);
alter table ACT_RU_VARIABLE
add constraint FK_ACT_VAR_BYTEARRAY
foreign key (BYTEARRAY_ID_)
references ACT_GE_BYTEARRAY (ID_);

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<definitions id="adhoc-definitions"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://activiti.org/bpmn20"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn">
<process id="adhoc" name="Adhoc Process">
<startEvent id="start"
activiti:formKey="adhocStart.form" />
<sequenceFlow id='flow1'
sourceRef='start'
targetRef='adhocTask' />
<userTask id="adhocTask" name="Adhoc Task"
activiti:formKey="adhocTask.form" >
<documentation>
Perform some arbitrary human task.
</documentation>
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>${taskAssignee}</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
<sequenceFlow id='flow2'
sourceRef='adhocTask'
targetRef='verifyTaskDone' />
<userTask id="verifyTaskDone" name="Verify Adhoc Task Completed."
activiti:formKey="adhocDone.form" >
<documentation>
Verify the arbitrary task was completed.
</documentation>
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>${initiator}</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
<sequenceFlow id='flow3' sourceRef='verifyTaskDone'
targetRef='theEnd' />
<endEvent id="theEnd" />
</process>
</definitions>

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.listener;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.impl.pvm.delegate.ExecutionListener;
import org.activiti.engine.impl.pvm.delegate.ExecutionListenerExecution;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.workflow.WorkflowQNameConverter;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
import org.alfresco.repo.workflow.activiti.script.ActivitiScriptBase;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
public class ScriptExecutionListener extends ActivitiScriptBase implements ExecutionListener
{
private static final String DELETED_FLAG = "deleted";
private static final String CANCELLED_FLAG = "cancelled";
@Override
public void notify(ExecutionListenerExecution execution) throws Exception {
if(script != null)
{
String scriptString = getStringValue(script, execution);
String scriptProcessorName = getStringValue(scriptProcessor, execution);
String runAsUser = getStringValue(runAs, execution);
// Make sure there is an authenticated user for the current thread, so when
// the script is executed using no 'runAs' from a job-executor thread, the workflow
// owner us used
boolean clearAuthenticationContext = checkFullyAuthenticatedUser(execution);
// Get all activiti-defined objects
Map<String, Object> scriptModel = getInputMap(execution, runAsUser);
// Add core alfresco objects to the input-map
getServiceRegistry().getScriptService().buildCoreModel(scriptModel);
try
{
Object scriptOutput = executeScript(scriptString, scriptModel, scriptProcessorName, runAsUser);
// TODO: What to do with the script-output?
if(scriptOutput != null)
{
// delegateTask.setVariableLocal("scriptOutput", scriptOutput);
}
}
finally
{
if(clearAuthenticationContext)
{
// If the current user has been set to the Task's assignee, we should clear it agian
AuthenticationUtil.clearCurrentSecurityContext();
}
}
}
else
{
throw new IllegalArgumentException("The field 'script' should be set on the TaskListener");
}
}
protected Map<String, Object> getInputMap(ExecutionListenerExecution execution, String runAsUser)
{
HashMap<String, Object> scriptModel = new HashMap<String, Object>(1);
// Add current logged-in user and it's user home
ActivitiScriptNode personNode = getPersonNode(runAsUser);
if(personNode != null)
{
ServiceRegistry registry = getServiceRegistry();
scriptModel.put(PERSON_BINDING_NAME, personNode);
NodeRef userHomeNode = (NodeRef) registry.getNodeService().getProperty(personNode.getNodeRef(), ContentModel.PROP_HOMEFOLDER);
if (userHomeNode != null)
{
scriptModel.put(USERHOME_BINDING_NAME, new ActivitiScriptNode(userHomeNode, registry));
}
}
// Add activiti-specific objects
scriptModel.put(EXECUTION_BINDING_NAME, execution);
// Add all workflow variables to model
Map<String, Object> variables = execution.getVariables();
for(Entry<String, Object> varEntry : variables.entrySet())
{
scriptModel.put(varEntry.getKey(), varEntry.getValue());
}
// Add deleted/cancelled flags
boolean cancelled = false;
boolean deleted = false;
if(ActivitiConstants.DELETE_REASON_DELETED.equals(execution.getDeleteReason()))
{
deleted = true;
}
else if(ActivitiConstants.DELETE_REASON_CANCELLED.equals(execution.getDeleteReason()))
{
cancelled = true;
}
scriptModel.put(DELETED_FLAG, deleted);
scriptModel.put(CANCELLED_FLAG, cancelled);
return scriptModel;
}
/**
* Checks a valid Fully Authenticated User is set.
* If none is set then attempts to set the workflow owner
* @param execution the execution
* @return <code>true</code> if the Fully Authenticated User was changed, otherwise <code>false</code>.
*/
private boolean checkFullyAuthenticatedUser(final DelegateExecution execution) {
if(AuthenticationUtil.getFullyAuthenticatedUser() == null)
{
NamespaceService namespaceService = getServiceRegistry().getNamespaceService();
WorkflowQNameConverter qNameConverter = new WorkflowQNameConverter(namespaceService);
String ownerVariableName = qNameConverter.mapQNameToName(ContentModel.PROP_OWNER);
String userName = (String) execution.getVariable(ownerVariableName);
if (userName != null)
{
AuthenticationUtil.setFullyAuthenticatedUser(userName);
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.properties;
import java.io.Serializable;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.namespace.QName;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiDescriptionPropertyHandler extends ActivitiTaskPropertyHandler
{
/**
* {@inheritDoc}
*/
@Override
protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value)
{
checkType(key, value, String.class);
task.setDescription((String) value);
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value)
{
checkType(key, value, String.class);
task.setDescription((String) value);
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected QName getKey()
{
return WorkflowModel.PROP_DESCRIPTION;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.properties;
import java.io.Serializable;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.task.Task;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.namespace.QName;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiOwnerPropertyHandler extends ActivitiTaskPropertyHandler
{
/**
* {@inheritDoc}
*/
@Override
protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value)
{
//Task assignment needs to be done after setting all properties
// so it is handled in ActivitiPropertyConverter.
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value)
{
checkType(key, value, String.class);
String assignee = (String) value;
String currentAssignee = task.getAssignee();
// Only set the assignee if the value has changes to prevent
// triggering assignementhandlers when not needed
if (currentAssignee == null || !currentAssignee.equals(assignee))
{
task.setAssignee(assignee);
}
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected QName getKey()
{
return ContentModel.PROP_OWNER;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.properties;
import java.io.Serializable;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiPackagePropertyHandler extends ActivitiTaskPropertyHandler
{
private static final String PCKG_KEY = "bpm_package";
private RuntimeService runtimeService;
/**
* {@inheritDoc}
*/
@Override
protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value)
{
return handlePackage(value, task.getProcessInstanceId());
}
/**
* {@inheritDoc}
*/
@Override
protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value)
{
return handlePackage(value, task.getProcessInstanceId());
}
private Object handlePackage(Serializable value, String processId)
{
Object currentPckg = runtimeService.getVariableLocal(processId, PCKG_KEY);
// Do not change package if one already exists!
if(currentPckg == null)
{
if(value instanceof NodeRef)
{
return nodeConverter.convertNode((NodeRef)value);
}
else
{
throw getInvalidPropertyValueException(WorkflowModel.ASSOC_PACKAGE, value);
}
}
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected QName getKey()
{
return WorkflowModel.ASSOC_PACKAGE;
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.properties;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.activiti.engine.TaskService;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.impl.task.IdentityLinkEntity;
import org.activiti.engine.impl.task.TaskEntity;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.IdentityLinkType;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.WorkflowAuthorityManager;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiPooledActorsPropertyHandler extends ActivitiTaskPropertyHandler
{
private TaskService taskService;
private WorkflowAuthorityManager authorityManager;
/**
* {@inheritDoc}
*/
@Override
protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value)
{
List<IdentityLink> links = taskService.getIdentityLinksForTask(task.getId());
UserAndGroupUpdates updates = getUserAndGroupUpdates(value, links);
updateTaskCandidates(task.getId(), updates);
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value)
{
List<IdentityLinkEntity> links = ((TaskEntity)task).getIdentityLinks();
UserAndGroupUpdates updates = getUserAndGroupUpdates(value, links);
updateTaskCandidates(task, updates);
return DO_NOT_ADD;
}
@SuppressWarnings("unchecked")
private Collection<NodeRef> getNodes(Serializable value)
{
if(value instanceof Collection<?>)
{
return (Collection<NodeRef>) value;
}
if(value instanceof NodeRef)
{
return Collections.singletonList((NodeRef)value);
}
throw getInvalidPropertyValueException(WorkflowModel.ASSOC_POOLED_ACTORS, value);
}
private void updateTaskCandidates(String taskId, UserAndGroupUpdates updates)
{
// Only new candidates are present in pooledUsers and pooledGroups, create Links for these
for(String user : updates.getUsers())
{
taskService.addCandidateUser(taskId, user);
}
for(String group : updates.getGroups())
{
taskService.addCandidateGroup(taskId, group);
}
// Remove all candidates which have been removed
for(IdentityLink link : updates.getLinksToRemove())
{
if(link.getUserId() != null)
{
taskService.deleteUserIdentityLink(link.getTaskId(),
link.getUserId(), link.getType());
}
else
{
taskService.deleteGroupIdentityLink(link.getTaskId(),
link.getGroupId(), link.getType());
}
}
}
private void updateTaskCandidates(DelegateTask task, UserAndGroupUpdates updates)
{
// Only new candidates are present in pooledUsers and pooledGroups, create Links for these
for(String user : updates.getUsers())
{
task.addCandidateUser( user);
}
for(String group : updates.getGroups())
{
task.addCandidateGroup( group);
}
// Remove all candidates which have been removed
for(IdentityLink link : updates.linksToRemove)
{
if(link.getUserId() != null)
{
task.deleteUserIdentityLink(link.getUserId(), link.getType());
}
else
{
task.deleteGroupIdentityLink(link.getGroupId(), link.getType());
}
}
}
/**
* Returns a DTO containing the users and groups to add and the links to remove.
*
* @param actors
* @param links
* @return
*/
private UserAndGroupUpdates getUserAndGroupUpdates(Serializable value, Collection<? extends IdentityLink> links)
{
Collection<NodeRef> actors = getNodes(value);
List<String> users = new ArrayList<String>();
List<String> groups = new ArrayList<String>();
for (NodeRef actor : actors)
{
String authorityName = authorityManager.getAuthorityName(actor);
List<String> pooledAuthorities = authorityManager.isUser(authorityName)? users : groups;
pooledAuthorities.add(authorityName);
}
// Removes all users and groups that are already links.
// Also records links that are not part of the new set of users and
// groups, in order to delete them.
List<IdentityLink> linksToRemove = new LinkedList<IdentityLink>();
for (IdentityLink link : links)
{
if(IdentityLinkType.CANDIDATE.equals(link.getType())) {
String userId = link.getUserId();
if(userId!=null)
{
if(users.remove(userId)==false)
{
linksToRemove.add(link);
}
}
else
{
String groupId = link.getGroupId();
if(groupId!=null && groups.remove(groupId)==false)
{
linksToRemove.add(link);
}
}
}
}
return new UserAndGroupUpdates(users, groups, linksToRemove);
}
/**
* {@inheritDoc}
*/
@Override
protected QName getKey()
{
return WorkflowModel.ASSOC_POOLED_ACTORS;
}
/**
* @param taskService the taskService to set
*/
public void setTaskService(TaskService taskService)
{
this.taskService = taskService;
}
private static class UserAndGroupUpdates
{
private final List<String> users;
private final List<String> groups;
private final List<IdentityLink> linksToRemove;
public UserAndGroupUpdates(List<String> users, List<String> groups, List<IdentityLink> linksToRemove)
{
this.users = users;
this.groups = groups;
this.linksToRemove = linksToRemove;
}
/**
* @return the users
*/
public List<String> getUsers()
{
return users;
}
/**
* @return the groups
*/
public List<String> getGroups()
{
return groups;
}
/**
* @return the linksToRemove
*/
public List<IdentityLink> getLinksToRemove()
{
return linksToRemove;
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.properties;
import java.io.Serializable;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.task.Task;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.activiti.ActivitiTaskPropertyHandler;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.namespace.QName;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiPriorityPropertyHandler extends ActivitiTaskPropertyHandler
{
/**
* {@inheritDoc}
*/
@Override
protected Object handleTaskProperty(Task task, TypeDefinition type, QName key, Serializable value)
{
checkType(key, value, Integer.class);
task.setPriority((Integer) value);
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected Object handleDelegateTaskProperty(DelegateTask task, TypeDefinition type, QName key, Serializable value)
{
checkType(key, value, Integer.class);
task.setPriority((Integer) value);
return DO_NOT_ADD;
}
/**
* {@inheritDoc}
*/
@Override
protected QName getKey()
{
return WorkflowModel.PROP_PRIORITY;
}
}

View File

@@ -0,0 +1,938 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.properties;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.activiti.engine.TaskService;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.history.HistoricDetailQuery;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricVariableUpdate;
import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition;
import org.activiti.engine.impl.task.TaskEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.IdentityLinkType;
import org.activiti.engine.task.Task;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.workflow.WorkflowAuthorityManager;
import org.alfresco.repo.workflow.WorkflowConstants;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.WorkflowNodeConverter;
import org.alfresco.repo.workflow.WorkflowObjectFactory;
import org.alfresco.repo.workflow.WorkflowPropertyHandlerRegistry;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
import org.alfresco.repo.workflow.activiti.ActivitiTaskTypeManager;
import org.alfresco.repo.workflow.activiti.ActivitiUtil;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.ClassAttributeDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
/**
* @since 4.0
* @author Nick Smith
*
*/
public class ActivitiPropertyConverter
{
private static final String ERR_CONVERT_VALUE = "activiti.engine.convert.value.error";
private static final String ERR_SET_TASK_PROPS_INVALID_VALUE = "activiti.engine.set.task.properties.invalid.value";
private static final String ERR_MANDATORY_TASK_PROPERTIES_MISSING = "activiti.engine.mandatory.properties.missing";
private final ActivitiTaskTypeManager typeManager;
private final MessageService messageService;
private final WorkflowObjectFactory factory;
private final WorkflowAuthorityManager authorityManager;
private final WorkflowPropertyHandlerRegistry handlerRegistry;
private final WorkflowNodeConverter nodeConverter;
private final ActivitiUtil activitiUtil;
public ActivitiPropertyConverter(ActivitiUtil activitiUtil,
WorkflowObjectFactory factory,
WorkflowPropertyHandlerRegistry handlerRegistry,
WorkflowAuthorityManager authorityManager,
MessageService messageService,
WorkflowNodeConverter nodeConverter)
{
this.activitiUtil = activitiUtil;
this.factory = factory;
this.handlerRegistry = handlerRegistry;
this.authorityManager = authorityManager;
this.messageService = messageService;
this.nodeConverter = nodeConverter;
this.typeManager = new ActivitiTaskTypeManager(factory, activitiUtil.getFormService());
}
public Map<QName, Serializable> getTaskProperties(Task task, boolean localOnly)
{
// retrieve type definition for task
TypeDefinition taskDef = typeManager.getFullTaskDefinition(task);
Map<QName, PropertyDefinition> taskProperties = taskDef.getProperties();
Map<QName, AssociationDefinition> taskAssociations = taskDef.getAssociations();
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
TaskService taskService = activitiUtil.getTaskService();
// Get the local task variables
Map<String, Object> localVariables = taskService.getVariablesLocal(task.getId());
Map<String, Object> variables = null;
if(!localOnly)
{
variables = new HashMap<String, Object>();
variables.putAll(localVariables);
// Execution-variables should also be added, if no value is present locally
Map<String, Object> executionVariables = activitiUtil.getExecutionVariables(task.getExecutionId());
for(Entry<String, Object> entry : executionVariables.entrySet())
{
if(!localVariables.containsKey(entry.getKey()))
{
variables.put(entry.getKey(), entry.getValue());
}
}
}
else
{
// Only local variables should be used.
variables = localVariables;
}
// Map the arbitrary properties
mapArbitraryProperties(variables, properties, localVariables, taskProperties, taskAssociations);
// Map activiti task instance fields to properties
properties.put(WorkflowModel.PROP_TASK_ID, task.getId());
properties.put(WorkflowModel.PROP_DESCRIPTION, task.getDescription());
// Since the task is never started explicitally, we use the create time
properties.put(WorkflowModel.PROP_START_DATE, task.getCreateTime());
// Due date is present on the process instance
String dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE);
properties.put(WorkflowModel.PROP_DUE_DATE, (Serializable) variables.get(dueDateKey));
// Since this is a runtime-task, it's not completed yet
properties.put(WorkflowModel.PROP_COMPLETION_DATE, null);
properties.put(WorkflowModel.PROP_PRIORITY, task.getPriority());
properties.put(ContentModel.PROP_CREATED, task.getCreateTime());
properties.put(ContentModel.PROP_OWNER, task.getAssignee());
// Be sure to fetch the outcome
String outcomeVarName = factory.mapQNameToName(WorkflowModel.PROP_OUTCOME);
if(variables.get(outcomeVarName) != null)
{
properties.put(WorkflowModel.PROP_OUTCOME, (Serializable) variables.get(outcomeVarName));
}
List<IdentityLink> links = taskService.getIdentityLinksForTask(task.getId());
mapPooledActors(links, properties);
return filterTaskProperties(properties);
}
public Map<QName, Serializable> getPathProperties(String executionId)
{
Map<String, Object> variables = activitiUtil.getExecutionVariables(executionId);
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(variables.size());
for (Entry<String, Object> entry: variables.entrySet())
{
QName qNameKey = factory.mapNameToQName(entry.getKey());
Serializable value = convertPropertyValue(entry.getValue());
properties.put(qNameKey, value);
}
return properties;
}
public List<NodeRef> getPooledActorsReference(Collection<IdentityLink> links)
{
List<NodeRef> pooledActorRefs = new ArrayList<NodeRef>();
if(links != null)
{
for(IdentityLink link : links)
{
if(IdentityLinkType.CANDIDATE.equals(link.getType()))
{
String id = link.getGroupId();
if(id == null)
{
id = link.getUserId();
}
NodeRef pooledNodeRef = authorityManager.mapNameToAuthority(id);
if (pooledNodeRef != null)
{
pooledActorRefs.add(pooledNodeRef);
}
}
}
}
return pooledActorRefs;
}
private void mapPooledActors(Collection<IdentityLink> links, Map<QName, Serializable> properties)
{
List<NodeRef> pooledActorRefs = getPooledActorsReference(links);
if (pooledActorRefs != null)
{
properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable) pooledActorRefs);
}
}
public Map<QName, Serializable> getTaskProperties(DelegateTask task, TypeDefinition typeDefinition, boolean localOnly)
{
Map<QName, PropertyDefinition> taskProperties = typeDefinition.getProperties();
Map<QName, AssociationDefinition> taskAssociations = typeDefinition.getAssociations();
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
// Get the local task variables
Map<String, Object> localVariables = task.getVariablesLocal();
Map<String, Object> variables = null;
if(localOnly==false)
{
variables = new HashMap<String, Object>();
variables.putAll(localVariables);
// Execution-variables should also be added, if no value is present locally
Map<String, Object> executionVariables = task.getExecution().getVariables();
for(Entry<String, Object> entry : executionVariables.entrySet())
{
String key = entry.getKey();
if(localVariables.containsKey(key)==false)
{
variables.put(key, entry.getValue());
}
}
}
else
{
// Only local variables should be used.
variables = localVariables;
}
// Map the arbitrary properties
mapArbitraryProperties(variables, properties, localVariables, taskProperties, taskAssociations);
// Map activiti task instance fields to properties
properties.put(WorkflowModel.PROP_TASK_ID, task.getId());
properties.put(WorkflowModel.PROP_DESCRIPTION, task.getDescription());
// Since the task is never started explicitally, we use the create time
properties.put(WorkflowModel.PROP_START_DATE, task.getCreateTime());
// Due date is present on the process instance
String dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE);
if(properties.containsKey(WorkflowModel.PROP_DUE_DATE)==false)
{
properties.put(WorkflowModel.PROP_DUE_DATE, (Serializable) variables.get(dueDateKey));
}
// Since this is a runtime-task, it's not completed yet
properties.put(WorkflowModel.PROP_COMPLETION_DATE, null);
properties.put(WorkflowModel.PROP_PRIORITY, task.getPriority());
properties.put(ContentModel.PROP_CREATED, task.getCreateTime());
properties.put(ContentModel.PROP_OWNER, task.getAssignee());
// TODO: Expose in DelegateTask
Set<IdentityLink> links = ((TaskEntity)task).getCandidates();
mapPooledActors(links, properties);
return filterTaskProperties(properties);
}
@SuppressWarnings("unchecked")
public Map<QName, Serializable> getTaskProperties(HistoricTaskInstance historicTask, Map<String,Object> variables)
{
// Retrieve type definition for task, based on taskFormKey variable
String formKey = (String) variables.get(ActivitiConstants.PROP_TASK_FORM_KEY);
TypeDefinition taskDef = typeManager.getFullTaskDefinition(formKey);
Map<QName, PropertyDefinition> taskProperties = taskDef.getProperties();
Map<QName, AssociationDefinition> taskAssociations = taskDef.getAssociations();
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
// Map the arbitrary properties
mapArbitraryProperties(variables, properties, variables, taskProperties, taskAssociations);
// Map activiti task instance fields to properties
properties.put(WorkflowModel.PROP_TASK_ID, historicTask.getId());
properties.put(WorkflowModel.PROP_DESCRIPTION, historicTask.getDescription());
// Since the task is never started explicitly, we use the create time
properties.put(WorkflowModel.PROP_START_DATE, historicTask.getStartTime());
Object dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE);
properties.put(WorkflowModel.PROP_DUE_DATE, (Serializable) variables.get(dueDateKey));
properties.put(WorkflowModel.PROP_COMPLETION_DATE, historicTask.getEndTime());
// TODO: Put task priority in history - http://jira.codehaus.org/browse/ACT-484
// properties.put(WorkflowModel.PROP_PRIORITY, historicTask.getPriority());
properties.put(ContentModel.PROP_CREATED, historicTask.getStartTime());
properties.put(ContentModel.PROP_OWNER, historicTask.getAssignee());
// Be sure to fetch the outcome
String outcomeVarName = factory.mapQNameToName(WorkflowModel.PROP_OUTCOME);
if(variables.get(outcomeVarName) != null)
{
properties.put(WorkflowModel.PROP_OUTCOME, (Serializable) variables.get(outcomeVarName));
}
// History of pooled actors is stored in task variable
List<NodeRef> pooledActors = new ArrayList<NodeRef>();
List<String> pooledActorRefIds = (List<String>) variables.get(ActivitiConstants.PROP_POOLED_ACTORS_HISTORY);
if(pooledActorRefIds != null)
{
for(String nodeId : pooledActorRefIds)
{
pooledActors.add(new NodeRef(nodeId));
}
}
// Add pooled actors. When no actors are found, set empty list
properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable) pooledActors);
return filterTaskProperties(properties);
}
// /**
// * Sets all default workflow properties that should be set based on the given taskproperties.
// * When the currentValues already contains a value for a certain key, this value is retained
// * and the value in taskProperties is ignored.
// *
// * @param startTask
// * start task instance
// */
// public void setDefaultWorkflowProperties(Map<String, Object> currentValues, Map<QName, Serializable> taskProperties)
// {
// if(taskProperties != null)
// {
// String workflowDescriptionName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION);
// if (!currentValues.containsKey(workflowDescriptionName))
// {
// currentValues.put(workflowDescriptionName, taskProperties
// .get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION));
// }
// String workflowDueDateName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE);
// if (!currentValues.containsKey(workflowDueDateName))
// {
// currentValues.put(workflowDueDateName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_DUE_DATE));
// }
// String workflowPriorityName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY);
// if (!currentValues.containsKey(workflowPriorityName))
// {
// currentValues.put(workflowPriorityName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_PRIORITY));
// }
// String workflowContextName = factory.mapQNameToName(WorkflowModel.PROP_CONTEXT);
// if (!currentValues.containsKey(workflowContextName))
// {
// Serializable contextRef = taskProperties.get(WorkflowModel.PROP_CONTEXT);
// currentValues.put(workflowContextName, convertNodeRefs(false, contextRef));
// }
// String pckgName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE);
// if (!currentValues.containsKey(pckgName))
// {
// Serializable pckgNode = taskProperties.get(WorkflowModel.ASSOC_PACKAGE);
// currentValues.put(pckgName, convertNodeRefs(false, pckgNode));
// }
// }
// }
/**
* Sets Default Properties of Task
*
* @param instance
* task instance
*/
public void setDefaultTaskProperties(DelegateTask task)
{
TypeDefinition typeDefinition = typeManager.getFullTaskDefinition(task);
// Only local task properties should be set to default value
Map<QName, Serializable> existingValues = getTaskProperties(task, typeDefinition, true);
Map<QName, Serializable> defaultValues = new HashMap<QName, Serializable>();
Map<QName, PropertyDefinition> propertyDefs = typeDefinition.getProperties();
// for each property, determine if it has a default value
for (Map.Entry<QName, PropertyDefinition> entry : propertyDefs.entrySet())
{
QName key = entry.getKey();
String defaultValue = entry.getValue().getDefaultValue();
if (defaultValue != null && existingValues.get(key) == null)
{
defaultValues.put(key, defaultValue);
}
}
// Special case for task description default value
String description = (String) existingValues.get(WorkflowModel.PROP_DESCRIPTION);
if (description == null || description.length() == 0)
{
// Use the shared description set in the workflowinstance
String descriptionKey = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION);
description = (String) task.getExecution().getVariable(descriptionKey);
if (description != null && description.length() > 0)
{
defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description);
}
else
{
String processDefinitionKey = ((TaskEntity)task).getExecution().getProcessDefinition().getId();
// Revert to title in metaData
String title = factory.getTaskTitle(typeDefinition, factory.buildGlobalId(processDefinitionKey), task.getName(), task.getName());
defaultValues.put(WorkflowModel.PROP_DESCRIPTION, title);
}
}
// Assign the default values to the task
if (defaultValues.size() > 0)
{
setTaskProperties(task, defaultValues);
}
}
public Map<QName, Serializable> getStartTaskProperties(HistoricProcessInstance historicProcessInstance, String taskDefId, boolean completed)
{
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
TypeDefinition taskDef = typeManager.getStartTaskDefinition(taskDefId);
Map<QName, PropertyDefinition> taskProperties = taskDef.getProperties();
Map<QName, AssociationDefinition> taskAssociations = taskDef.getAssociations();
Map<String, Object> variables = getStartVariables(historicProcessInstance);
// Map all the properties
mapArbitraryProperties(variables, properties, variables, taskProperties, taskAssociations);
// Map activiti task instance fields to properties
properties.put(WorkflowModel.PROP_TASK_ID, ActivitiConstants.START_TASK_PREFIX + historicProcessInstance.getId());
// properties.put(WorkflowModel.PROP_DESCRIPTION, historicTask.getDescription());
properties.put(WorkflowModel.PROP_START_DATE, historicProcessInstance.getStartTime());
// Use workflow due-date at the time of starting the process
String wfDueDateKey = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE);
String dueDateKey = factory.mapQNameToName(WorkflowModel.PROP_DUE_DATE);
Serializable dueDate = (Serializable) variables.get(wfDueDateKey);
if(dueDate == null)
{
dueDate = (Serializable) variables.get(dueDateKey);
}
properties.put(WorkflowModel.PROP_DUE_DATE, dueDate);
// TODO: is it okay to use start-date?
properties.put(WorkflowModel.PROP_COMPLETION_DATE, historicProcessInstance.getStartTime());
// Use workflow priority at the time of starting the process
String priorityKey = factory.mapQNameToName(WorkflowModel.PROP_PRIORITY);
Serializable priority = (Serializable) variables.get(priorityKey);
if(priority == null)
{
String wfPriorityKey = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY);
priority = (Serializable) variables.get(wfPriorityKey);
}
properties.put(WorkflowModel.PROP_PRIORITY, priority);
properties.put(ContentModel.PROP_CREATED, historicProcessInstance.getStartTime());
// Use initiator username as owner
ActivitiScriptNode ownerNode = (ActivitiScriptNode) variables.get(WorkflowConstants.PROP_INITIATOR);
if(ownerNode != null)
{
properties.put(ContentModel.PROP_OWNER, (Serializable) ownerNode.getProperties().get("userName"));
}
if(completed)
{
// Override default 'Not Yet Started' when start-task is completed
properties.put(WorkflowModel.PROP_STATUS, WorkflowConstants.TASK_STATUS_COMPLETED);
// Outcome is default transition
properties.put(WorkflowModel.PROP_OUTCOME, ActivitiConstants.DEFAULT_TRANSITION_NAME);
}
return filterTaskProperties(properties);
}
/**
* @param historicProcessInstance
* @return
*/
public Map<String, Object> getStartVariables(HistoricProcessInstance historicProcessInstance)
{
// Get historic variable values for start-event
HistoricActivityInstance startEvent = activitiUtil.getHistoryService()
.createHistoricActivityInstanceQuery()
.processInstanceId(historicProcessInstance.getId())
.activityId(historicProcessInstance.getStartActivityId())
.singleResult();
Map<String, Object> variables = getHistoricActivityVariables(startEvent.getId());
return variables;
}
/**
* Get all variable updates for process instance, latest updates on top
*
* @param processId
* @return
*/
public Map<String, Object> getHistoricProcessVariables(String processId)
{
HistoricDetailQuery query = activitiUtil.getHistoryService()
.createHistoricDetailQuery()
.processInstanceId(processId);
return getHistoricVariables(query);
}
/**
* Get all variable updates for task instance, latest updates on top
*
* @param taskId
* @return
*/
public Map<String, Object> getHistoricTaskVariables(String taskId)
{
HistoricDetailQuery query = activitiUtil.getHistoryService()
.createHistoricDetailQuery()
.taskId(taskId);
return getHistoricVariables(query);
}
/**
* Get all variable updates for activity, latest updates on top
*
* @param taskId
* @return
*/
public Map<String, Object> getHistoricActivityVariables(String activityId)
{
HistoricDetailQuery query = activitiUtil.getHistoryService()
.createHistoricDetailQuery()
.activityInstanceId(activityId);
return getHistoricVariables(query);
}
private Map<String, Object> getHistoricVariables(HistoricDetailQuery query)
{
List<HistoricDetail> historicDetails = query.variableUpdates()
.orderByVariableName().asc()
.orderByTime().desc()
.orderByVariableRevision().desc()
.list();
return convertHistoricDetails(historicDetails);
}
private void mapArbitraryProperties(Map<String, Object> variables, Map<QName, Serializable> properties,
Map<String, Object> localVariables, Map<QName, PropertyDefinition> taskProperties, Map<QName, AssociationDefinition> taskAssociations)
{
// Map arbitrary task variables
for (Entry<String, Object> entry : variables.entrySet())
{
String key = entry.getKey();
QName qname = factory.mapNameToQName(key);
// Add variable, only if part of task definition or locally defined
// on task
if (taskProperties.containsKey(qname) || taskAssociations.containsKey(qname) || localVariables.containsKey(key))
{
Serializable value = convertPropertyValue(entry.getValue());
properties.put(qname, value);
}
}
}
/**
* Convert an Activiti variable value to an Alfresco value.
*
* @param value
* activti value
* @return alfresco value
*/
public Serializable convertPropertyValue(Object value)
{
if (value == null)
{
return null;
}
if(nodeConverter.isSupported(value))
{
return nodeConverter.convert(value);
}
if (value instanceof Serializable)
{
return (Serializable) value;
}
String msg = messageService.getMessage(ERR_CONVERT_VALUE, value);
throw new WorkflowException(msg);
}
@SuppressWarnings("unchecked")
private Map<QName, Serializable> getNewTaskProperties(Task task, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add,
Map<QName, List<NodeRef>> remove)
{
// create properties to set on task instance
Map<QName, Serializable> newProperties = properties;
if (add != null || remove != null)
{
if (newProperties == null )
{
newProperties = new HashMap<QName, Serializable>(10);
}
Map<QName, Serializable> existingProperties = getTaskProperties(task, false);
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 : 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 : toRemove.getValue())
{
existingRemove.remove(nodeRef);
}
}
}
}
}
return newProperties;
}
public void setTaskProperties(DelegateTask task, Map<QName, Serializable> properties)
{
if(properties==null || properties.isEmpty())
return;
TypeDefinition type = typeManager.getFullTaskDefinition(task);
Map<String, Object> variablesToSet = handlerRegistry.handleVariablesToSet(properties, type, task, DelegateTask.class);
if(variablesToSet.size() > 0)
{
task.setVariablesLocal(variablesToSet);
}
}
/**
* Sets the properties on the task, using Activiti API.
*/
public void setTaskProperties(Task task, Map<QName, Serializable> properties)
{
if(properties==null || properties.isEmpty())
return;
TypeDefinition type = typeManager.getFullTaskDefinition(task);
Map<String, Object> variablesToSet = handlerRegistry.handleVariablesToSet(properties, type, task, Task.class);
TaskService taskService = activitiUtil.getTaskService();
// Will be set when an assignee is present in passed properties.
taskService.saveTask(task);
// Set the collected variables on the task
taskService.setVariablesLocal(task.getId(), variablesToSet);
setTaskOwner(task, properties);
}
/**
* @param task
* @param properties
*/
private void setTaskOwner(Task task, Map<QName, Serializable> properties)
{
QName ownerKey = ContentModel.PROP_OWNER;
if (properties.containsKey(ownerKey))
{
Serializable owner = properties.get(ownerKey);
if (owner != null && !(owner instanceof String))
{
throw getInvalidPropertyValueException(ownerKey, owner);
}
String assignee = (String) owner;
String currentAssignee = task.getAssignee();
// Only set the assignee if the value has changes to prevent
// triggering assignementhandlers when not needed
if (ObjectUtils.equals(currentAssignee, assignee)==false)
{
activitiUtil.getTaskService().setAssignee(task.getId(), assignee);
}
}
}
private WorkflowException getInvalidPropertyValueException(QName key, Serializable value)
{
String msg = messageService.getMessage(ERR_SET_TASK_PROPS_INVALID_VALUE, value, key);
return new WorkflowException(msg);
}
/**
* Filter out all internal task-properties.
*
* @param properties
* @return filtered properties.
*/
private Map<QName, Serializable> filterTaskProperties(
Map<QName, Serializable> properties) {
if(properties != null)
{
properties.remove(QName.createQName(null, ActivitiConstants.PROP_POOLED_ACTORS_HISTORY));
properties.remove(QName.createQName(null, ActivitiConstants.PROP_TASK_FORM_KEY));
}
return properties;
}
/**
* Convert a list of {@link HistoricDetail} to a map with key-value pairs.
* @param details the histroicDetails. Should be a list of {@link HistoricVariableUpdate}s.
*/
public Map<String, Object> convertHistoricDetails(List<HistoricDetail> details)
{
// TODO: Sorting should be performed in query, not available right now.
Collections.sort(details, new Comparator<HistoricDetail>()
{
@Override
public int compare(HistoricDetail o1, HistoricDetail o2)
{
Long id1 = Long.valueOf(o1.getId());
Long id2 = Long.valueOf(o2.getId());
return - id1.compareTo(id2);
}
});
Map<String, Object> variables = new HashMap<String, Object>();
for(HistoricDetail detail : details)
{
HistoricVariableUpdate varUpdate = (HistoricVariableUpdate) detail;
// First value for a single key is used
if(!variables.containsKey(varUpdate.getVariableName()))
{
variables.put(varUpdate.getVariableName(), varUpdate.getValue());
}
}
return variables;
}
public Map<String, Object> getStartVariables(String processDefId, Map<QName, Serializable> properties)
{
ProcessDefinition procDef = activitiUtil.getProcessDefinition(processDefId);
String startTaskTypeName = activitiUtil.getStartTaskTypeName(processDefId);
TypeDefinition startTaskType = factory.getTaskFullTypeDefinition(startTaskTypeName, true);
// Lookup type definition for the startTask
Map<QName, PropertyDefinition> taskProperties = startTaskType.getProperties();
// Get all default values from the definitions
Map<QName, Serializable> defaultProperties = new HashMap<QName, Serializable>();
for (Map.Entry<QName, PropertyDefinition> entry : taskProperties.entrySet())
{
String defaultValue = entry.getValue().getDefaultValue();
if (defaultValue != null)
{
defaultProperties.put(entry.getKey(), defaultValue);
}
}
// Put all passed properties in map with defaults
if(properties != null)
{
defaultProperties.putAll(properties);
}
// Special case for task description default value
// Use the shared description set in the workflowinstance
String description = (String) defaultProperties.get(WorkflowModel.PROP_DESCRIPTION);
if(description == null)
{
String wfDescription = (String) defaultProperties.get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION);
String procDefKey = procDef.getKey();
ReadOnlyProcessDefinition deployedDef = activitiUtil.getDeployedProcessDefinition(processDefId);
String startEventName = deployedDef.getInitial().getId();
String wfDefKey = factory.buildGlobalId(procDefKey);
factory.getTaskDescription(startTaskType, wfDefKey, wfDescription, startEventName);
defaultProperties.put(WorkflowModel.PROP_DESCRIPTION, description);
}
return handlerRegistry.handleVariablesToSet(defaultProperties, startTaskType, null, Void.class);
}
public void checkMandatoryProperties(DelegateTask task)
{
// Check all mandatory properties are set. This is checked here instead of in
// the completeTask() to allow taskListeners to set variable values before checking
List<QName> missingProps = getMissingMandatoryTaskProperties(task);
if (missingProps != null && missingProps.size() > 0)
{
String missingPropString = StringUtils.join(missingProps.iterator(), ", ");
throw new WorkflowException(messageService.getMessage(ERR_MANDATORY_TASK_PROPERTIES_MISSING, missingPropString));
}
}
/**
* Get missing mandatory properties on Task
*
* @param task
* task instance
* @return array of missing property names (or null, if none)
*/
private List<QName> getMissingMandatoryTaskProperties(DelegateTask task)
{
TypeDefinition typeDefinition = typeManager.getFullTaskDefinition(task);
// retrieve properties of task
Map<QName, Serializable> existingValues = getTaskProperties(task, typeDefinition, false);
// retrieve definition of task
Map<QName, PropertyDefinition> propertyDefs = typeDefinition.getProperties();
Map<QName, AssociationDefinition> assocDefs = typeDefinition.getAssociations();
List<QName> missingProps = findMissingProperties(existingValues, propertyDefs);
List<QName> missingAssocs= findMissingProperties(existingValues, assocDefs);
missingProps.addAll(missingAssocs);
return missingProps;
}
private List<QName> findMissingProperties(Map<QName, Serializable> existingValues,
Map<QName, ? extends ClassAttributeDefinition> definitions)
{
// for each property, determine if it is mandatory
List<QName> missingProps = new ArrayList<QName>();
for (Map.Entry<QName, ? extends ClassAttributeDefinition> entry : definitions.entrySet())
{
QName name = entry.getKey();
// Skip System and CM properties. Why?
if ((name.getNamespaceURI().equals(NamespaceService.CONTENT_MODEL_1_0_URI)
|| (name.getNamespaceURI().equals(NamespaceService.SYSTEM_MODEL_1_0_URI))))
{
continue;
}
if (isMandatory(entry.getValue()))
{
Object value = existingValues.get(entry.getKey());
if (value == null || isEmptyString(value))
{
missingProps.add(entry.getKey());
}
}
}
return missingProps;
}
private boolean isMandatory(ClassAttributeDefinition definition)
{
if(definition instanceof PropertyDefinition)
{
PropertyDefinition propDef = (PropertyDefinition) definition;
return propDef.isMandatory();
}
AssociationDefinition assocDSef = (AssociationDefinition) definition;
return assocDSef.isTargetMandatory();
}
/**
* @param value
* @return
*/
private boolean isEmptyString(Object value)
{
if(value instanceof String)
{
String str = (String)value;
return str.isEmpty();
}
return false;
}
public Task updateTask(Task task, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add,
Map<QName, List<NodeRef>> remove)
{
Map<QName, Serializable> newProperties = getNewTaskProperties(task, properties, add, remove);
if (newProperties != null)
{
setTaskProperties(task, newProperties);
return activitiUtil.getTaskInstance(task.getId());
}
return task;
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.script;
import java.util.Map;
import org.activiti.engine.delegate.VariableScope;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.el.Expression;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.ScriptService;
import org.alfresco.service.cmr.workflow.WorkflowException;
/**
* Base class for execution scripts, using {@link ScriptService} as part of
* activiti workflow.
*
* @author Frederik Heremans
*/
public class ActivitiScriptBase {
protected static final String PERSON_BINDING_NAME = "person";
protected static final String USERHOME_BINDING_NAME = "userhome";
protected static final String EXECUTION_BINDING_NAME = "execution";
protected Expression script;
protected Expression runAs;
protected Expression scriptProcessor;
protected Object executeScript(String theScript, Map<String, Object> model, String scriptProcessorName, String runAsUser)
{
String user = AuthenticationUtil.getFullyAuthenticatedUser();
Object scriptResult = null;
if(runAsUser == null && user != null)
{
// Just execute the script using the current user
scriptResult = executeScript(theScript, model, scriptProcessorName);
}
else {
if(runAsUser != null)
{
// Check if the user used for running exists
validateRunAsUser(runAsUser);
}
else
{
// No current user is authenticated, use the system-user to execute the script
runAsUser = AuthenticationUtil.getSystemUserName();
}
executeScriptAsUser(theScript, model, scriptProcessorName, runAsUser);
}
return scriptResult;
}
protected Object executeScriptAsUser(final String theScript, final Map<String, Object> model, final String scriptProcessorName, final String runAsUser)
{
// execute as specified runAsUser
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>()
{
public Object doWork() throws Exception
{
return executeScript(theScript, model, scriptProcessorName);
}
}, runAsUser);
}
protected Object executeScript(String theScript, Map<String, Object> model, String scriptProcessorName)
{
// Execute the script using the appropriate processor
Object scriptResult = null;
if(scriptProcessorName != null)
{
scriptResult = getServiceRegistry().getScriptService().executeScriptString(scriptProcessorName, theScript, model);
}
else
{
// Use default script-processor
scriptResult = getServiceRegistry().getScriptService().executeScriptString(theScript, model);
}
return scriptResult;
}
protected String getStringValue(Expression expression, VariableScope scope) {
if(expression != null)
{
return (String) expression.getValue(scope);
}
return null;
}
protected ServiceRegistry getServiceRegistry()
{
ProcessEngineConfigurationImpl config = Context.getProcessEngineConfiguration();
if(config != null)
{
// Fetch the registry that is injected in the activiti spring-configuration
ServiceRegistry registry = (ServiceRegistry) config.getBeans().get(ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
if(registry == null)
{
throw new RuntimeException(
"Service-registry not present in ProcessEngineConfiguration beans, expected ServiceRegistry with key" +
ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
}
return registry;
}
throw new IllegalStateException("No ProcessEngineCOnfiguration found in active context");
}
/**
* Checks that the specified 'runAs' field
* specifies a valid username.
*/
private void validateRunAsUser(final String runAsUser) {
Boolean runAsExists = AuthenticationUtil.runAs(new RunAsWork<Boolean>()
{ // Validate using System user to ensure sufficient permissions available to access person node.
public Boolean doWork() throws Exception {
return getServiceRegistry().getPersonService().personExists(runAsUser);
}
}, AuthenticationUtil.getSystemUserName());
if (!runAsExists)
{
throw new WorkflowException("runas user '" + runAsUser + "' does not exist.");
}
}
protected ActivitiScriptNode getPersonNode(String runAsUser)
{
String userName = null;
if(runAsUser != null)
{
userName = runAsUser;
}
else
{
userName = AuthenticationUtil.getFullyAuthenticatedUser();
}
if(userName != null)
{
ServiceRegistry services = getServiceRegistry();
NodeRef person = services.getPersonService().getPerson(userName);
if(person !=null)
{
return new ActivitiScriptNode(person, services);
}
}
return null;
}
public void setScript(Expression script) {
this.script = script;
}
public void setRunAs(Expression runAs) {
this.runAs = runAs;
}
public void setScriptProcessor(Expression scriptProcessor) {
this.scriptProcessor = scriptProcessor;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.tasklistener;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.impl.pvm.delegate.TaskListener;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
import org.alfresco.repo.workflow.activiti.script.ActivitiScriptBase;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* A {@link TaskListener} that runs the script in property 'script'
* using scripting-language specified by 'language'
*
* @author Frederik Heremans
*/
public class ScriptTaskListener extends ActivitiScriptBase implements TaskListener
{
private static final String TASK_BINDING_NAME = "task";
@Override
public void notify(DelegateTask delegateTask)
{
if(script != null)
{
String scriptString = getStringValue(script, delegateTask);
String scriptProcessorName = getStringValue(scriptProcessor, delegateTask);
String runAsUser = getStringValue(runAs, delegateTask);
// Make sure there is an authenticated user for the current thread, so when
// the script is executed using no 'runAs' from a job-executor thread, the task's assignee
// will be the authenticated user.
boolean clearAuthenticationContext = checkFullyAuthenticatedUser(delegateTask);
// Get all activiti-defined objects
Map<String, Object> scriptModel = getInputMap(delegateTask, runAsUser);
// Add core alfresco objects to the input-map
getServiceRegistry().getScriptService().buildCoreModel(scriptModel);
try
{
Object scriptOutput = executeScript(scriptString, scriptModel, scriptProcessorName, runAsUser);
// TODO: What to do with the script-output?
if(scriptOutput != null)
{
// delegateTask.setVariableLocal("scriptOutput", scriptOutput);
}
}
finally
{
if(clearAuthenticationContext)
{
// If the current user has been set to the Task's assignee, we should clear it agian
AuthenticationUtil.clearCurrentSecurityContext();
}
}
}
else
{
throw new IllegalArgumentException("The field 'script' should be set on the TaskListener");
}
}
protected Map<String, Object> getInputMap(DelegateTask delegateTask, String runAsUser)
{
HashMap<String, Object> scriptModel = new HashMap<String, Object>(1);
// Add current logged-in user and it's user home
ActivitiScriptNode personNode = getPersonNode(runAsUser);
if(personNode != null)
{
ServiceRegistry registry = getServiceRegistry();
scriptModel.put(PERSON_BINDING_NAME, personNode);
NodeRef userHomeNode = (NodeRef) registry.getNodeService().getProperty(personNode.getNodeRef(), ContentModel.PROP_HOMEFOLDER);
if (userHomeNode != null)
{
scriptModel.put(USERHOME_BINDING_NAME, new ActivitiScriptNode(userHomeNode, registry));
}
}
// Add activiti-specific objects
scriptModel.put(TASK_BINDING_NAME, delegateTask);
scriptModel.put(EXECUTION_BINDING_NAME, delegateTask.getExecution());
// Add all workflow variables to model
Map<String, Object> variables = delegateTask.getExecution().getVariables();
for(Entry<String, Object> varEntry : variables.entrySet())
{
scriptModel.put(varEntry.getKey(), varEntry.getValue());
}
return scriptModel;
}
/**
* Checks a valid Fully Authenticated User is set.
* If none is set then attempts to set the task assignee as the Fully Authenticated User.
* @param delegateTask the delegate task
* @return <code>true</code> if the Fully Authenticated User was changed, otherwise <code>false</code>.
*/
private boolean checkFullyAuthenticatedUser(final DelegateTask delegateTask) {
if(AuthenticationUtil.getFullyAuthenticatedUser() == null)
{
String userName = delegateTask.getAssignee();
if (userName != null)
{
AuthenticationUtil.setFullyAuthenticatedUser(userName);
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.tasklistener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.impl.pvm.delegate.TaskListener;
import org.activiti.engine.impl.task.IdentityLinkEntity;
import org.activiti.engine.impl.task.TaskEntity;
import org.activiti.engine.task.IdentityLink;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.WorkflowQNameConverter;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
/**
* Tasklistener that is notified when a task completes.
*
* This will set a few properties on the task, indicating it is complete
* and preparing it for historic usage.
*
* @author Frederik Heremans
*/
public class TaskCompleteListener implements TaskListener
{
private ActivitiPropertyConverter propertyConverter;
private WorkflowQNameConverter qNameConverter;
@Override
public void notify(DelegateTask task)
{
// Check all mandatory properties are set. This is checked here instead of in
// the completeTask() to allow taskListeners to set variable values before checking
propertyConverter.checkMandatoryProperties(task);
// Set properties for ended task
Map<String, Object> endTaskVariables = new HashMap<String, Object>();
// Set task status to completed
String statusKey = qNameConverter.mapQNameToName(WorkflowModel.PROP_STATUS);
endTaskVariables.put(statusKey, "Completed");
// Add pooled actors to task-variables to be preserved in history (if any)
addPooledActorsAsVariable(task, endTaskVariables);
// Set variables locally on the task
task.setVariablesLocal(endTaskVariables);
}
private void addPooledActorsAsVariable(DelegateTask task,
Map<String, Object> variables) {
List<IdentityLinkEntity> links = ((TaskEntity)task).getIdentityLinks();
if(links.size() > 0)
{
// Add to list of IdentityLink
List<IdentityLink> identityLinks = new ArrayList<IdentityLink>();
identityLinks.addAll(links);
List<NodeRef> pooledActorRefs = propertyConverter.getPooledActorsReference(identityLinks);
// Save references as a variable
List<String> nodeIds = new ArrayList<String>();
for(NodeRef ref : pooledActorRefs)
{
nodeIds.add(ref.toString());
}
variables.put(ActivitiConstants.PROP_POOLED_ACTORS_HISTORY, nodeIds);
}
}
public void setNamespaceService(NamespaceService namespaceService)
{
this.qNameConverter = new WorkflowQNameConverter(namespaceService);
}
/**
* @param propertyConverter the propertyConverter to set
*/
public void setPropertyConverter(ActivitiPropertyConverter propertyConverter)
{
this.propertyConverter = propertyConverter;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.tasklistener;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.form.FormData;
import org.activiti.engine.impl.form.TaskFormHandler;
import org.activiti.engine.impl.pvm.delegate.TaskListener;
import org.activiti.engine.impl.task.TaskEntity;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter;
/**
* Tasklistener that is notified when a task is created. This will set all
* default properties for this task.
*
* @author Frederik Heremans
*/
public class TaskCreateListener implements TaskListener
{
private ActivitiPropertyConverter propertyConverter;
@Override
public void notify(DelegateTask task)
{
// Set all default properties, based on the type-definition
propertyConverter.setDefaultTaskProperties(task);
// The taskDefinition key is set as a variable in order to be available
// in the history
String taskFormKey = getFormKey(task);
if (taskFormKey != null)
{
task.setVariableLocal(ActivitiConstants.PROP_TASK_FORM_KEY, taskFormKey);
}
}
private String getFormKey(DelegateTask task)
{
FormData formData = null;
TaskEntity taskEntity = (TaskEntity) task;
TaskFormHandler taskFormHandler = taskEntity.getTaskDefinition().getTaskFormHandler();
if (taskFormHandler != null)
{
formData = taskFormHandler.createTaskForm(taskEntity);
if (formData != null) { return formData.getFormKey(); }
}
return null;
}
/**
* @param propertyConverter the propertyConverter to set
*/
public void setPropertyConverter(ActivitiPropertyConverter propertyConverter)
{
this.propertyConverter = propertyConverter;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.variable;
import java.util.List;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.variable.SerializableType;
import org.activiti.engine.impl.variable.ValueFields;
import org.activiti.engine.impl.variable.VariableType;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNodeList;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Custom {@link VariableType} that allows storing a list of {@link ActivitiScriptNode}s as
* a process variable in activiti.
*
* @author Frederik Heremans
*/
public class ScriptNodeListVariableType extends SerializableType
{
public static final String TYPE_NAME = "alfrescoScriptNodeList";
private ServiceRegistry serviceRegistry;
@Override
public String getTypeName()
{
return TYPE_NAME;
}
@Override
public boolean isCachable()
{
// The ActivitiScriptNodeList can be cached since it uses the serviceRegistry internally
// for resolving actual values.
return true;
}
@Override
public boolean isAbleToStore(Object value)
{
if(value == null)
{
return true;
}
return ActivitiScriptNodeList.class.isAssignableFrom(value.getClass());
}
@Override
public void setValue(Object value, ValueFields valueFields)
{
if(value != null)
{
if(!(value instanceof ActivitiScriptNodeList))
{
throw new ActivitiException("Passed value is not an instance of ActivitiScriptNodeList, cannot set variable value.");
}
// Extract all node references
List<NodeRef> nodeRefs = ((ActivitiScriptNodeList) value).getNodeReferences();
// Save the list as a serializable
super.setValue(nodeRefs, valueFields);
}
}
@Override
@SuppressWarnings("unchecked")
public Object getValue(ValueFields valueFields)
{
Object serializable = super.getValue(valueFields);
if(serializable == null)
{
return null;
}
if(!(serializable instanceof List<?>))
{
throw new ActivitiException("Serializable stored in variable is not instance of List<NodeRef>, cannot get value.");
}
ActivitiScriptNodeList scriptNodes = new ActivitiScriptNodeList();
// Wrap all node references in an ActivitiScriptNode
List<NodeRef> nodeRefs =(List<NodeRef>) serializable;
for(NodeRef ref : nodeRefs)
{
scriptNodes.add(new ActivitiScriptNode(ref, serviceRegistry));
}
return scriptNodes;
}
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.serviceRegistry = serviceRegistry;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.workflow.activiti.variable;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.variable.ValueFields;
import org.activiti.engine.impl.variable.VariableType;
import org.alfresco.repo.jscript.ScriptNode;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Custom {@link VariableType} that allows storing {@link ActivitiScriptNode} as
* a process variable in activiti, allowing node properties being used in
* scripts.
*
* @author Frederik Heremans
*/
public class ScriptNodeVariableType implements VariableType
{
public static final String TYPE_NAME = "alfrescoScriptNode";
private ServiceRegistry serviceRegistry;
@Override
public String getTypeName()
{
return TYPE_NAME;
}
@Override
public boolean isCachable()
{
// The ScriptNode can be cached since it uses the serviceRegistry internally
// for resolving actual values.
return true;
}
@Override
public boolean isAbleToStore(Object value)
{
if(value == null)
{
return true;
}
return ScriptNode.class.isAssignableFrom(value.getClass());
}
@Override
public void setValue(Object value, ValueFields valueFields)
{
String textValue = null;
if(value != null)
{
if(!(value instanceof ActivitiScriptNode))
{
throw new ActivitiException("Passed value is not an instance of ActivitiScriptNode, cannot set variable value.");
}
NodeRef reference = (((ActivitiScriptNode)value).getNodeRef());
if(reference != null)
{
// Use the string representation of the NodeRef
textValue = reference.toString();
}
}
valueFields.setTextValue(textValue);
}
@Override
public Object getValue(ValueFields valueFields)
{
ScriptNode scriptNode = null;
String nodeRefString = valueFields.getTextValue();
if(nodeRefString != null)
{
scriptNode = new ActivitiScriptNode(new NodeRef(nodeRefString), serviceRegistry);
}
return scriptNode;
}
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.serviceRegistry = serviceRegistry;
}
}