ALF-9693: Social publishing now using the activiti process definition + fixed issue with activiti process without wait-state

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30078 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Frederik Heremans
2011-08-26 07:52:20 +00:00
parent b8d0df0c95
commit 578a58bbc8
12 changed files with 566 additions and 224 deletions

View File

@@ -33,6 +33,8 @@
class="org.alfresco.repo.workflow.activiti.AddTaskListenerParseListener"> class="org.alfresco.repo.workflow.activiti.AddTaskListenerParseListener">
<property name="createTaskListener" ref="activitiCreateTaskListener" /> <property name="createTaskListener" ref="activitiCreateTaskListener" />
<property name="completeTaskListener" ref="activitiCompleteTaskListener" /> <property name="completeTaskListener" ref="activitiCompleteTaskListener" />
<property name="processCreateListener" ref="activitiProcessCreateListener" />
</bean> </bean>
<!-- --> <!-- -->
@@ -178,6 +180,8 @@
<property name="services" ref="ServiceRegistry" /> <property name="services" ref="ServiceRegistry" />
</bean> </bean>
<bean id="activitiProcessCreateListener" class="org.alfresco.repo.workflow.activiti.listener.ProcessStartExecutionListener" />
<bean id="activitiRepositoryService" factory-bean="activitiProcessEngine" <bean id="activitiRepositoryService" factory-bean="activitiProcessEngine"
factory-method="getRepositoryService" /> factory-method="getRepositoryService" />
<bean id="activitiRuntimeService" factory-bean="activitiProcessEngine" <bean id="activitiRuntimeService" factory-bean="activitiProcessEngine"

View File

@@ -77,7 +77,7 @@
<property name="permissionService" ref="PermissionService" /> <property name="permissionService" ref="PermissionService" />
<property name="transferManifestNodeFactory" ref="transferManifestNodeFactory" /> <property name="transferManifestNodeFactory" ref="transferManifestNodeFactory" />
<property name="versionService" ref="VersionService" /> <property name="versionService" ref="VersionService" />
<property name="workflowEngineId" value="jbpm" /> <property name="workflowEngineId" value="activiti" />
</bean> </bean>

View File

@@ -0,0 +1,119 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<bean id="publishingService_dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
<property name="models">
<list>
<value>alfresco/model/publishingModel.xml</value>
</list>
</property>
</bean>
<!-- Bootstrap the publishing process definition -->
<bean parent="workflowDeployer">
<property name="workflowDefinitions">
<list>
<props>
<prop key="engineId">jbpm</prop>
<prop key="location">alfresco/workflow/publish_web_content_processdefinition.xml</prop>
<prop key="mimetype">text/xml</prop>
<prop key="redeploy">true</prop>
</props>
<props>
<prop key="engineId">activiti</prop>
<prop key="location">alfresco/workflow/publish-web-content.bpmn20.xml</prop>
<prop key="mimetype">text/xml</prop>
<prop key="redeploy">true</prop>
</props>
</list>
</property>
<property name="models">
<list>
<value>alfresco/workflow/publishingWorkflowModel.xml</value>
</list>
</property>
</bean>
<bean id="publishingResourceBundles" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
<property name="resourceBundles">
<list>
<value>alfresco.messages.publishing-service</value>
</list>
</property>
</bean>
<!-- Channel Service -->
<bean id="channelService" class="org.alfresco.repo.publishing.ChannelServiceImpl">
<property name="publishingRootObject" ref="publishingRootObject" />
<property name="dictionaryService" ref="DictionaryService" />
<property name="nodeService" ref="NodeService" />
<property name="channelHelper" ref="channelHelper" />
</bean>
<!-- Channel Type Implementations -->
<bean id="baseChannelType" class="org.alfresco.repo.publishing.AbstractChannelType" abstract="true" >
<property name="channelService" ref="channelService" />
<property name="serviceRegistry" ref="ServiceRegistry" />
</bean>
<bean id="channelHelper" class="org.alfresco.repo.publishing.ChannelHelper">
<property name="nodeService" ref="NodeService" />
<property name="dictionaryService" ref="DictionaryService" />
<property name="fileFolderService" ref="FileFolderService" />
<property name="permissionService" ref="PermissionService" />
</bean>
<bean id="publishingRootObject" class="org.alfresco.repo.publishing.PublishingRootObject">
<property name="nodeService" ref="NodeService" />
<property name="namespaceService" ref="NamespaceService" />
<property name="searchService" ref="SearchService" />
<property name="publishingEventHelper" ref="publishingEventHelper" />
<property name="retryingTransactionHelper" ref="retryingTransactionHelper" />
<property name="permissionService" ref="PermissionService" />
<property name="publishingStore" value="${spaces.store}" />
<property name="publishingRootPath" value="${publishing.root}" />
</bean>
<bean id="publishingEventHelper" class="org.alfresco.repo.publishing.PublishingEventHelper">
<property name="nodeService" ref="NodeService" />
<property name="contentService" ref="ContentService" />
<property name="serializer" ref="publishingPackageSerializer" />
<property name="workflowService" ref="WorkflowService" />
<property name="permissionService" ref="PermissionService" />
<property name="transferManifestNodeFactory" ref="transferManifestNodeFactory" />
<property name="versionService" ref="VersionService" />
<property name="workflowEngineId" value="activiti" />
</bean>
<!-- Publish Event Action -->
<bean id="pub_publishEvent" class="org.alfresco.repo.publishing.PublishEventAction" >
<property name="publishingEventProcessor" ref="publishingEventProcessor" />
</bean>
<!-- Publishing Event Processor -->
<bean id="publishingEventProcessor" class="org.alfresco.repo.publishing.PublishingEventProcessor">
<property name="channelHelper" ref="channelHelper" />
<property name="channelService" ref="channelService" />
<property name="publishingEventHelper" ref="publishingEventHelper" />
<property name="nodeService" ref="NodeService" />
<property name="behaviourFilter" ref="policyBehaviourFilter" />
<property name="urlShortener" ref="urlShortener" />
<property name="dictionaryService" ref="DictionaryService" />
</bean>
<!-- Publishing Service -->
<bean id="publishingService" class="org.alfresco.repo.publishing.PublishServiceImpl">
<property name="publishingRootObject" ref="publishingRootObject" />
<property name="publishingEventHelper" ref="publishingEventHelper" />
</bean>
<bean id="publishingPackageSerializer" class="org.alfresco.repo.publishing.StandardNodeSnapshotSerializer" />
<bean id="urlShortener" class="org.alfresco.repo.urlshortening.BitlyUrlShortenerImpl" >
<property name="username" value="${urlshortening.bitly.username}" />
<property name="apiKey" value="${urlshortening.bitly.api.key}" />
</bean>
</beans>

View File

@@ -6,13 +6,19 @@
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://alfresco.org"> expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://alfresco.org">
<process id="activitiPublishWebContent" name="Publish Web Content Activiti Process"> <process id="publishWebContent" name="Publish Web Content Activiti Process">
<startEvent id="start" <startEvent id="start"
activiti:formKey="pubwf:startPublish" /> activiti:formKey="pubwf:startPublish" />
<sequenceFlow id='flow1' <sequenceFlow id='flow0'
sourceRef='start' sourceRef='start'
targetRef='startWait' />
<receiveTask id="startWait" name="Receive event for start-task" />
<sequenceFlow id='flow1'
sourceRef='startWait'
targetRef='checkForScheduledTime'> targetRef='checkForScheduledTime'>
<extensionElements> <extensionElements>
<!-- If the schedule date is null, publish should be performed immediatly. Gateway used the 'schedule' boolean --> <!-- If the schedule date is null, publish should be performed immediatly. Gateway used the 'schedule' boolean -->
@@ -20,7 +26,7 @@
<activiti:field name="script"> <activiti:field name="script">
<activiti:string> <activiti:string>
// Check if the publish should be scheduled or performed immediatly // Check if the publish should be scheduled or performed immediatly
if (typeof pubwf_scheduledPublishDate != 'undefined') { if (typeof pubwf_scheduledPublishDate != 'undefined' &amp;&amp; pubwf_scheduledPublishDate != null) {
execution.setVariable("schedule", true); execution.setVariable("schedule", true);
} else { } else {
execution.setVariable("schedule", false); execution.setVariable("schedule", false);
@@ -39,7 +45,7 @@
<exclusiveGateway id="checkForScheduledTime" name="Check Schedule" /> <exclusiveGateway id="checkForScheduledTime" name="Check Schedule" />
<sequenceFlow id='flow3' sourceRef='checkForScheduledTime' targetRef='schedulePublish' > <sequenceFlow id='flow3' sourceRef='checkForScheduledTime' targetRef='waitForScheduledTime' >
<conditionExpression xsi:type="tFormalExpression">${schedule}</conditionExpression> <conditionExpression xsi:type="tFormalExpression">${schedule}</conditionExpression>
</sequenceFlow> </sequenceFlow>
@@ -60,15 +66,15 @@
</extensionElements> </extensionElements>
</serviceTask> </serviceTask>
<receiveTask id="schedulePublish" name="Wait for publish time" /> <receiveTask id="waitForScheduledTime" name="Wait for publish time" />
<boundaryEvent id="publishTimer" cancelActivity="true" attachedToRef="schedulePublish"> <boundaryEvent id="publishTimer" cancelActivity="true" attachedToRef="waitForScheduledTime">
<timerEventDefinition> <timerEventDefinition>
<timeDate>${iso8601PublishDate}</timeDate> <timeDate>${iso8601PublishDate}</timeDate>
</timerEventDefinition> </timerEventDefinition>
</boundaryEvent> </boundaryEvent>
<sequenceFlow id='flow5' sourceRef='schedulePublish' <sequenceFlow id='flow5' sourceRef='waitForScheduledTime'
targetRef='end' /> targetRef='end' />
<sequenceFlow id='flow6' sourceRef='publishTimer' <sequenceFlow id='flow6' sourceRef='publishTimer'

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 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.publishing;
import java.util.List;
import org.alfresco.service.cmr.workflow.WorkflowPath;
/**
* @author Nick Smith
* @author Frederik Heremans
* @since 4.0
*
*/
public class PublishWebContentActivitiTest extends PublishWebContentProcessTest
{
private static final String DEF_NAME = "activiti$publishWebContent";
@Override
protected String getWorkflowDefinitionName()
{
return DEF_NAME;
}
/**
* Activiti has 2 paths: a timer-scope-path and the main execution-path
*/
protected void checkNode(String expNode)
{
List<WorkflowPath> paths = workflowService.getWorkflowPaths(instanceId);
assertEquals(2, paths.size());
WorkflowPath path = paths.get(0);
assertEquals(expNode, path.getNode().getName());
}
}

View File

@@ -19,224 +19,21 @@
package org.alfresco.repo.publishing; package org.alfresco.repo.publishing;
import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.model.ContentModel.PROP_NAME;
import static org.alfresco.repo.publishing.PublishingModel.PROP_PUBLISHING_EVENT_STATUS;
import static org.alfresco.repo.publishing.PublishingModel.PROP_WF_PUBLISHING_EVENT;
import static org.alfresco.repo.publishing.PublishingModel.PROP_WF_SCHEDULED_PUBLISH_DATE;
import static org.alfresco.repo.publishing.PublishingModel.TYPE_PUBLISHING_EVENT;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.Serializable;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.action.executer.ActionExecuter;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.publishing.Status;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
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.namespace.QName;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.GUID;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/** /**
* @author Nick Smith * @author Nick Smith
* @author Frederik Heremans
* @since 4.0 * @since 4.0
* *
*/ */
public class PublishWebContentJbpmTest extends BaseSpringTest public class PublishWebContentJbpmTest extends PublishWebContentProcessTest
{ {
private static final String DEF_NAME = "jbpm$publishWebContent"; private static final String DEF_NAME = "jbpm$publishWebContent";
private ServiceRegistry serviceRegistry;
private Repository repositoryHelper;
private ActionExecuter publishEventAction;
private NodeService nodeService;
private WorkflowService workflowService;
private RetryingTransactionHelper transactionHelper;
private NodeRef event;
private String instanceId;
private long threadId;
@Test
public void testProcessTimers() throws Exception
{
final Calendar scheduledTime = Calendar.getInstance();
scheduledTime.add(Calendar.SECOND, 5);
startWorkflowAndCommit(scheduledTime);
// Should be waiting for scheduled time.
checkNode("waitForScheduledTime");
// Wait for scheduled time to elapse.
Thread.sleep(10000);
// Should have ended
checkEnded(instanceId);
// Check the Publish Event Action was called
verify(publishEventAction).execute(any(Action.class), any(NodeRef.class));
assertFalse("The action should be run from a different Thread!", Thread.currentThread().getId()==threadId);
}
public void testProcessNoSchedule() throws Exception
{
startWorkflowAndCommit(null);
// Wait for async action to execute
Thread.sleep(500);
// Should have ended
checkEnded(instanceId);
// Check the Publish Event Action was called
verify(publishEventAction).execute(any(Action.class), any(NodeRef.class));
assertFalse("The action should be run from a different Thread!", Thread.currentThread().getId()==threadId);
}
private void checkEnded(String instanceId2)
{
WorkflowInstance instance = workflowService.getWorkflowById(instanceId2);
if(instance.isActive())
{
List<WorkflowPath> paths = workflowService.getWorkflowPaths(instance.getId());
String nodeName = paths.get(0).getNode().getName();
fail("Workflow should have ended! At node: " +nodeName);
}
}
private void startWorkflowAndCommit(final Calendar scheduledTime)
{
RetryingTransactionCallback<Void> startWorkflowCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
WorkflowPath path = startWorkflow(scheduledTime);
// End the Start task.
WorkflowTask startTask = workflowService.getStartTask(path.getInstance().getId());
workflowService.endTask(startTask.getId(), null);
return null;
}
};
transactionHelper.doInTransaction(startWorkflowCallback);
}
private WorkflowPath startWorkflow(Calendar scheduledTime)
{
WorkflowDefinition definition = workflowService.getDefinitionByName(DEF_NAME);
assertNotNull("The definition is null!", definition);
NodeRef pckg = workflowService.createPackage(null);
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
params.put(PROP_WF_PUBLISHING_EVENT, event);
params.put(PROP_WF_SCHEDULED_PUBLISH_DATE, scheduledTime);
params.put(WorkflowModel.ASSOC_PACKAGE, pckg);
WorkflowPath path = workflowService.startWorkflow(definition.getId(), params);
assertNotNull(path);
this.instanceId = path.getInstance().getId();
return path;
}
private void checkNode(String expNode)
{
List<WorkflowPath> paths = workflowService.getWorkflowPaths(instanceId);
assertEquals(1, paths.size());
WorkflowPath path = paths.get(0);
assertEquals(expNode, path.getNode().getName());
}
@Before
public void onSetUp()
{
serviceRegistry = (ServiceRegistry)getApplicationContext().getBean("ServiceRegistry");
repositoryHelper = (Repository) getApplicationContext().getBean("repositoryHelper");
publishEventAction = (ActionExecuter) getApplicationContext().getBean("pub_publishEvent");
reset(publishEventAction);
ActionDefinition actionDef = mock(ActionDefinition.class);
when(publishEventAction.getActionDefinition()).thenReturn(actionDef);
// Record thread action is run in.
Mockito.doAnswer(new Answer<Void>()
{
public Void answer(InvocationOnMock invocation) throws Throwable
{
threadId = Thread.currentThread().getId();
return null;
}
}).when(publishEventAction).execute(any(Action.class), any(NodeRef.class));
this.workflowService = serviceRegistry.getWorkflowService();
this.nodeService = serviceRegistry.getNodeService();
this.transactionHelper = serviceRegistry.getRetryingTransactionHelper();
AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName());
NodeRef companyHome = repositoryHelper.getCompanyHome();
String name = GUID.generate();
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(PROP_NAME, name);
props.put(PROP_PUBLISHING_EVENT_STATUS, Status.SCHEDULED.name());
QName assocName = QName.createQNameWithValidLocalName(PublishingModel.NAMESPACE, name);
ChildAssociationRef eventAssoc = nodeService.createNode(companyHome, ASSOC_CONTAINS, assocName, TYPE_PUBLISHING_EVENT, props);
this.event = eventAssoc.getChildRef();
}
@Override @Override
protected String[] getConfigLocations() protected String getWorkflowDefinitionName()
{ {
return new String[] return DEF_NAME;
{
ApplicationContextHelper.CONFIG_LOCATIONS[0], "classpath:test/alfresco/test-web-publishing--workflow-context.xml"
};
}
@After
public void onTearDown()
{
try
{
workflowService.cancelWorkflow(instanceId);
}
catch (Exception e)
{
// NOOP
}
nodeService.deleteNode(event);
AuthenticationUtil.clearCurrentSecurityContext();
} }
} }

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2005-2011 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.publishing;
import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.model.ContentModel.PROP_NAME;
import static org.alfresco.repo.publishing.PublishingModel.PROP_PUBLISHING_EVENT_STATUS;
import static org.alfresco.repo.publishing.PublishingModel.PROP_WF_PUBLISHING_EVENT;
import static org.alfresco.repo.publishing.PublishingModel.PROP_WF_SCHEDULED_PUBLISH_DATE;
import static org.alfresco.repo.publishing.PublishingModel.TYPE_PUBLISHING_EVENT;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.Serializable;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.action.executer.ActionExecuter;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.publishing.Status;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
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.namespace.QName;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.GUID;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* @author Nick Smith
* @author Frederik Heremans
*
* @since 4.0
*
*/
public abstract class PublishWebContentProcessTest extends BaseSpringTest
{
protected ServiceRegistry serviceRegistry;
protected Repository repositoryHelper;
protected ActionExecuter publishEventAction;
protected NodeService nodeService;
protected WorkflowService workflowService;
protected RetryingTransactionHelper transactionHelper;
protected NodeRef event;
protected String instanceId;
protected long threadId;
/**
* Get definition to use
*/
protected abstract String getWorkflowDefinitionName();
@Test
public void testProcessTimers() throws Exception
{
final Calendar scheduledTime = Calendar.getInstance();
scheduledTime.add(Calendar.SECOND, 5);
startWorkflowAndCommit(scheduledTime);
// Should be waiting for scheduled time.
checkNode("waitForScheduledTime");
// Wait for scheduled time to elapse.
Thread.sleep(10000);
// Should have ended
checkEnded(instanceId);
// Check the Publish Event Action was called
verify(publishEventAction).execute(any(Action.class), any(NodeRef.class));
assertFalse("The action should be run from a different Thread!", Thread.currentThread().getId()==threadId);
}
public void testProcessNoSchedule() throws Exception
{
startWorkflowAndCommit(null);
// Wait for async action to execute
Thread.sleep(500);
// Should have ended
checkEnded(instanceId);
// Check the Publish Event Action was called
verify(publishEventAction).execute(any(Action.class), any(NodeRef.class));
assertFalse("The action should be run from a different Thread!", Thread.currentThread().getId()==threadId);
}
private void checkEnded(String instanceId2)
{
WorkflowInstance instance = workflowService.getWorkflowById(instanceId2);
if(instance.isActive())
{
List<WorkflowPath> paths = workflowService.getWorkflowPaths(instance.getId());
String nodeName = paths.get(0).getNode().getName();
fail("Workflow should have ended! At node: " +nodeName);
}
}
private void startWorkflowAndCommit(final Calendar scheduledTime)
{
RetryingTransactionCallback<Void> startWorkflowCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
WorkflowPath path = startWorkflow(scheduledTime);
// End the Start task.
WorkflowTask startTask = workflowService.getStartTask(path.getInstance().getId());
workflowService.endTask(startTask.getId(), null);
return null;
}
};
transactionHelper.doInTransaction(startWorkflowCallback);
}
private WorkflowPath startWorkflow(Calendar scheduledTime)
{
WorkflowDefinition definition = workflowService.getDefinitionByName(getWorkflowDefinitionName());
assertNotNull("The definition is null!", definition);
NodeRef pckg = workflowService.createPackage(null);
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
params.put(PROP_WF_PUBLISHING_EVENT, event);
params.put(PROP_WF_SCHEDULED_PUBLISH_DATE, scheduledTime);
params.put(WorkflowModel.ASSOC_PACKAGE, pckg);
WorkflowPath path = workflowService.startWorkflow(definition.getId(), params);
assertNotNull(path);
this.instanceId = path.getInstance().getId();
return path;
}
protected void checkNode(String expNode)
{
List<WorkflowPath> paths = workflowService.getWorkflowPaths(instanceId);
assertEquals(1, paths.size());
WorkflowPath path = paths.get(0);
assertEquals(expNode, path.getNode().getName());
}
@Before
public void onSetUp()
{
serviceRegistry = (ServiceRegistry)getApplicationContext().getBean("ServiceRegistry");
repositoryHelper = (Repository) getApplicationContext().getBean("repositoryHelper");
publishEventAction = (ActionExecuter) getApplicationContext().getBean("pub_publishEvent");
reset(publishEventAction);
ActionDefinition actionDef = mock(ActionDefinition.class);
when(publishEventAction.getActionDefinition()).thenReturn(actionDef);
// Record thread action is run in.
Mockito.doAnswer(new Answer<Void>()
{
public Void answer(InvocationOnMock invocation) throws Throwable
{
threadId = Thread.currentThread().getId();
return null;
}
}).when(publishEventAction).execute(any(Action.class), any(NodeRef.class));
this.workflowService = serviceRegistry.getWorkflowService();
this.nodeService = serviceRegistry.getNodeService();
this.transactionHelper = serviceRegistry.getRetryingTransactionHelper();
AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName());
NodeRef companyHome = repositoryHelper.getCompanyHome();
String name = GUID.generate();
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(PROP_NAME, name);
props.put(PROP_PUBLISHING_EVENT_STATUS, Status.SCHEDULED.name());
QName assocName = QName.createQNameWithValidLocalName(PublishingModel.NAMESPACE, name);
ChildAssociationRef eventAssoc = nodeService.createNode(companyHome, ASSOC_CONTAINS, assocName, TYPE_PUBLISHING_EVENT, props);
this.event = eventAssoc.getChildRef();
}
@Override
protected String[] getConfigLocations()
{
return new String[]
{
ApplicationContextHelper.CONFIG_LOCATIONS[0], "classpath:test/alfresco/test-web-publishing--workflow-context.xml"
};
}
@After
public void onTearDown()
{
try
{
workflowService.cancelWorkflow(instanceId);
}
catch (Exception e)
{
// NOOP
}
nodeService.deleteNode(event);
AuthenticationUtil.clearCurrentSecurityContext();
}
}

View File

@@ -132,7 +132,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
WorkflowInstance instance = workflowService.getWorkflowById(wfId); WorkflowInstance instance = workflowService.getWorkflowById(wfId);
assertNotNull(instance); assertNotNull(instance);
List<WorkflowPath> paths = workflowService.getWorkflowPaths(wfId); List<WorkflowPath> paths = workflowService.getWorkflowPaths(wfId);
assertEquals(1, paths.size()); assertEquals(2, paths.size());
Map<QName, Serializable> props = workflowService.getPathProperties(paths.get(0).getId()); Map<QName, Serializable> props = workflowService.getPathProperties(paths.get(0).getId());
assertEquals(eventNode, props.get(PROP_WF_PUBLISHING_EVENT)); assertEquals(eventNode, props.get(PROP_WF_PUBLISHING_EVENT));
assertEquals(schedule, props.get(PROP_WF_SCHEDULED_PUBLISH_DATE)); assertEquals(schedule, props.get(PROP_WF_SCHEDULED_PUBLISH_DATE));

View File

@@ -46,6 +46,7 @@ import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance; import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricTaskInstanceQuery; import org.activiti.engine.history.HistoricTaskInstanceQuery;
import org.activiti.engine.impl.RepositoryServiceImpl; import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.bpmn.behavior.ReceiveTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior; import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.deployer.BpmnDeployer; import org.activiti.engine.impl.bpmn.deployer.BpmnDeployer;
import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator; import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator;
@@ -598,6 +599,16 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
return null; return null;
} }
private boolean isReceiveTask(PvmActivity act)
{
if(act instanceof ActivityImpl)
{
ActivityImpl actImpl = (ActivityImpl) act;
return (actImpl.getActivityBehavior() instanceof ReceiveTaskActivityBehavior);
}
return false;
}
private Collection<PvmActivity> findUserTasks(PvmActivity startEvent) private Collection<PvmActivity> findUserTasks(PvmActivity startEvent)
{ {
// Use a linked hashmap to get the task defs in the right order // Use a linked hashmap to get the task defs in the right order
@@ -609,6 +620,17 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
return userTasks.values(); return userTasks.values();
} }
private boolean isFirstActivity(PvmActivity activity, ReadOnlyProcessDefinition procDef)
{
if(procDef.getInitial().getOutgoingTransitions().size() == 1)
{
if(procDef.getInitial().getOutgoingTransitions().get(0).getDestination().equals(activity)) {
return true;
}
}
return false;
}
private void findUserTasks(PvmActivity currentActivity, Map<String, PvmActivity> userTasks) private void findUserTasks(PvmActivity currentActivity, Map<String, PvmActivity> userTasks)
{ {
// Only process activity if not already present to prevent endless loops // Only process activity if not already present to prevent endless loops
@@ -974,11 +996,15 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
// Start the process-instance // Start the process-instance
ProcessInstance instance = runtimeService.startProcessInstanceById(processDefId, variables); ProcessInstance instance = runtimeService.startProcessInstanceById(processDefId, variables);
if(instance.isEnded())
// Set ID of workflowinstance after ProcessInstance is created {
runtimeService.setVariable(instance.getId(), WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID, createGlobalId(instance.getId())); return typeConverter.buildCompletedPath(instance.getId(), instance.getId());
}
else
{
return typeConverter.convert((Execution)instance); return typeConverter.convert((Execution)instance);
} }
}
catch (ActivitiException ae) catch (ActivitiException ae)
{ {
String msg = messageService.getMessage(ERR_START_WORKFLOW, workflowDefinitionId); String msg = messageService.getMessage(ERR_START_WORKFLOW, workflowDefinitionId);
@@ -1299,10 +1325,37 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
// Set start task end date on the process // Set start task end date on the process
runtimeService.setVariable(processInstanceId, ActivitiConstants.PROP_START_TASK_END_DATE, new Date()); runtimeService.setVariable(processInstanceId, ActivitiConstants.PROP_START_TASK_END_DATE, new Date());
// Check if the current activity is a signalTask and the first activity in the process,
// this is a workaround for processes without any task/waitstates that should otherwise end
// when they are started.
ProcessInstance processInstance = activitiUtil.getProcessInstance(processInstanceId);
String currentActivity = ((ExecutionEntity)processInstance).getActivityId();
ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
PvmActivity activity = procDef.findActivity(currentActivity);
if(isReceiveTask(activity) && isFirstActivity(activity, procDef))
{
// Signal the process to start flowing, beginning from the recieve task
runtimeService.signal(processInstanceId);
// It's possible the process has ended after signalling the receive task
processInstance = activitiUtil.getProcessInstance(processInstanceId);
if(processInstance != null) {
return typeConverter.getVirtualStartTask(processInstanceId, false);
}
else
{
return typeConverter.getVirtualStartTask(activitiUtil.getHistoricProcessInstance(processInstanceId));
}
}
else
{
// Return virtual start task for the execution, it's safe to use the processInstanceId // Return virtual start task for the execution, it's safe to use the processInstanceId
return typeConverter.getVirtualStartTask(processInstanceId, false); return typeConverter.getVirtualStartTask(processInstanceId, false);
} }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View File

@@ -18,6 +18,7 @@
*/ */
package org.alfresco.repo.workflow.activiti; package org.alfresco.repo.workflow.activiti;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.TaskListener; import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior; import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.parser.BpmnParseListener; import org.activiti.engine.impl.bpmn.parser.BpmnParseListener;
@@ -42,6 +43,7 @@ public class AddTaskListenerParseListener implements BpmnParseListener
{ {
private TaskListener completeTaskListener; private TaskListener completeTaskListener;
private TaskListener createTaskListener; private TaskListener createTaskListener;
private ExecutionListener processCreateListener;
@Override @Override
public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity)
@@ -64,7 +66,7 @@ public class AddTaskListenerParseListener implements BpmnParseListener
@Override @Override
public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition) public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition)
{ {
// Nothing to do here processDefinition.addExecutionListener(ExecutionListener.EVENTNAME_START, processCreateListener);
} }
@Override @Override
@@ -197,4 +199,9 @@ public class AddTaskListenerParseListener implements BpmnParseListener
this.createTaskListener = createTaskListener; this.createTaskListener = createTaskListener;
} }
public void setProcessCreateListener(ExecutionListener processCreateListener)
{
this.processCreateListener = processCreateListener;
}
} }

View File

@@ -19,6 +19,7 @@
package org.alfresco.repo.workflow.activiti.listener; package org.alfresco.repo.workflow.activiti.listener;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.DelegateExecution;
@@ -61,7 +62,19 @@ public class ConvertDateToISO8601 implements ExecutionListener
throw new IllegalArgumentException("Both fields 'source' and 'target' shoudl be set"); throw new IllegalArgumentException("Both fields 'source' and 'target' shoudl be set");
} }
Date dateToConvert = (Date) execution.getVariable(sourceVarName); Object dateVar = execution.getVariable(sourceVarName);
Date dateToConvert = null;
// Accept null, Date or Calendar as value
if(dateVar != null) {
if(dateVar instanceof Date) {
dateToConvert = (Date) execution.getVariable(sourceVarName);
} else if(dateVar instanceof Calendar) {
dateToConvert = ((Calendar) execution.getVariable(sourceVarName)).getTime();
} else {
throw new IllegalArgumentException("Variable with name: " + sourceVarName + " must be a Date or a Calendar");
}
}
if(dateToConvert != null) { if(dateToConvert != null) {
// Convert the date to ISO-8601 format // Convert the date to ISO-8601 format
String convertedDate = ISO8601DateFormat.format(dateToConvert); String convertedDate = ISO8601DateFormat.format(dateToConvert);

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2005-2011 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 org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.repo.workflow.WorkflowConstants;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
/**
* An {@link ExecutionListener} that set all additional variables that are needed
* when process starts.
*
* @author Frederik Heremans
*/
public class ProcessStartExecutionListener implements ExecutionListener
{
public void notify(DelegateExecution execution) throws Exception
{
// Add the workflow ID
execution.setVariable(WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID, BPMEngineRegistry
.createGlobalId(ActivitiConstants.ENGINE_ID, execution.getId()));
}
}