Invoke Multiple Subprocesses

This activity allows you to start multiple subprocess instances determined dynamically based on data (such as service orders) at run time. The subprocess instances may be of same process definition, different process definition, or a combination of them.

At runtime this activity uses an XML document, called the execution plan, to control which subprocesses to launch. Typically the execution plan is created by a preceding activity, possibly by consulting with an external product/service catalog.

If you are not already familiar with concepts such as Smart Subprocess Versions, and parameter binding modes, please refer to Invoke Subprocess Activity for a description of these concepts, which are the same for single and multiple subprocesses. Here we focus on aspects specific to multiple subprocess invocation, especially the syntax and semantics involved in creating an execution plan.

Activity Configuration

Here's a shot of the configurator tab for Multiple Subprocess Invoke:



Note: To open the subprocess definition, double-click on the process icon for a row under Subprocesses.

The following attributes pertain:

Execution Plan Variable
This points to the variable containing the execution plan. The variable must be a document variable type (XmlObject, JAXBElement, etc).
synchronous
A boolean value indicating whether the parent process waits for completion of subprocesses.
Force Parallel Execution
Normally, the engine will try to run with a single thread for all subprocesses as far as possible. This attribute is a boolean value and when it is set to true, the engine will allocate one thread for each subprocess so they can run in parallel.
Delay Between Instances (sec)
You can specify the number of seconds between launching two subprocesses. If you leave it blank, the default value is 0 second. For service processes running in synchronous mode, this attribute is ignored. In other cases, setting this to a nonzero value can help avoid certain race conditions where the JMS message travels faster than a needed database update. For this case a value greater than 0 has the side effect of forcing parallel execution (as if Force Parallel Execution is true), as any delayed execution uses a new thread.
Events tab
See the section below Wait For Unsolicited Events

Execution Plan

The execution plan is an XML document and its XSD looks like (extracted from MDW source code)

<?xml version="1.0"?>
<xsd:schema xmlns = "http://mdw.centurylink.com/bpm"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	targetNamespace="http://mdw.centurylink.com/bpm"
	elementFormDefault="unqualified"
	attributeFormDefault="unqualified">
    <xsd:element name="Parameter">
		<xsd:complexType>
			<xsd:simpleContent>
				<xsd:extension base="xsd:string">
					<xsd:attribute name="Name" type="xsd:string" use="required"/>
				</xsd:extension>
			</xsd:simpleContent>
		</xsd:complexType>
	</xsd:element>
    <xsd:element name="LogicalProcessName" type="xsd:string"/>
	<xsd:element name="ProcessId" type="xsd:string"/>
    <xsd:element name="InstanceId" type="xsd:string"/>
    <xsd:element name="StatusCode" type="xsd:int"/>
   	<xsd:element name="SubprocessInstance">
        <xsd:complexType>
            <xsd:sequence>
	            <xsd:element ref="LogicalProcessName"/>
    			<xsd:element ref="ProcessId"/>
                <xsd:element ref="InstanceId"/>
                <xsd:element ref="StatusCode"/>
                <xsd:element ref="Parameter" minOccurs="0" maxOccurs="unbounded" />
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="ProcessExecutionPlan">
    	<xsd:complexType>
            <xsd:sequence>
            	<xsd:element ref="SubprocessInstance" minOccurs="0" maxOccurs="unbounded"/>
             </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

MDW has already created an XMLBean for this XSD, so you do not normally need to deal with the XSD. The following shows a piece of sample code using XMLBeans to construct the execution plan that contains 3 subprocess instances, 2 for UniSubProcess and 1 for EvcSubProcess:

String createExecutionPlan() {
	ProcessExecutionPlanDocument exeplan_doc = ProcessExecutionPlanDocument.Factory.newInstance();
	ProcessExecutionPlan exeplan = exeplan_doc.addNewProcessExecutionPlan();
	SubprocessInstance subprocinst = exeplan.addNewSubprocessInstance();
	subprocinst.setLogicalProcessName("UniSubProcess");
	subprocinst.setStatusCode(WorkStatus.STATUS_PENDING_PROCESS.intValue());
	addParameter(subprocinst, "OrderNumber", "C30234456");
	addParameter(subprocinst, "UNI_ID", "UNI-101");
	addParameter(subprocinst, "OrderContent", "$orderContent");
	subprocinst = exeplan.addNewSubprocessInstance();
	subprocinst.setLogicalProcessName("UniSubProcess");
	subprocinst.setStatusCode(WorkStatus.STATUS_PENDING_PROCESS.intValue());
	addParameter(subprocinst, "OrderNumber", "C30234456");
	addParameter(subprocinst, "UNI_ID", "UNI-102");
	addParameter(subprocinst, "OrderContent", "$orderContent");
	subprocinst = exeplan.addNewSubprocessInstance();
	subprocinst.setLogicalProcessName("EvcSubProcess");
	subprocinst.setStatusCode(WorkStatus.STATUS_PENDING_PROCESS.intValue());
	addParameter(subprocinst, "OrderNumber", "C30234456");
	addParameter(subprocinst, "EVC_ID", "EVC-A");
	addParameter(subprocinst, "OrderContent", "$orderContent");
	addParameter(subprocinst, "DueDate", "$due_date");
	return exeplan_doc.xmlText();
}

Note: In Groovy script the $ character is used for templating and must be escaped. So, for example, the call to addParameter() should pass "\$orderContent" instead of "$orderContent"). Here is an example to call: addParameter(subprocinst, "orderContent", "\$orderContent");

The utility method addParameter() above is defined as

void addParameter(SubprocessInstance subprocinst, String name, String value) {
	Parameter param = subprocinst.addNewParameter();
	param.setName(name);
	param.setStringValue(value);
}

The following points need to be emphasized:

Binding of Output Variable Values

Because the activity can potentially launch multiple instances of the same subprocess, you may need multiple variables in the parent process to receive the bindings. For example, if one subprocess defines an output variable foo, the parent process cannot just have a single variable, say parent_foo, to receive the value of foo as there can be multiple instances of the variable (one for each subprocess). The number of parent process variables needed to receive the values may not even be known at design time.

To alleviate the problem, the activity allows, in addition, a special binding for output variables in the execution plan, $ (a single dollar sign without being followed by a parent process variable name). The output variable value will be set in the execution plan itself (replacing the binding specification $), instead of to a parent variable. The application specific logic can retrieve the values as needed from the execution plan. The following sample code shows how to retrieve the variable bindings from the first subprocess instance:

protected String getParameter(SubprocessInstance subprocinst, String name) {
	if (subprocinst.getParameterList()==null) return null;
	for (Parameter p : subprocinst.getParameterList()) {
		if (p.getName().equals(name)) return p.getStringValue();
	}
	return null;
}

ProcessExecutionPlanDocument plan = getProcessExecutionPlan();
SubprocessInstance subprocinst = plan.getProcessExecutionPlan().getSubprocessInstanceList().get(0);
super.setParameterValue(binding_varialbe_name, getParameter(subprocinst, output_variable_name));

The processing logic can be implemented in a subsequent activity, or can be implemented by overriding the method onFinish().

Activity Configuration

The following lists attributes that may need to be configured:

Execution Plan Variable
This should holds the name of the variable containing the execution plan. The type of the variable must be org.apache.xmlbeans.XmlObject
synchronous
A boolean value indicating whether the parent process waits for completion of subprocesses.
Force Parallel Execution
Normally, the engine will try to run with a single thread all all subprocesses as far as possible. This attribute is a boolean value and when it is set to true, the engine will allocate one thread for each subprocess.
Delay Between Instances (sec)
You can specify the number of seconds between launching two subprocesses. If you leave it blank, the default value is 0 second. For service processes running in synchronous mode, this attribute is ignored. In other cases, a value greater than 0 has the side effect of forcing parallel execution (as if Force Parallel Execution is true), as any delayed execution uses a new thread.
Events
See the section below on Wait For Unsolicited Events for details
Status after receiving event
See the section below on Wait For Unsolicited Events for details

Wait For Unsolicited Events

It is often desired that when a process is waiting for all sub processes, it needs to handle unsolicited external events such as receiving a supplemental order or cancellation of the request. The activity allows to optionally register for listening to unsolicited events before all subprocesses terminate. It uses the same mechanism as EventWaitActivity to register the event waits (following this link for details of event wait registration and additional details).

When the activity instance receives an event before the timer expires, it can be left in one of the 2 statuses:

We note that the activity can only listen to recurring events. This limitation is needed to avoid infinite looping when the activity instances resume waiting from hold status, non-recurring events that have already arrived would always trigger a transition.

To provide custom code handling unsolicited events, you will need to override TimerWaitActivity.processOtherMessage(String message). The method is passed in with one argument, which is the entire message. The default method does nothing. The completion code after processing the message is configured in the MDW Studio, but you can override it in the code by invoking setReturnCode().