- jBPM 3.1.2 to 3.2 upgrade

- Implementation of Workflow Timers (primarily for WCM content launch, but also useful in other scenarios)

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5578 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Caruana
2007-04-30 17:21:52 +00:00
parent 9878ca5b2a
commit 7567867be1
37 changed files with 1030 additions and 97 deletions

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.jscript;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -69,6 +70,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
private static final String IMPORT_RESOURCE = "resource=\"";
private static final String PATH_CLASSPATH = "classpath:";
private static final String SCRIPT_ROOT = "_root";
/** Base Value Converter */
private ValueConverter valueConverter = new ReturnValueConverter();
/** Store into which to resolve cm:name based script paths */
private StoreRef storeRef;
@@ -506,7 +510,11 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
Object result = cx.evaluateString(scope, script, "AlfrescoScript", 1, null);
// extract java object result if wrapped by Rhino
if (result instanceof Wrapper)
if (result instanceof Serializable)
{
result = valueConverter.convertValueForRepo((Serializable)result);
}
else if (result instanceof Wrapper)
{
result = ((Wrapper)result).unwrap();
}
@@ -556,4 +564,30 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
}
return newModel;
}
/**
* Value conversion for handling Javascript return values.
*/
public class ReturnValueConverter extends ValueConverter
{
/**
* Convert an object from any script wrapper value to a valid repository serializable value.
* This includes converting JavaScript Array objects to Lists of valid objects.
*
* @param value Value to convert from script wrapper object to repo serializable value
*
* @return valid repo value
*/
public Serializable convertValueForRepo(Serializable value)
{
if (value instanceof Wrapper ||
value instanceof ScriptableObject ||
value instanceof Serializable[])
{
value = super.convertValueForRepo(value);
}
return value;
}
}
}

View File

@@ -600,6 +600,10 @@ public class WorkflowInterpreter
currentPath = null;
out.print(executeCommand("show definitions"));
}
else
{
return "Syntax Error.\n";
}
}
else if (command[0].equals("use"))

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.workflow.jbpm;
import java.util.Date;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.jbpm.calendar.BusinessCalendar;
import org.jbpm.calendar.Duration;
import org.jbpm.graph.def.GraphElement;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.job.Timer;
import org.jbpm.jpdl.el.impl.JbpmExpressionEvaluator;
import org.jbpm.scheduler.def.CreateTimerAction;
/**
* Extended Create Timer action for supporting Alfresco implemented timers.
*
* Alfresco timer supports ability to provide due date expression that can
* evaluate to a date.
*
* @author davidc
*/
public class AlfrescoCreateTimerAction extends CreateTimerAction
{
private static final long serialVersionUID = -7427571820130058416L;
protected static BusinessCalendar businessCalendar = new BusinessCalendar();
/* (non-Javadoc)
* @see org.jbpm.scheduler.def.CreateTimerAction#createTimer(org.jbpm.graph.exe.ExecutionContext)
*/
@Override
protected Timer createTimer(ExecutionContext executionContext)
{
Date dueDate = null;
String dueDateExpression = getDueDate();
if (dueDateExpression.startsWith("#{"))
{
Object result = JbpmExpressionEvaluator.evaluate(dueDateExpression, executionContext);
if (!(result instanceof Date))
{
throw new WorkflowException("duedate expression must evaluate to a date");
}
dueDate = (Date)result;
}
else
{
Duration duration = new Duration(getDueDate());
dueDate = businessCalendar.add(new Date(), duration);
}
Timer timer = new AlfrescoTimer(executionContext.getToken());
timer.setName(getTimerName());
timer.setRepeat(getRepeat());
timer.setDueDate(dueDate);
timer.setAction(getTimerAction());
timer.setTransitionName(getTransitionName());
timer.setGraphElement(executionContext.getEventSource());
timer.setTaskInstance(executionContext.getTaskInstance());
// if this action was executed for a graph element
if ((getEvent() != null) && (getEvent().getGraphElement() != null))
{
GraphElement graphElement = getEvent().getGraphElement();
try
{
executionContext.setTimer(timer);
// fire the create timer event on the same graph element
graphElement.fireEvent("timer-create", executionContext);
}
finally
{
executionContext.setTimer(null);
}
}
return timer;
}
}

View File

@@ -101,7 +101,7 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler
while (iter.hasNext())
{
Element element = iter.next();
if (element.getNodeType() == Element.ELEMENT_NODE)
if (element.getNodeType() == org.dom4j.Node.ELEMENT_NODE)
{
isTextOnly = false;
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.workflow.jbpm;
import java.io.InputStream;
import org.hibernate.SessionFactory;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.configuration.ObjectFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springmodules.workflow.jbpm31.JbpmFactoryLocator;
/**
* Implementation of Spring Module's JbpmConfigurationFactoryBean for
* Jbpm 3.2.
*
* @author Costin Leau
* @author davidc
*/
public class AlfrescoJbpmConfigurationFactoryBean implements InitializingBean, FactoryBean, BeanFactoryAware, BeanNameAware
{
private JbpmConfiguration jbpmConfiguration;
private ObjectFactory objectFactory;
private Resource configuration;
private SessionFactory sessionFactory;
private String contextName = JbpmContext.DEFAULT_JBPM_CONTEXT_NAME;
/**
* FactoryLocator
*/
private JbpmFactoryLocator factoryLocator = new JbpmFactoryLocator();
/* (non-Javadoc)
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
public void setBeanFactory(BeanFactory beanFactory) throws BeansException
{
factoryLocator.setBeanFactory(beanFactory);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
*/
public void setBeanName(String name)
{
factoryLocator.setBeanName(name);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception
{
if (configuration == null)
throw new IllegalArgumentException("configuration or objectFactory property need to be not null");
// 1. Construct Jbpm Configuration
// NOTE: Jbpm 3.2 adds a JbpmConfiguration value to its context
InputStream stream = configuration.getInputStream();
jbpmConfiguration = JbpmConfiguration.parseInputStream(stream);
// 2. inject the HB session factory if it is the case
if (sessionFactory != null)
{
JbpmContext context = jbpmConfiguration.createJbpmContext(contextName);
try
{
context.setSessionFactory(sessionFactory);
} finally
{
context.close();
}
}
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public Object getObject() throws Exception
{
return jbpmConfiguration;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class getObjectType()
{
return JbpmConfiguration.class;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton()
{
return true;
}
/**
* @return Returns the configuration.
*/
public Resource getConfiguration()
{
return configuration;
}
/**
* @param configuration The configuration to set
*/
public void setConfiguration(Resource configuration)
{
this.configuration = configuration;
}
/**
* @return Returns the objectFactory.
*/
public ObjectFactory getObjectFactory()
{
return objectFactory;
}
/**
* @param objectFactory The objectFactory to set
*/
public void setObjectFactory(ObjectFactory objectFactory)
{
this.objectFactory = objectFactory;
}
/**
* @return Returns the contextName.
*/
public String getContextName()
{
return contextName;
}
/**
* @param contextName The contextName to set
*/
public void setContextName(String contextName)
{
this.contextName = contextName;
}
/**
* @return Returns the sessionFactory.
*/
public SessionFactory getSessionFactory()
{
return sessionFactory;
}
/**
* @param sessionFactory The sessionFactory to set
*/
public void setSessionFactory(SessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.workflow.jbpm;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.jbpm.JbpmContext;
import org.jbpm.graph.exe.Token;
import org.jbpm.job.Timer;
import org.jbpm.taskmgmt.exe.TaskInstance;
/**
* Extended JBPM Timer that provides Alfresco context.
*
* NOTE: The action triggered by the timer is executed as the user assigned
* to the task associated with the timer. If not associated with a
* task, the timer is executed unauthenticated.
*
* @author davidc
*/
public class AlfrescoTimer extends Timer
{
private static final long serialVersionUID = -6618486175822866286L;
/**
* Construct
*/
public AlfrescoTimer()
{
super();
}
/**
* Construct
*
* @param token
*/
public AlfrescoTimer(Token token)
{
super(token);
}
/* (non-Javadoc)
* @see org.jbpm.job.Job#execute(org.jbpm.JbpmContext)
*/
@Override
public boolean execute(final JbpmContext jbpmContext)
throws Exception
{
boolean executeResult = false;
// establish authentication context
String username = null;
TaskInstance taskInstance = getTaskInstance();
if (taskInstance != null)
{
String actorId = taskInstance.getActorId();
if (actorId != null && actorId.length() > 0)
{
username = actorId;
}
}
// execute timer
if (username == null)
{
executeResult = super.execute(jbpmContext);
}
else
{
executeResult = AuthenticationUtil.runAs(new RunAsWork<Boolean>()
{
@SuppressWarnings("synthetic-access")
public Boolean doWork() throws Exception
{
return AlfrescoTimer.super.execute(jbpmContext);
}
}, username);
}
return executeResult;
}
}

View File

@@ -57,8 +57,8 @@ public class JBPMDeleteProcessTest extends TestCase {
jbpmConfiguration = JbpmConfiguration.parseXmlString(
"<jbpm-configuration>" +
" <jbpm-context>" +
" <service name='persistence' " +
" factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
" <service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
" <service name='tx' factory='org.jbpm.tx.TxServiceFactory' />" +
" </jbpm-context>" +
" <string name='resource.hibernate.cfg.xml' " +
" value='jbpmresources/hibernate.cfg.xml' />" +
@@ -164,7 +164,7 @@ public class JBPMDeleteProcessTest extends TestCase {
//
// Uncomment the following line to force constraint violation
//
// taskInstance.setVariableLocally("var1", "var1TaskValue");
taskInstance.setVariableLocally("var1", "var1TaskValue");
taskInstance.setVariableLocally("var2", "var2UpdatedValue");
taskInstance.end();
@@ -185,7 +185,7 @@ public class JBPMDeleteProcessTest extends TestCase {
GraphSession graphSession = jbpmContext.getGraphSession();
ProcessInstance processInstance = graphSession.loadProcessInstance(processId);
graphSession.deleteProcessInstance(processInstance, true, true, true);
graphSession.deleteProcessInstance(processInstance, true, true);
} finally {
jbpmContext.close();
}

View File

@@ -89,7 +89,6 @@ import org.jbpm.graph.def.Transition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
import org.jbpm.jpdl.par.ProcessArchive;
import org.jbpm.jpdl.xml.JpdlXmlReader;
import org.jbpm.jpdl.xml.Problem;
import org.jbpm.taskmgmt.def.Task;
import org.jbpm.taskmgmt.exe.PooledActor;
@@ -97,7 +96,6 @@ import org.jbpm.taskmgmt.exe.TaskInstance;
import org.springframework.util.StringUtils;
import org.springmodules.workflow.jbpm31.JbpmCallback;
import org.springmodules.workflow.jbpm31.JbpmTemplate;
import org.xml.sax.InputSource;
/**
@@ -437,7 +435,7 @@ public class JBPMEngine extends BPMEngine
* @param workflowDefinitionId workflow definition id
* @return process definition
*/
private ProcessDefinition getProcessDefinition(GraphSession graphSession, String workflowDefinitionId)
protected ProcessDefinition getProcessDefinition(GraphSession graphSession, String workflowDefinitionId)
{
ProcessDefinition processDefinition = graphSession.getProcessDefinition(getJbpmId(workflowDefinitionId));
if (processDefinition == null)
@@ -576,7 +574,7 @@ public class JBPMEngine extends BPMEngine
* @param workflowId workflow id
* @return process instance
*/
private ProcessInstance getProcessInstance(GraphSession graphSession, String workflowId)
protected ProcessInstance getProcessInstance(GraphSession graphSession, String workflowId)
{
ProcessInstance processInstance = graphSession.getProcessInstance(getJbpmId(workflowId));
if (processInstance == null)
@@ -646,7 +644,7 @@ public class JBPMEngine extends BPMEngine
WorkflowInstance workflowInstance = createWorkflowInstance(processInstance);
// delete the process instance
graphSession.deleteProcessInstance(processInstance, true, true, true);
graphSession.deleteProcessInstance(processInstance, true, true);
return workflowInstance;
}
});
@@ -675,7 +673,7 @@ public class JBPMEngine extends BPMEngine
WorkflowInstance workflowInstance = createWorkflowInstance(processInstance);
// delete the process instance
graphSession.deleteProcessInstance(processInstance, true, true, true);
graphSession.deleteProcessInstance(processInstance, true, true);
workflowInstance.active = false;
workflowInstance.endDate = new Date();
return workflowInstance;
@@ -877,7 +875,7 @@ public class JBPMEngine extends BPMEngine
* @param taskId task id
* @return task instance
*/
private TaskInstance getTaskInstance(TaskMgmtSession taskSession, String taskId)
protected TaskInstance getTaskInstance(TaskMgmtSession taskSession, String taskId)
{
TaskInstance taskInstance = taskSession.getTaskInstance(getJbpmId(taskId));
if (taskInstance == null)
@@ -1114,32 +1112,8 @@ public class JBPMEngine extends BPMEngine
protected ProcessDefinition def;
protected String[] problems;
}
/**
* JpdlXmlReader with access to problems encountered during compile.
*
* @author davidc
*/
private static class JBPMEngineJpdlXmlReader extends JpdlXmlReader
{
private static final long serialVersionUID = -753730152120696221L;
public JBPMEngineJpdlXmlReader(InputStream inputStream)
{
super(new InputSource(inputStream));
}
/**
* Gets the problems
*
* @return problems
*/
public List getProblems()
{
return problems;
}
}
/**
* Construct a Process Definition from the provided Process Definition stream
*
@@ -1184,7 +1158,7 @@ public class JBPMEngine extends BPMEngine
{
try
{
JBPMEngineJpdlXmlReader jpdlReader = new JBPMEngineJpdlXmlReader(definitionStream);
JBPMJpdlXmlReader jpdlReader = new JBPMJpdlXmlReader(definitionStream);
ProcessDefinition def = jpdlReader.readProcessDefinition();
compiledDef = new CompiledProcessDefinition(def, jpdlReader.getProblems());
}

View File

@@ -87,10 +87,10 @@ public class JBPMEngineTest extends BaseSpringTest
packageComponent = (WorkflowPackageComponent)applicationContext.getBean("workflowPackageImpl");
// deploy test process messages
I18NUtil.registerResourceBundle("org/alfresco/repo/workflow/jbpm/test-messages");
I18NUtil.registerResourceBundle("jbpmresources/test-messages");
// deploy test process definition
ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_processdefinition.xml");
ClassPathResource processDef = new ClassPathResource("jbpmresources/test_processdefinition.xml");
assertFalse(workflowComponent.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML));
WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML);
testWorkflowDef = deployment.definition;
@@ -123,7 +123,7 @@ public class JBPMEngineTest extends BaseSpringTest
public void testDeployWorkflow() throws Exception
{
ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_processdefinition.xml");
ClassPathResource processDef = new ClassPathResource("jbpmresources/test_processdefinition.xml");
WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML);
testWorkflowDef = deployment.definition;
assertNotNull(testWorkflowDef);
@@ -472,7 +472,7 @@ public class JBPMEngineTest extends BaseSpringTest
throws IOException
{
// deploy test script definition
ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_script.xml");
ClassPathResource processDef = new ClassPathResource("jbpmresources/test_script.xml");
assertFalse(workflowComponent.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML));
WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML);
assertNotNull(deployment);

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.workflow.jbpm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.jpdl.JpdlException;
import org.jbpm.jpdl.par.ProcessArchive;
import org.jbpm.jpdl.par.ProcessArchiveParser;
import org.jbpm.jpdl.xml.JpdlXmlReader;
import org.xml.sax.InputSource;
/**
* Alfresco specific process archive parser to allow for extensions
* to jPDL.
*
* @author davidc
*/
public class JBPMJpdlArchiveParser implements ProcessArchiveParser
{
private static final long serialVersionUID = 1L;
/* (non-Javadoc)
* @see org.jbpm.jpdl.par.ProcessArchiveParser#readFromArchive(org.jbpm.jpdl.par.ProcessArchive, org.jbpm.graph.def.ProcessDefinition)
*/
public ProcessDefinition readFromArchive(ProcessArchive processArchive, ProcessDefinition processDefinition)
throws JpdlException
{
// NOTE: This method implementation is a copy from the JpdlXmlReader class
// with the difference of constructing an AlfrescoCreateTimerAction.
// It may need to be updated whenever a jbpm library upgrade is performed.
try
{
byte[] processBytes = processArchive.getEntry("processdefinition.xml");
if (processBytes == null)
{
throw new JpdlException("no processdefinition.xml inside process archive");
}
// creating the JpdlXmlReader
InputStream processInputStream = new ByteArrayInputStream(processBytes);
InputSource processInputSource = new InputSource(processInputStream);
JpdlXmlReader jpdlXmlReader = new JBPMJpdlXmlReader(processInputSource, processArchive);
processDefinition = jpdlXmlReader.readProcessDefinition();
// close all the streams
jpdlXmlReader.close();
processInputStream.close();
}
catch (IOException e)
{
throw new JpdlException("io problem while reading processdefinition.xml: " + e.getMessage(), e);
}
return processDefinition;
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.workflow.jbpm;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.dom4j.Element;
import org.jbpm.graph.def.Action;
import org.jbpm.graph.def.Event;
import org.jbpm.graph.def.Node;
import org.jbpm.instantiation.Delegation;
import org.jbpm.jpdl.xml.JpdlXmlReader;
import org.jbpm.jpdl.xml.ProblemListener;
import org.jbpm.scheduler.def.CancelTimerAction;
import org.jbpm.scheduler.def.CreateTimerAction;
import org.jbpm.taskmgmt.def.Task;
import org.xml.sax.InputSource;
/**
* Extended JpdlXmlReader with access to problems encountered during compile.
*
* Provides extension to Timers (to allow for absolute date).
*
* @author davidc
*/
public class JBPMJpdlXmlReader extends JpdlXmlReader
{
private static final long serialVersionUID = -753730152120696221L;
/*
* Construct
*/
public JBPMJpdlXmlReader(InputStream inputStream)
{
super(new InputSource(inputStream));
}
/*
* Construct
*/
public JBPMJpdlXmlReader(InputSource inputSource, ProblemListener problemListener)
{
super(inputSource, problemListener);
}
/*
* Construct
*/
public JBPMJpdlXmlReader(InputSource inputSource)
{
super(inputSource);
}
/*
* Construct
*/
public JBPMJpdlXmlReader(Reader reader)
{
super(reader);
}
/* (non-Javadoc)
* @see org.jbpm.jpdl.xml.JpdlXmlReader#readNodeTimer(org.dom4j.Element, org.jbpm.graph.def.Node)
*/
protected void readNodeTimer(Element timerElement, Node node)
{
// NOTE: This method implementation is a copy from the JpdlXmlReader class
// with the difference of constructing an AlfrescoCreateTimerAction.
// It may need to be updated whenever a jbpm library upgrade is performed.
String name = timerElement.attributeValue("name", node.getName());
CreateTimerAction createTimerAction = new AlfrescoCreateTimerAction();
createTimerAction.read(timerElement, this);
createTimerAction.setTimerName(name);
createTimerAction.setTimerAction(readSingleAction(timerElement));
addAction(node, Event.EVENTTYPE_NODE_ENTER, createTimerAction);
CancelTimerAction cancelTimerAction = new CancelTimerAction();
cancelTimerAction.setTimerName(name);
addAction(node, Event.EVENTTYPE_NODE_LEAVE, cancelTimerAction);
}
/* (non-Javadoc)
* @see org.jbpm.jpdl.xml.JpdlXmlReader#readTaskTimer(org.dom4j.Element, org.jbpm.taskmgmt.def.Task)
*/
protected void readTaskTimer(Element timerElement, Task task)
{
// NOTE: This method implementation is a copy from the JpdlXmlReader class
// with the difference of constructing an AlfrescoCreateTimerAction.
// It may need to be updated whenever a jbpm library upgrade is performed.
String name = timerElement.attributeValue("name", task.getName());
if (name == null)
name = "timer-for-task-" + task.getId();
CreateTimerAction createTimerAction = new AlfrescoCreateTimerAction();
createTimerAction.read(timerElement, this);
createTimerAction.setTimerName(name);
Action action = null;
if ("timer".equals(timerElement.getName()))
{
action = readSingleAction(timerElement);
}
else
{
Delegation delegation = createMailDelegation("task-reminder", null, null, null, null);
action = new Action(delegation);
}
createTimerAction.setTimerAction(action);
addAction(task, Event.EVENTTYPE_TASK_CREATE, createTimerAction);
// read the cancel-event types
Collection cancelEventTypes = new ArrayList();
String cancelEventTypeText = timerElement.attributeValue("cancel-event");
if (cancelEventTypeText != null)
{
// cancel-event is a comma separated list of events
StringTokenizer tokenizer = new StringTokenizer(cancelEventTypeText, ",");
while (tokenizer.hasMoreTokens())
{
cancelEventTypes.add(tokenizer.nextToken().trim());
}
}
else
{
// set the default
cancelEventTypes.add(Event.EVENTTYPE_TASK_END);
}
Iterator iter = cancelEventTypes.iterator();
while (iter.hasNext())
{
String cancelEventType = (String) iter.next();
CancelTimerAction cancelTimerAction = new CancelTimerAction();
cancelTimerAction.setTimerName(name);
addAction(task, cancelEventType, cancelTimerAction);
}
}
/**
* Gets the problems
*
* @return problems
*/
public List getProblems()
{
return problems;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.workflow.jbpm;
import org.alfresco.util.AbstractLifecycleBean;
import org.jbpm.job.executor.JobExecutor;
import org.springframework.context.ApplicationEvent;
import org.springmodules.workflow.jbpm31.JbpmTemplate;
/**
* JBPM Scheduler
*
* Manages lifecycle of Jbpm Job Executor.
*
* @author davidc
*/
public class JBPMScheduler extends AbstractLifecycleBean
{
private JobExecutor executor = null;
private JbpmTemplate jbpmTemplate;
/**
* @param jbpmTemplate
*/
public void setJBPMTemplate(JbpmTemplate jbpmTemplate)
{
this.jbpmTemplate = jbpmTemplate;
}
@Override
protected void onBootstrap(ApplicationEvent event)
{
executor = jbpmTemplate.getJbpmConfiguration().getJobExecutor();
executor.start();
}
@Override
protected void onShutdown(ApplicationEvent event)
{
executor.stop();
}
}

View File

@@ -84,6 +84,7 @@ public class JBPMTransactionTemplate extends JbpmTemplate
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@SuppressWarnings("synthetic-access")
@Override
public void afterPropertiesSet() throws Exception
{

View File

@@ -0,0 +1,25 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field">
<subclass name="org.alfresco.repo.workflow.jbpm.AlfrescoCreateTimerAction"
extends="org.jbpm.graph.def.Action"
discriminator-value="C">
<property name="timerName" column="TIMERNAME_" />
<property name="dueDate" column="DUEDATE_" />
<property name="repeat" column="REPEAT_" />
<property name="transitionName" column="TRANSITIONNAME_" />
<many-to-one name="timerAction"
column="TIMERACTION_"
foreign-key="FK_CRTETIMERACT_TA"
cascade="all" />
</subclass>
</hibernate-mapping>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field">
<subclass name="org.alfresco.repo.workflow.jbpm.AlfrescoTimer"
discriminator-value="S"
extends="org.jbpm.job.Timer">
</subclass>
</hibernate-mapping>

View File

@@ -0,0 +1,7 @@
<action-types>
<action-type element="action" class="org.jbpm.graph.def.Action" />
<action-type element="create-timer" class="org.alfresco.repo.workflow.jbpm.CreateTimerAction" />
<action-type element="cancel-timer" class="org.jbpm.scheduler.def.CancelTimerAction" />
<action-type element="script" class="org.jbpm.graph.action.Script" />
<action-type element="mail" class="org.jbpm.graph.action.MailAction" />
</action-types>

View File

@@ -2,6 +2,7 @@
<jbpm-context>
<service name="persistence" factory="org.jbpm.persistence.db.DbPersistenceServiceFactory" />
<service name="tx" factory="org.jbpm.tx.TxServiceFactory" />
<service name="message" factory="org.jbpm.msg.db.DbMessageServiceFactory" />
<service name="scheduler" factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory" />
<service name="logging" factory="org.jbpm.logging.db.DbLoggingServiceFactory" />
@@ -9,20 +10,31 @@
</jbpm-context>
<!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
<string name="resource.hibernate.cfg.xml" value="hibernate.cfg.xml" />
<string name="resource.business.calendar" value="org/jbpm/calendar/jbpm.business.calendar.properties" />
<string name="resource.default.modules" value="org/jbpm/graph/def/jbpm.default.modules.properties" />
<string name='resource.converter' value='org/alfresco/repo/workflow/jbpm/jbpm.converter.properties' />
<string name="resource.action.types" value="org/jbpm/graph/action/action.types.xml" />
<string name="resource.action.types" value="org/alfresco/repo/workflow/jbpm/jbpm.action.types.xml" />
<string name="resource.node.types" value="org/alfresco/repo/workflow/jbpm/jbpm.node.types.xml" />
<string name="resource.parsers" value="org/jbpm/jpdl/par/jbpm.parsers.xml" />
<string name="resource.parsers" value="org/alfresco/repo/workflow/jbpm/jbpm.parsers.xml" />
<string name="resource.varmapping" value="org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml" />
<string name="resource.mail.templates" value="jbpm.mail.templates.xml" />
<long name="jbpm.msg.wait.timout" value="5000" singleton="true" />
<int name="jbpm.byte.block.size" value="1024" singleton="true" />
<string name="mail.smtp.host" value="localhost" />
<bean name="jbpm.task.instance.factory" class="org.alfresco.repo.workflow.jbpm.WorkflowTaskInstanceFactory" singleton="true" />
<bean name="jbpm.variable.resolver" class="org.jbpm.jpdl.el.impl.JbpmVariableResolver" singleton="true" />
<bean name="jbpm.mail.address.resolver" class="org.jbpm.identity.mail.IdentityAddressResolver" singleton="true" />
<int name="jbpm.byte.block.size" value="1024" singleton="true" />
<string name="jbpm.mail.smtp.host" value="localhost" />
<bean name="jbpm.task.instance.factory" class="org.alfresco.repo.workflow.jbpm.WorkflowTaskInstanceFactory" singleton="true" />
<bean name="jbpm.variable.resolver" class="org.jbpm.jpdl.el.impl.JbpmVariableResolver" singleton="true" />
<bean name="jbpm.mail.address.resolver" class="org.jbpm.identity.mail.IdentityAddressResolver" singleton="true" />
<bean name="jbpm.job.executor" class="org.jbpm.job.executor.JobExecutor">
<field name="jbpmConfiguration"><ref bean="jbpmConfiguration" /></field>
<field name="name"><string value="AlfrescoJbpmJobExector" /></field>
<field name="nbrOfThreads"><int value="1" /></field>
<field name="idleInterval"><int value="90000" /></field> <!-- 15 minutes -->
<field name="maxIdleInterval"><int value="3600000" /></field> <!-- 1 hour -->
<field name="historyMaxSize"><int value="20" /></field>
<field name="maxLockTime"><int value="600000" /></field> <!-- 10 minutes -->
<field name="lockMonitorInterval"><int value="60000" /></field> <!-- 1 minute -->
<field name="lockBufferTime"><int value="5000" /></field> <!-- 5 seconds -->
</bean>
</jbpm-configuration>

View File

@@ -13,6 +13,7 @@
<node-type element="milestone-node" class="org.jbpm.graph.node.MilestoneNode" />
<node-type element="interleave-start" class="org.jbpm.graph.node.InterleaveStart" />
<node-type element="interleave-end" class="org.jbpm.graph.node.InterleaveEnd" />
<node-type element="mail-node" class="org.jbpm.graph.node.MailNode" />
<node-type element="page" class="org.jboss.seam.pageflow.Page" />
<node-type element="start-page" class="org.jboss.seam.pageflow.Page" />
</node-types>

View File

@@ -0,0 +1,4 @@
<parsers>
<parser class="org.alfresco.repo.workflow.jbpm.JBPMJpdlArchiveParser" />
<parser class="org.jbpm.jpdl.par.FileArchiveParser" />
</parsers>

View File

@@ -151,6 +151,22 @@
<converter class="org.jbpm.context.exe.converter.BytesToByteArrayConverter" />
<variable-instance class="org.jbpm.context.exe.variableinstance.ByteArrayInstance" />
</jbpm-type>
<!-- hibernatable long id types -->
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.HibernateLongIdMatcher" />
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.HibernateLongInstance" />
</jbpm-type>
<!-- hibernatable string id types -->
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.HibernateStringIdMatcher" />
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.HibernateStringInstance" />
</jbpm-type>
<!-- java.io.Serializable -->
<jbpm-type>
@@ -161,22 +177,6 @@
<variable-instance class="org.jbpm.context.exe.variableinstance.ByteArrayInstance" />
</jbpm-type>
<!-- hibernatable long id types -->
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.HibernateLongIdMatcher" />
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.HibernateLongInstance" />
</jbpm-type>
<!-- hibernatable string id types -->
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.HibernateStringIdMatcher" />
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.HibernateStringInstance" />
</jbpm-type>
<!-- hibernatable ejb3 types -->
<jbpm-type>
<matcher>
@@ -185,6 +185,14 @@
<variable-instance class="org.jbpm.context.exe.variableinstance.Ejb3Instance" />
</jbpm-type>
<!-- JSR 170 JCR Node -->
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.JcrNodeMatcher" />
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.JcrNodeInstance" />
</jbpm-type>
</list>
</jbpm-types>

View File

@@ -1,14 +0,0 @@
test.workflow.title=Test
test.workflow.description=Workflow for testing purposes
test.node.start.title=Start
test.node.start.description=The Start
test.node.review.title=Review
test.node.review.description=The Review
test.node.end.title=End
test.node.end.description=The End
test.node.start.transition.review=Review
test.task.submit.title=Submit Review Title
test.task.submit.description=Submit Review Description

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="wf:grouptasks">
<swimlane name="initiator" />
<start-state name="start">
<task name="wf:submitGroupReviewTask" swimlane="initiator" />
<transition name="" to="review" />
<transition name="parallel" to="parallel" />
</start-state>
<swimlane name="reviewer">
<assignment class="org.alfresco.repo.workflow.jbpm.AlfrescoAssignment">
<pooledactors>#{bpm_groupAssignee}</pooledactors>
</assignment>
</swimlane>
<task-node name="review">
<task name="wf:reviewTask" swimlane="reviewer"/>
<transition name="reject" to="end" />
<transition name="approve" to="end" />
</task-node>
<node name="parallel">
<action class="org.alfresco.repo.workflow.jbpm.ForEachFork">
<foreach>#{people.getMembers(bpm_groupAssignee)}</foreach>
<var>reviewer</var>
</action>
<transition name="review" to="parallelreview" />
</node>
<task-node name="parallelreview">
<task name="wf:reviewTask">
<assignment class="org.alfresco.repo.workflow.jbpm.AlfrescoAssignment">
<actor>#{reviewer}</actor>
</assignment>
</task>
<transition name="" to="endparallel" />
</task-node>
<join name="endparallel">
<transition to="end" />
</join>
<end-state name="end" />
</process-definition>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="wf:pooledtasks">
<swimlane name="initiator" />
<start-state name="start">
<task name="wf:submitParallelReviewTask" swimlane="initiator" />
<transition name="" to="review" />
</start-state>
<swimlane name="reviewer">
<assignment class="org.alfresco.repo.workflow.jbpm.AlfrescoAssignment">
<pooledactors>#{bpm_assignees}</pooledactors>
</assignment>
</swimlane>
<task-node name="review">
<task name="wf:reviewTask" swimlane="reviewer"/>
<transition name="" to="end" />
</task-node>
<end-state name="end" />
</process-definition>

View File

@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="test">
<swimlane name="initiator"></swimlane>
<start-state name="start">
<task name="submit" swimlane="initiator">
<controller>
<variable name="reviewer" access="write,required" />
<variable name="testNode" access="write,required" />
</controller>
</task>
<transition name="" to="review">
<action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
<script>
<expression>
function result()
{
return ("Initiator: " + initiator.properties["cm:userName"] + ", process instance = " + executionContext.processInstance.id + ", testNode children = " + testNode.children.length);
}
result();
</expression>
<variable name="scriptResult" access="write"/>
</script>
</action>
</transition>
<transition name="end" to="end"/>
</start-state>
<swimlane name="reviewer">
<assignment actor-id="#{reviewer}"></assignment>
</swimlane>
<task-node name="review">
<event type="node-enter">
<script>
System.out.println("company home: " + companyhome.name);
System.out.println("initiator home: " + initiatorhome.name);
System.out.println("the reviewer is " + reviewer);
System.out.println("node " + testNode.name + " contains " + testNode.children.length + " children");
System.out.println("scriptResult = " + scriptResult);
</script>
</event>
<task name="review" duedate="1 business day" blocking="true" swimlane="reviewer">
<controller>
<variable name="comment" access="read,write,required"></variable>
</controller>
</task>
<transition name="" to="end"></transition>
</task-node>
<end-state name="end"></end-state>
</process-definition>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="test_processevents">
<event type="process-start">
<script>
System.out.println("Process start");
</script>
</event>
<start-state name="start">
<transition to="end"/>
</start-state>
<end-state name="end"/>
<event type="process-end">
<script>
if (cancelled) { System.out.println("cancelled"); }
System.out.println("Process end: " + cancelled);
</script>
</event>
</process-definition>

View File

@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="test_script">
<swimlane name="initiator"></swimlane>
<start-state name="start">
<task name="submit" swimlane="initiator">
<event type="task-assign">
<script>
System.out.println("taskInstance.create: " + taskInstance.create);
System.out.println("taskInstance.description: " + taskInstance.description);
</script>
</event>
<controller>
<variable name="testNode" access="write,required" />
<variable name="bpm_workflowDescription" access="write" />
</controller>
</task>
<transition name="" to="doit" />
</start-state>
<node name="doit">
<event type="node-enter">
<script>
<expression>
System.out.println("testNode.created: " + testNode.properties{"cm:created"});
System.out.println("test node " + testNode.name + " contains " + testNode.children.length + " children");
</expression>
<variable name="testNode" access="read" />
</script>
</event>
<event type="node-enter">
<action
class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
<script>
<!-- following line fails as it attempts to convert properties of children to javascript objects -->
<!-- except the beanshell line above has already pre-created the children without the javascript scope -->
<!-- object -->
<!-- var result = "testNode.created: " + testNode.properties["cm:created"] + ", testNode.children.length: " + testNode.children[0].properties["cm:name"]; -->
<expression>
var result = "testNode.created: " + theTestNode.properties["cm:created"] + ", theTestNode.children.length: " + theTestNode.children.length;
if (logger.isLoggingEnabled())
{
logger.log(result);
}
result;
</expression>
<variable name="testNode" access="read" mapped-name="theTestNode" />
<variable name="alfrescoScriptResult" access="write" />
</script>
</action>
</event>
<transition name="" to="end" />
</node>
<end-state name="end">
<event type="node-enter">
<script>
System.out.println("javascript: " + alfrescoScriptResult);
System.out.println("bpm_workflowDescription: " + bpm_workflowDescription);
</script>
</event>
</end-state>
</process-definition>

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="wf:testtaskassign">
<swimlane name="initiator" />
<start-state name="start">
<task name="bpm:startTask" swimlane="initiator" />
<transition name="" to="assign" />
</start-state>
<swimlane name="assignee">
<assignment class="org.alfresco.repo.workflow.jbpm.AlfrescoAssignment">
<actor>#{bpm_assignee}</actor>
</assignment>
</swimlane>
<task-node name="assign">
<task name="wf:workflowTask" swimlane="assignee">
<event type="task-assign">
<script>
System.out.println("Task assigned: " + executionContext.getTaskInstance().getActorId());
</script>
</event>
</task>
<transition name="" to="end" />
</task-node>
<end-state name="end" />
</process-definition>

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="wf:xor">
<swimlane name="initiator" />
<start-state name="start">
<task name="submit" swimlane="initiator" />
<transition name="" to="orsplit" />
</start-state>
<fork name="orsplit">
<transition name="a" to="a" />
<transition name="bc" to="bc" />
</fork>
<task-node name="a">
<task name="a">
<assignment actor-id="usera" />
</task>
<transition name="" to="orjoin" />
</task-node>
<fork name="bc">
<transition name="b" to="b" />
<transition name="c" to="c" />
</fork>
<task-node name="b">
<task name="b">
<assignment actor-id="userb" />
</task>
<transition name="" to="bcjoin" />
</task-node>
<task-node name="c">
<task name="c">
<assignment actor-id="userc" />
</task>
<transition name="" to="bcjoin" />
</task-node>
<join name="bcjoin">
<transition name="" to="orjoin" />
</join>
<join name="orjoin">
<event type="node-enter">
<script>
node.nOutOfM = 1;
</script>
</event>
<transition name="" to="wait" />
</join>
<state name="wait">
<transition name="" to="end" />
</state>
<end-state name="end" />
</process-definition>