Tammo and Oliver from the IAAS
took your ideas and worked on a syntax for BPEL4Coders. This file is a quick summary of our results.
Main design goals:
We introduced implicit variable declaration, which does not fulfill the second goal, but perfectly fits to the first goal.
BPEL offers block-structured and graph-based programming. Since block-structured programming is more in-line with JavaScript, we first present on a ordering process how this is reflected in BPEL4Coders.
The commands are executed in sequential order (BPEL: <sequence>)
process OrderCheapestBook {
// partner porttypes - identified by QNames of their WSDL porttype
service amazon = {http://aws.amazon.com/aws}Bookstore
service bol = {http://bws.bol.de/bws}Bookstore
// port types offered by the process. Since the process acts as "client" for the partner, "client" as chosen as keyword
client me = {urn:iaas:toll}BookReseller
// variable declaration. XML support is built-in: po is an XML variable, whose type is defined in a WSDL
var {urn:iaas:ourxsd}PurchaseOrder po
// the variable purchaseOrder is declared
// the type is the type returned by the function me.orderBook()
// me is a client. Therefore the semantics of function calls on that object is to wait for a partner to call. The received message is returned by the function
// BPEL: <receive partnerLink="me" operation="orderBook" variable="purchaseOrder" />
var purchaseOrder = me.orderBook()
// Service declaration. Type is determined by the first assignment to it
service res
// Variables without direct type declaration get their type by the first assigment to them
var tmp1, tmp2
// Parallel execution (BPEL: <flow> instead of <sequence>.)
// nested sequential execution can be made by "seq {...}"
par {
tmp1 = amazon.getDollarPriceForISBN()
tmp2 = bol.getEURPriceForISBN()
}
// compiler exception
tmp1 = tmp2
// TODO: compiler exception, since amazon and bol are type incompatible
if (tmp1/@price < tmp2/@price) {
res = amazon
} else {
res = bol
}
// me.orderBook() is a synchronous operation
// <reply> is done by assigning a value to the operation (cf. Pascal-Syntax)
me.orderBook() = res.buyBook()
}
Having seen the block-structured part, a simplified loan approval process is now used to illustrate the graph-based part of the language.
process LoanApproval {
service autoAssessor = {urn:auto}pt
service humanAssessor = {urn:human}pt
service customer = {urn:customer}pt
client me = {urn:me}pt
// After the statement, links may come
// Links are separated by commas
// The condition is enclosed in square brackets
// the target of a link (indicated by ->) is a label
var request = me.request() [$request.amount < 50000]->lauto, [$request.amount >= 50000]->lhuman
// Label "lauto" for the autoAssessor
lauto: var risk = autoAssessor.approve(request) l1=[$risk = 'high']->lhuman, l2=[risk = 'low']->lapp
lhuman: var hresult = humanAssessor.approve(request) [$hresult = 'approved']->lapp, [$hresult = 'rejected']->lrej
lapp: customer.appoved();
lrej: customer.rejected();
}
The example misses join conditions. Join conditions are put in square brackets in front of the label. To reference links in the join conditions, links have to be named. This happens by putting <linkName>= in front of the link-condition and target.
For example:
lauto: var risk = autoAssessor.approve(request) l1=[$risk = 'high']->lhuman, l2=[risk = 'low']->lapp
[l1 and l2] lapp: customer.appoved();
Scopes are enclosed in braces (prefixed by {{scope }} -> Matthieu):
// Scope scope { onEvent: me.getStatus() {|msg| // Event handler for call at operation "getStatus()" // msg contains the message sent by the partner client.notify("processing"); } onEvent: me.getExtStatus() {|msg| ... } ... } onFault f { // Fault Handler for fault f } onCompensation { // Compensation Handler ... } onTermination { // Termination Handler ... } ... }
There is no disambiguity in the usage of the brackets. For example, if a scope should be nested in a handler, it looks like follows:
...
} onFault f {
scope("test") {
...
} onFault g {
}
} onCompensation {
...
{{{XML-Code for the extension}}}
First a few general points:
Optional target namespace declaration. For process elements it defaults to ODE target namespace.
namespace foo = "urn:/example.com"
process foo::request {
}
# Or
use namespace foo
process request {
}
Importing documents definitions. After import, elements can be referenced relatively.
foo = import "xsd|wsdl|..." foo.portTypes.bar
Variable declaration is optional and mostly used to add some specific behavior to them. Modifiers in a variable declaration are its type, unique and external:
var foo string unique, bar external(baz), baz int
Function definition in Javascript that returns an xpath function, plain javascript inside and allows to "hook" other expression languages. Those functions are also used for correlation (see later).
function foo(msg) {
xpath("foo/bar");
}
something.other = foo
something.other = xpath('.....')
Process definition
process foo {
...
}
Sequence is always easy
sequence {
}
Partner links must be defined before being used (but no explicit role definition is needed) and are associated with the closest surrounding scope. How they are bound to an endpoint or interface (service/port, Java class, ...) is outside of the scope of SimPEL and is meant to be specified by a deployment descriptor.
partnerLink foo, bar, baz
Pick syntax, receive is the same thing without the pick wrapper
pick {
receive(p1, o1) { |msg|
# More BPEL code
}
receive(p2, o2) { |msg|
}
someVar = receive(p3, o3)
timeout(val) {
}
}
Flow, each block is a sequence. The signal and join instructions are here to model links,
parrallel {
...
signal(link1, "expr")
...
} and {
...
join(link1, link2, link3, "($link1 and $link2) or $link3")
...
} and {
}
If, else if and else:
if (expr) { } else if (expr) { } else { }
While:
while(i<10) {
i = i + 1
}
Repeat until:
do {
i = i+1
} until(i<10)
Sequential foreach and parrallel foreach are two different constructs. Allow break?
for(m = 0; m < 10; m = m + 2) {
}
forall(m = 0 to 10) {
}
Invoke syntax - toParts? fromParts?
foo = invoke(pl, operation, msg)
Assignment
foo = bar foo = bar + " World" foo = [xpath: concat(bar, " World")]
Throw, faultVar is optional:
throw faultName, faultVar
Wait, support now()+duration expression
wait 2m
Correlation is achieved by declaring a function that will extract a value from a message and match it against another value. So there's no real correlation set per se, just a set of functions used to extract data from messages. We also introduce a special variable declarator that allows for some variables to be unique (and indexed).
function oidFromOrder(msg) {
....
}
process {
var oid unique
msg = receive(pl, op1)
oid = oidFromOrder(msg)
....
receive(pl, op2, {oidFromOrder: oid}) { |msg2|
....
}
}
We've separated the fault handling role of scopes (try...catch) from the eventing role (*Handlers).
try { scope { .... } event (pl, op) { |var| ... } alarm { } compensation { } } catch (Fault f1) { } catch (f2) { }
For all variables that are passed inside blocks (mostly receive and event), a context can be provided as well. The is meant to introduce security context manipulations:
receive(pl, op) { |var, ctx|
...
}
Hello world example
process Helloworld {
receive(client, hello) (msg) {
msg = msg + " World"
reply msg
}
}
External counter example
process ExternalCounter {
receive(my_pl, start_op) (msg_in) {
resp = <root><count>0</count></root>
while(resp < 10) {
invoke(partner_pl, partner_start_op) (msg_in)
resp = receive(partner_pl, partner_reply_op)
}
reply resp
}
}
