A BPEL process can be created when a particular receive activity of a process is called. It must be an activity within the process which is not preceded by an other activity and which is marked with a "createInstance" property set to "true". Not in all cases calling such a receive activity will result in the creation of a process. Processes with multiple start activities might be already created and have their instance creating receive activities called. This will be considered as a separate case. It is further necessary that a process is known to the BPEL engine in order to be started.
Glossary: In the following descriptions the BPEL processes will be called processes for short. The information related to a process instance, will be encapsulated within a process context. In the concrete implementation, for the sake of simplicity, the process and the process context will be encapsulated in a object called process instance.
As to explain how bexee creates a new process we will consider the following case:
A receive activity is called, the correspondent process has been deployed and the process has not been created by another receive activity.
The bexee engine receives a call for a receive activity; the call is represented by a message. A factory for process instances finds the correspondent process and tries to find a process context. As the process instance is not yet created, the process context can't be found. Nevertheless the factory returns a process instance with a process and a null process context. The process instance object will then be passed together with the message representing the receive activity call to a process controller. In bexee the logic for process execution and handling will be concentrated within a process controller. It therefore will be up to the controller to see that the process context is null. A null process context is an indication to the process controller that it's necessary to instantiate the process. The process controller will create a new process context, and initiate it before passing the message to the correspondent receive activity. Process instance initiation. This is represented in the following sequence diagram.
Process context creation and initiation will be described in the following section.
When an incoming Message for a Receive Activity arrives, the engine
has to decide, whether a new instance of a process has to be created
or the message has to be passed to an existing process instance. This
decision is taken based on the process specific correlation data. In
case it is not possible to correlate the arriving message to an
existing process instance, a new process instance has to be created.
The process instance factory will create a new process instance, find
the right BPEL process and associate a null process context with it.
Such a process instance will be passed together with the message to
the process controller. As we intend to concentrate process handling
logic within the process controller, it's the process controller that
hat to handle the situation where the process context is null.
Receiving a process instance with a null process context, the
controller will create a new context and initiate it instead of
passing the message to the correspondent receive activity.
After the creation of a new process context, the message
representing a receive call is not processed with the correspondent
receive activity. The process instance must be initiated first, i.e.
the partner links, partners, variables, correlation sets, fault
handlers, compensation handlers and the event handlers have to be
initiated first before the message with the correspondent activity
will be processed. Internally the process is represented as a tree and
the process instance specific data is encapsulated within the process
context. Initiating the process is done recursively until the
correspondent first receive activity is processed. The activity then
consumes its call represented as a message. After processing the
receive activity, the following activities are processed until a next
receive activity is processed. The processing doesn't continue there
because the receive call message has been consumed by the last
receive. The processing returns to the process controller and waits
then for a next receive call.
This section describes in further detail how exactly an incoming
message is dispatched to the ProcessController
.
Asynchronous request/response
We will first consider the easier case, where a message arrives at the engine for processing but the client doesn't need the answer right away. Actually it is not the client that makes this decision, but this is specified in the deployed BPEL process but this shall not be of further importance for what we will try to explain.
DispatcherThread
. ProcessController
to process the message. ProcessController
finishes processing the
message, it will either send the result to a call back interface, if
one was provided or, if not, just terminate. Synchronous request/response
Things get a bit more tricky when a client starts a synchronous BPEL process. This means that the client needs to wait until the processing of the entire BPEL process is done in order to get the response.
As we saw in the preceding section the processing of an incoming message will always occur inside a new thread. The reason of doing this is explained in Processing asynchronous <invoke>s.
For the main controller, this means that it has to wait until the entire BPEL process has been processed.
DispatcherThread
. ProcessContext
object. Hence the main thread that
was started by the client is now waiting. ProcessController
to process the message. ProcessController
finishes processing the
message, it will store the result in the ProcessContext
and notify all locking threads. ProcessContext
and send a
response back to the client. // locate ProcessContext ctx Thread dispatcher = new DispatcherThread(ctx); dispatcher.start(); synchronized (ctx) { try { ctx.wait(); } catch (InterruptedException e) { // exception handling } } return ctx.getResult();
public void run() { ProcessController.getinstance().dispatchMessage(instance, message); }
ctx.setResult(...); synchronized (ctx) { ctx.notify(); }
Multithreading issues with Mozilla Browsers
Note that there seems to be a serious restriction when using a Mozilla browser: If a web resource is requested that takes a long time to send a response (e.g. a synchronous request that started a long-running BPEL process) it is NOT possible to send a request to that same resource again. It is of course still possible to send a request to other resources on the same server and Tomcat will use a new thread to process the request as expected. But it looks like Mozilla does not send a request to exactly the same resource until a response for the first request has been received.
The came across this issue by using either Mozilla/5.0 rv: 1.7.3 or Mozilla/5.0 rv: 1.6 (Firefox).
Further investigations were not undertaken due to time constraints and the thought that calling a web service from within a web browser is rather unusual. But nevertheless this is a serious limitations and should definitely be taken seriously if the project will be continued seriously.
Processing asynchronous <invoke>s
It is possible that a BPEL process doesn't terminate after the first message it received, because it might have to wait for more messages from other sources. This could be the case if we execute an asynchronous <invokes> activity inside a BPEL process. The process will continue it's execution until it reaches a point where an input response is needed, e.g. a <receive> element.
<!-- initiate the remote process --> <invoke name="invokeAsyncService" partnerLink="AsyncBPELService" portType="services:AsyncBPELService" operation="initiate" inputVariable="request"/> <!-- receive the result of the remote process --> <receive name="receive_invokeAsyncService" partnerLink="AsyncBPELService" portType="services:AsyncBPELServiceCallback" operation="onResult" variable="response"/>
Message-driven process execution
Because the arriving new message will again trigger process
execution (in exactly the same way a new message does as described
above). Hence we are confronted with a message-driven model and it is
thus not necessary for the ProcessController
to wait
until a new message arrives. Quite the contrary, the thread that
triggered the ProcessController
will terminate and if a
new message arrives again later, the ProcessController
will be triggered again by a new thread to process the message and so
on.
This leads to the big advantage that the
ProcessController
doesn't have to wait at a given
activity or remember where it had to wait.
All the ProcessController
does when it arrives at a
<receive> element is informing the
ProcessContext
about the element. The
ProcessContext
maintains a list of Receive
activities. This list will be used by the
ProcessController
to see whether the process instance in
a state where it can accept a given incoming message.
The following example tries to explain how an incoming request is
processed by the ProcessController
.
Note that this example doesn't make much sense and is incomplete as parts of the BPEL document have been deleted for the sake of the simplicity of this illustration.
Below you'll find an excerpt of a BPEL document and the corresponding
sequence diagram. Note that the processing of the
<partnerLinks>
elements is omitted in the sequence
diagram and the processing of the <variables>
elements is only shown once in the diagram.
<process name="bexeeExample"> <partnerLinks> <partnerLink name="client" /> <partnerLink name="service" /> </partnerLinks> <variables> <variable name="input" messageType="tns:ReceiveRequestMessage"/> <variable name="response" messageType="tns:ReceiveResultMessage"/> </variables> <sequence> <!-- receive input from requestor --> <receive name="receiveInput" partnerLink="client" operation="initiate" variable="input" createInstance="yes"/> <!-- initiate the remote process --> <invoke name="asyncInvoke" partnerLink="service" portType="services:AsyncBPELService" operation="initiate" inputVariable="input"/> <!-- receive the result from the invoked service --> <receive name="receiveResult" partnerLink="service" portType="services:AsyncBPELServiceCallback" operation="onResult" variable="response"/> <!-- continue processing and invoke client callback --> </sequence> </process>
ProcessController
.
ProcessController
checks if the ProcessContext
was used before.
ProcessController
initialized the
ProcessContext
by calling its init()
method.
ProcessController
gets a list of
<Receive>
objects from the
ProcessContext
. This list represents the ports on the
process that are ready to receive an incoming message.
ProcessController
checks if the incoming message can be
matched onto one of the retrieved <Receive>
objects.
Because the process state changes over time this list also does and it
is thus important for the ProcessController
to check if the
process instance is ready to accept incoming message.
ProcessController
removes
the object from the ProcessContext
.
ProcessController
thus needs to start at the very top of
the process. It therefore calls the accept()
on the top
object, which is the <Process>
object.
<Process>
object does within it's
accept()
is starting a call back to the
ProcessController
in order to get processed (this is called
double-dispatching).
process(Process, ProcessInstance)
method the
ProcessController
expands the Process
objects
children and starts recursively calling accept()
on all
of them.
Receive
object is
encountered. The processing stops here and returns because the process
can only continue after another incoming message has been received and
matched. Once that happens (not shown in the diagram) the procedure
begins again as described, but this time on the second
Receive
object.