mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
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:
@@ -33,6 +33,8 @@
|
||||
class="org.alfresco.repo.workflow.activiti.AddTaskListenerParseListener">
|
||||
<property name="createTaskListener" ref="activitiCreateTaskListener" />
|
||||
<property name="completeTaskListener" ref="activitiCompleteTaskListener" />
|
||||
<property name="processCreateListener" ref="activitiProcessCreateListener" />
|
||||
|
||||
</bean>
|
||||
|
||||
<!-- -->
|
||||
@@ -178,6 +180,8 @@
|
||||
<property name="services" ref="ServiceRegistry" />
|
||||
</bean>
|
||||
|
||||
<bean id="activitiProcessCreateListener" class="org.alfresco.repo.workflow.activiti.listener.ProcessStartExecutionListener" />
|
||||
|
||||
<bean id="activitiRepositoryService" factory-bean="activitiProcessEngine"
|
||||
factory-method="getRepositoryService" />
|
||||
<bean id="activitiRuntimeService" factory-bean="activitiProcessEngine"
|
||||
|
@@ -77,7 +77,7 @@
|
||||
<property name="permissionService" ref="PermissionService" />
|
||||
<property name="transferManifestNodeFactory" ref="transferManifestNodeFactory" />
|
||||
<property name="versionService" ref="VersionService" />
|
||||
<property name="workflowEngineId" value="jbpm" />
|
||||
<property name="workflowEngineId" value="activiti" />
|
||||
</bean>
|
||||
|
||||
|
||||
|
119
config/alfresco/web-publishing-context.xml
Normal file
119
config/alfresco/web-publishing-context.xml
Normal 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>
|
@@ -6,13 +6,19 @@
|
||||
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">
|
||||
|
||||
<process id="activitiPublishWebContent" name="Publish Web Content Activiti Process">
|
||||
<process id="publishWebContent" name="Publish Web Content Activiti Process">
|
||||
|
||||
<startEvent id="start"
|
||||
activiti:formKey="pubwf:startPublish" />
|
||||
|
||||
<sequenceFlow id='flow1'
|
||||
<sequenceFlow id='flow0'
|
||||
sourceRef='start'
|
||||
targetRef='startWait' />
|
||||
|
||||
<receiveTask id="startWait" name="Receive event for start-task" />
|
||||
|
||||
<sequenceFlow id='flow1'
|
||||
sourceRef='startWait'
|
||||
targetRef='checkForScheduledTime'>
|
||||
<extensionElements>
|
||||
<!-- 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:string>
|
||||
// Check if the publish should be scheduled or performed immediatly
|
||||
if (typeof pubwf_scheduledPublishDate != 'undefined') {
|
||||
if (typeof pubwf_scheduledPublishDate != 'undefined' && pubwf_scheduledPublishDate != null) {
|
||||
execution.setVariable("schedule", true);
|
||||
} else {
|
||||
execution.setVariable("schedule", false);
|
||||
@@ -39,7 +45,7 @@
|
||||
|
||||
<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>
|
||||
</sequenceFlow>
|
||||
|
||||
@@ -60,15 +66,15 @@
|
||||
</extensionElements>
|
||||
</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>
|
||||
<timeDate>${iso8601PublishDate}</timeDate>
|
||||
</timerEventDefinition>
|
||||
</boundaryEvent>
|
||||
|
||||
<sequenceFlow id='flow5' sourceRef='schedulePublish'
|
||||
<sequenceFlow id='flow5' sourceRef='waitForScheduledTime'
|
||||
targetRef='end' />
|
||||
|
||||
<sequenceFlow id='flow6' sourceRef='publishTimer'
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
@@ -19,224 +19,21 @@
|
||||
|
||||
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 class PublishWebContentJbpmTest extends BaseSpringTest
|
||||
public class PublishWebContentJbpmTest extends PublishWebContentProcessTest
|
||||
{
|
||||
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
|
||||
protected String[] getConfigLocations()
|
||||
protected String getWorkflowDefinitionName()
|
||||
{
|
||||
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();
|
||||
return DEF_NAME;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
@@ -132,7 +132,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
|
||||
WorkflowInstance instance = workflowService.getWorkflowById(wfId);
|
||||
assertNotNull(instance);
|
||||
List<WorkflowPath> paths = workflowService.getWorkflowPaths(wfId);
|
||||
assertEquals(1, paths.size());
|
||||
assertEquals(2, paths.size());
|
||||
Map<QName, Serializable> props = workflowService.getPathProperties(paths.get(0).getId());
|
||||
assertEquals(eventNode, props.get(PROP_WF_PUBLISHING_EVENT));
|
||||
assertEquals(schedule, props.get(PROP_WF_SCHEDULED_PUBLISH_DATE));
|
||||
|
@@ -46,6 +46,7 @@ import org.activiti.engine.history.HistoricProcessInstance;
|
||||
import org.activiti.engine.history.HistoricTaskInstance;
|
||||
import org.activiti.engine.history.HistoricTaskInstanceQuery;
|
||||
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.deployer.BpmnDeployer;
|
||||
import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator;
|
||||
@@ -598,6 +599,16 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
|
||||
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)
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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
|
||||
ProcessInstance instance = runtimeService.startProcessInstanceById(processDefId, variables);
|
||||
|
||||
// Set ID of workflowinstance after ProcessInstance is created
|
||||
runtimeService.setVariable(instance.getId(), WorkflowConstants.PROP_WORKFLOW_INSTANCE_ID, createGlobalId(instance.getId()));
|
||||
if(instance.isEnded())
|
||||
{
|
||||
return typeConverter.buildCompletedPath(instance.getId(), instance.getId());
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeConverter.convert((Execution)instance);
|
||||
}
|
||||
}
|
||||
catch (ActivitiException ae)
|
||||
{
|
||||
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
|
||||
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 typeConverter.getVirtualStartTask(processInstanceId, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.alfresco.repo.workflow.activiti;
|
||||
|
||||
import org.activiti.engine.delegate.ExecutionListener;
|
||||
import org.activiti.engine.delegate.TaskListener;
|
||||
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
|
||||
import org.activiti.engine.impl.bpmn.parser.BpmnParseListener;
|
||||
@@ -42,6 +43,7 @@ public class AddTaskListenerParseListener implements BpmnParseListener
|
||||
{
|
||||
private TaskListener completeTaskListener;
|
||||
private TaskListener createTaskListener;
|
||||
private ExecutionListener processCreateListener;
|
||||
|
||||
@Override
|
||||
public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity)
|
||||
@@ -64,7 +66,7 @@ public class AddTaskListenerParseListener implements BpmnParseListener
|
||||
@Override
|
||||
public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition)
|
||||
{
|
||||
// Nothing to do here
|
||||
processDefinition.addExecutionListener(ExecutionListener.EVENTNAME_START, processCreateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -197,4 +199,9 @@ public class AddTaskListenerParseListener implements BpmnParseListener
|
||||
this.createTaskListener = createTaskListener;
|
||||
}
|
||||
|
||||
public void setProcessCreateListener(ExecutionListener processCreateListener)
|
||||
{
|
||||
this.processCreateListener = processCreateListener;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
package org.alfresco.repo.workflow.activiti.listener;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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) {
|
||||
// Convert the date to ISO-8601 format
|
||||
String convertedDate = ISO8601DateFormat.format(dateToConvert);
|
||||
|
@@ -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()));
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user