First of all, getting familiar with the WS-BPEL 2.0 standard is a very good idea. To use ODE, you will need to write processes using the BPEL language. There are several examples in our distributions that you can use to get started, but a decent understanding of the spec is assumed.
Ode can be deployed in two different environments:
Unzip the distribution somewhere on your disk, everything needed is inside.
Get the WAR file in the distribution root directory, rename it to ode.war and copy this file to Tomcat
's webapp directory. Start Tomcat and Ode should be up and running. You should get the Axis2
welcome page under http://localhost:8080/ode
. The Ode WAR includes its own embedded database (Derby
) so you don't have to worry about configuring any external database for now.
Copy the content of the examples directory in the distribution (the 3 sub-directories) to tomcat/webapps/ode/WEB-INF/processes, this will automatically deploy the 3 example processes. Use the sendsoap command located in the distribution bin directory to send test messages. The messages to run each of the 3 examples are provided in their respective directory (testRequest.soap). For each example type something like:
bin/sendsoap http://localhost:8080/ode/processes/helloWorld examples/HelloWorld2/testRequest.soap
The sendsoap executable can be found in the distribution bin directory. The urls should be updated according to the address defined in the WSDL file for the process service.
The ODE war should have been copied to the webapps directory of Tomcat and the server should have been started at least once before following these instructions. This ensures that the webapp is properly exploded.
<Context path="/ode" docBase="ode" debug="5" reloadable="true" crossContext="true"> <Resource name="jdbc/ODEDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/ode?autoReconnect=true"/> </Context>
ode-axis2.db.mode=EXTERNAL ode-axis2.db.ext.dataSource=java:comp/env/jdbc/ODEDB
You're done!
Here's a quick overview to deploy PXE/ODE on a JBI container (e.g. ServiceMix
)
Check our download page and get the latest JBI-based distribution. Unzip it in a directory of your choice. We'll now refer to this directory as ODE_HOME.
For example, with ServiceMix you can use file-system deployment:
(from ode/jbi directory) cp ODE_HOME/ode-jbi-1.1.zip SERVICEMIX_DIR/install
The above will result in ODE being installed with the default settings. You may wish to first modify the ode-jbi.properties file found in the root of the installer ZIP.
We are assuming that the reader is familiar with JBI deployment concepts. Deploying a process consists of the following steps:
Some JBI examples are available under the examples directory of the ODE distro:
Extract the distro-jbi-2.0-SNAPSHOT.zip created in the build instructions above.
To compile the examples, you may wish to define the ODE_HOME environment variable. The build script does a good job of figuring this out without ODE_HOME set, however.
# On Linux/Unix export ODE_HOME=/path/to/ode/distribution # On Windows set ODE_HOME=C:\Path\To\Ode\Distribution
and run Ant in the example's directory:
cd %ODE_HOME%/examples/PingPong ant
This will create a JBI service assembly in the "build" subdirectory. With ServiceMix you can simply copy it to the file-system hot deployment directory:
(from PingPong directory) cp build/PingPing-sa.zip SERVICEMIX_DIR/deploy
Finally, you can test the example by typing:
(from PingPong directory) ant test
ODE now relies strictly on abstract web service definitions (i.e., without binding/service/port definitions), meaning that you only need abstract WSDLs when compiling processes. Since JBI uses normalized messages (in theory, at least), there is no need to define bindings for the BPEL service engine.
In deploy.xml, you simply define the JBI internal endpoints invoked or provided by your partnerLinks,
<?xml version="1.0" encoding="UTF-8"?>
<deploy xmlns="http://www.apache.org/ode/schemas/dd/2007/03"
xmlns:process="urn:/Ping.bpel"
xmlns:ping="urn:/Ping.wsdl"
xmlns:pong="urn:/Pong.wsdl">
<process name="process:Ping">
<active>true</active>
<provide partnerLink="PingPartnerLink">
<service name="ping:PingService" port="PingPort"/>
</provide>
<invoke partnerLink="PongPartnerLink">
<service name="pong:PongService" port="PongPort"/>
</invoke>
</process>
</deploy>
One may use JBI binding components to make services externally available and therefore providing concrete bindings to those binding components. An example of exposing process services via SOAP/HTTP can be found in the PingPong ping-http service unit
.
The generated installer will use an internally-managed embedded Derby database. No configuration is required. To use an external database one needs to modify ode-jbi.properties found in the component installer zip.
Many binding components are not very good about delivering messages in the correct format for WSDL11 services.
ServiceMix' so-called lightweight components make it difficult to properly expose process services since they do not fully implement the JBI contract and do not allow the process engine to enquire about its external endpoints.
Each deployment is a directory with all relevant deployment artifacts. At the minimum it will contain the deployment descriptor, one or more process definitions (BPEL or .cbp), WSDL and XSDs (excluding those compiled into the .cbp). It may also contain other files, such as SVGs or XSLs. The deployment descriptor is a file named deploy.xml (see the next paragraoh for its description).
During deployment, the process engine loads all documents from the deployment descriptor. Loading documents allow it to reference processes, service and schema definitions using fully qualified names, and import based on namespaces instead of locations.
To deploy in Ode, just copy the whole directory containing your artifacts (the directory itself, not only its content) in the path %DEPLOYMENT_ROOT%/WEB-INF/processes (in Tomcat it would be %TOMCAT_HOME%/webapps/ode/WEB-INF/processes).
To deploy your process in Ode you will need to create a simple deployment descriptor with basic information. The deploy.xml file configures one or several processes to use specific services. For each process, deploy.xml must supply binding information for partner links to concrete WSDL services. Every partner link used with a <receive> activity must be matched with a <provide> element, and every partnerLink used in an <invoke> activity must be matched with an <invoke> element in deploy.xml (unless that partnerLink has initializePartnerRole="false").
The XML schema describing ODE's deployment descriptor is available here
. The root element, deploy, contains a list of all deployed processes from the deployment directory:
<deploy> <process ...>* { other elements } </process> </deploy>
Each process is identified by its qualified name and specifies bindings for provided and invoked services:
<process name = QName fileName = String? bpel11wsdlFileName = String? > (<provide> | <invoke>)* { other elements } </process>
Each process element must provide a name attribute with the qualified name of the process. Optionally, a fileName attribute can be used to specify the location of the BPEL process definition (the .bpel file). The fileName attribute does not need to be provided unless non-standard compilation options are used or the bpel11wsdlFileName attribute is used to specify a WSDL document for a BPEL 1.1 process.
Each <process> element must enumerate the services provided by the process and bind each service to an endpoint. This is done through <provide> elements which associates {{partnerLink}}s with {{endpoint}}s:
<provide partnerLink=NCName> <service name = QName port = NCName?> </provide>
Note, that only one partnerLink can be bound to any specified endpoint.
The port attribute can be used to select a particular endpoint from the service definition.
A very simple process that would only be invoked would use a deploy.xml very similar to:
<deploy xmlns="http://www.apache.org/ode/schemas/dd/2007/03" xmlns:pns="http://ode/bpel/unit-test" xmlns:wns="http://ode/bpel/unit-test.wsdl"> <process name="pns:HelloWorld2"> <active>true</active> <provide partnerLink="helloPartnerLink"> <service name="wns:HelloService" port="HelloPort"/> </provide> </process> </deploy>
See the complete example here
.
A deployment including two processes invoking each other and whose execution would be triggered by a first message would look like:
<deploy xmlns="http://www.apache.org/ode/schemas/dd/2007/03" xmlns:main="http://ode/bpel/unit-test" xmlns:mws="http://ode/bpel/unit-test.wsdl" xmlns:resp="http://ode/bpel/responder"> <process name="main:MagicSessionMain"> <provide partnerLink="executePartnerLink"> <service name="mws:MSMainExecuteService" port="MSExecutePort"/> </provide> <provide partnerLink="responderPartnerLink"> <service name="mws:MSMainService" port="MSMainPort"/> </provide> <invoke partnerLink="responderPartnerLink"> <service name="mws:MSResponderService" port="MSResponderPort"/> </invoke> </process> <process name="resp:MagicSessionResponder"> <type>resp:MagicSessionResponder</type> <provide partnerLink="mainPartnerLink"> <service name="mws:MSResponderService" port="MSResponderPort"/> </provide> <invoke partnerLink="mainPartnerLink"> <service name="mws:MSMainService" port="MSMainPort"/> </invoke> </process> </deploy>
See the complete example here
.
For performance purposes, you can define a process as being executed only in-memory. This greatly reduces the amount of generated queries and puts far less load on your database. Both persistent and non-persistent processes can cohabit in Ode.
To declare a process as in-memory just add an in-memory element in your deploy.xml:
<process name="pns:HelloWorld2"> <in-memory>true</in-memory> <provide partnerLink="helloPartnerLink"> <service name="wns:HelloService" port="HelloPort"/> </provide> </process>
Be aware that in-memory executions introduces many restrictions on your process and what it can do. The instances of these processes can't be queried by using the Management API. The process definition can only include one single receive activity (the one that will trigger the instance creation).
Before starting on what process versioning exactly does, let's see what the world (or at least Ode) would be without versioning. It will be much more easier for you to understand the solution after fully seeing the problem.
So you're starting using Ode and you've just designed you first business process. It's all nice and dandy and works perfectly. It works so well that you let your users start using it. It's not really production but you know, release early, release often, so let's see what users think of it. After a couple of days you realize that a couple of steps are missing, you add them in your process and once again, it executes smoothly. So let's see what our users think of the improvement! Next thing you know, your phone starts ringing and the user on the other side is most likely pretty upset. What happened?
So when you start using a process, executions for it are created, running processes if you like (also called process instances). Depending on the type of your business these could take a variable amount of time to execute but they're usually not instantaneous. So you have all these running processes sometimes doing things, sometimes just waiting there and all of a sudden, a brand new process definition replaces the original one all these executions have been using so far. What is a process engine to do with all these executions? Well, the most logic thing on earth: just nuke them all.
At this time there's no simple automated way to migrate a running process that has been executing using one definition to another new one. Computing the differences between the 2 definitions can be very complex and chances are that they're not even compatible! When you think of all these little tasks that are arranged just so to guarantee a perfect execution using the right data types, even minor alterations can get really tricky to apply on instances without blowing them all.
So here is the crude and sad truth: without having some versioning goodness in it, a process engine will always delete all the running instances when a new process definition is deployed.
So if existing executions can't be migrated, what are you going to do with them? Well, just let them be. Versioning is based on the fact that, instead of directly updating the original process definition (leaving its instances to their dreadful fate), another new version of this definition is created. The older one is declared retired so no new executions can be started on that one, the new process is the one to be used now on. But running instances can still finish their job peacefully as the process they've been using to execute so far is still available and unchanged.
However Ode also has the concept of deployment bundles and supports 2 modes of deployment (remotely or manually directly on the filsesystem). Let's see how we get versioning to work under those conditions.
In Ode, processes are deployed in what we call a deployment bundle. When you come down to it, it's just a zip file or a directory containing Ode's deployment descriptor (deploy.xml), the processes BPEL and all the other goodies necessary for your BPEL to run (WSDLs, schemas, xsl stylesheets, you name it). And what Ode is using to know you're redeploying the same thing is the deployment bundle name.
So when you're redeploying a deployment bundle in Ode, here is what happens:
There are a couple of additional remarks to make. The first is that the version is a single, sequentially incremented (which is to say that 3 comes after 2 and 2 comes after 1) number. All deployed bundles share the same sequence. The second thing to be aware of is that all processes in a bundle share the same version number and it's the number of their bundle.
Let's use the notation Foo-x(Bar-x, Baz-x) to represent the deployment of the Foo bundle in version x with processes Bar and Baz (sharing the same version number as just explained). The following illustrates a valid deployment sequence:
That's both tasty and healthy!
There's still a last question left unsolved: what happens if you take your bundle and deploy it under a different name with the same content. If you know a bit about source version control (like CVS or Subversion), that's very close to branching, only you might be executing two branches at the same time. As Ode can't find another bundle with the same, the processes will simply be deployed without retiring anything. You will effectively have twice the same process deployed under different versions. In that scenario you're supposed to know what you're doing.
If two identical process definitions are deployed at the same time, the behavior of the engine is unspecified. Which one of the two process will pick up the message? Who knows!? But this can be a very useful feature in specific cases when you want to deploy the same process twice (by same understand same name and same namespace) but the 2 definitions are actually different and enable different endpoints. This allows the parallel deployment of two different version of the same process provided that they don't overlap in their endpoint implementation.
Ode supports 2 different ways of deploying bundles:
The first way works just as described previously. Under the hood, your process bundle is a zip and it gets unzipped in a directory named bundlename-version. The version number is automatically generated by the engine. So you only need to provide the zip itself and a consistent bundle name.
For the second way, it's a bit more tricky. Because you're directly interacting with the deployment directory, you're allowed to create a bundle directory with any name you like (even not numbered at all). In that case Ode will still create a version number, it just won't be shown on the filesystem. However as it won't be able to find the previous bundle to retire, it will just deploy the new bundle along with all other processes, even if you already had some conflicting deployments. Basically, if you don't number your directories properly, every new deployment will be a new branch. In short, you don't really want to do that.
Another thing you're allowed to do with the file system is simply to replace (or remove and copy) all the files in the deployment bundle directory and remove the .deployed marker file to trigger redeployment. In that case Ode will simply consider you've undeployed and deployed the whole thing. So we get back to the situation where we don't have any versioning. Which can be very useful when you're in development mode because you usually don't care much about the running instances and you usually don't want to pile up versions of process definitions.
Ode has a complete management API to check which processes are deployed, running and completed instances, variables values and more. To see which methods are available, have a look at the ProcessManagement
and InstanceManagement
interfaces, the javadoc is pretty comprehensive.
These two interfaces are available as web services on the Axis2-based distribution. The corresponding WSDL can be found here
.
To invoke these two services, any web service client should work (in a perfect interoperable world). To ease the invocation when using an Axis2 client, a helper class is bundled in ode-axis2.jar: ServiceClientUtil
. Usage examples are also available in test classes InstanceManagementTest
and ProcessManagementTest
. Here is a short example demonstrating the invocation of the listAllProcesses operation:
ServiceClientUtil client = new ServiceClientUtil(); OMElement root = client.buildMessage("listAllProcesses", new String[] {}, new String[] {}); OMElement result = client.send(msg, "http://localhost:8080/ode/processes/ProcessManagement");
We're using XMLBeans
to serialize and deserialize the returned values from/to XML so in the previous example. So if you'd like to have objects instead of an Axiom
structure in the previous example, just add the following lines of code:
ScopeInfoDocument scopeIndoDoc = ScopeInfoDocument.Factory.parse(result.getXMLStreamReader());
You will need to include ode-bpel-api.jar in your classpath.
More details are available in the Process Management API specification
ODE generates events to let you track what is exactly happening in the engine and produces detailed information about process executions. These events are persisted in ODE's database and can be queried using the Management API. The default behavior for the engine is to always generate all events for every executed action. However from a performance standpoint it's a good idea to deactivate some of the events you're not interested in (or even all of them). Inserting all these events generates a non-negligeable overhead.
The following table details each event possibly generated by ODE:
| Event Name | Process/Scope | Description | Type |
|---|---|---|---|
| ActivityEnabledEvent | Scope | An activity is enabled (just before it's started) | activityLifecycle |
| ActivityDisabledEvent | Scope | An activity is disabled (due to dead path elimination) | activityLifecycle |
| ActivityExecStartEvent | Scope | An activity starts its execution | activityLifecycle |
| ActivityExecEndEvent | Scope | An activity execution terminates | activityLifecycle |
| CompensationHandlerRegistered | Scope | A compensation handler gets registered on a scope | scopeHandling |
| CorrelationMatchEvent | Process | A matching correlation has been found upon reception of a message | correlation |
| CorrelationNoMatchEvent | Process | No matching correlation has been found upon reception of a message | correlation |
| CorrelationSetWriteEvent | Scope | A correlation set value has been initialized | dataHandling |
| ExpressionEvaluationFailedEvent | Scope | The evaluation of an expression failed | dataHandling |
| ExpressionEvaluationSuccessEvent | Scope | The evaluation of an expression succeeded | dataHandling |
| NewProcessInstanceEvent | Process | A new process instance is created | instanceLifecycle |
| PartnerLinkModificationEvent | Scope | A partner link has been modified (a new value has been assigned to it) | dataHandling |
| ProcessCompletionEvent | Process | A process instance completes | instanceLifecycle |
| ProcessInstanceStartedEvent | Process | A process instance starts | instanceLifecycle |
| ProcessInstanceStateChangeEvent | Process | The state of a process instance has changed | instanceLifecycle |
| ProcessMessageExchangeEvent | Process | A process instance has received a message | instanceLifecycle |
| ProcessTerminationEvent | Process | A process instance terminates | instanceLifecycle |
| ScopeCompletionEvent | Scope | A scope completes | scopeHandling |
| ScopeFaultEvent | Scope | A fault has been produced in a scope | scopeHandling |
| ScopeStartEvent | Scope | A scope started | scopeHandling |
| VariableModificationEvent | Scope | The value of a variable has been modified | dataHandling |
| VariableReadEvent | Scope | The value of a variable has been read | dataHandling |
The second column specifies wether an event is associated with the process itself or with one of its scopes. The event type is used for filtering events.
Using ODE's deployment descriptor, it's possible to tweak events generation to filtrate which ones get created. First, events can be filtered at the process level using one of the following stanza:
<dd:process-events generate="all"/> <!-- Default configuration --> <dd:process-events generate="none"/> <dd:process-events> <dd:enable-event>dataHandling</dd:enable-event> <dd:enable-event>activityLifecycle</dd:enable-event> </dd:process-events>
The first form just duplicates the default behaviour, when nothing is specified in the deployment descriptor, all events are generated. The third form lets you define which type of event is generated, possible types are: instanceLifecycle, activityLifecycle, dataHandling, scopeHandling, correlation.
It's also possible to define filtering for each scope of your process. This overrides the settings defined on the process. In order to define event filtering on a scope, the scope activity MUST have a name in your process definition. Scopes are referenced by name in the deployment descriptor:
<dd:deploy xmlns:dd="http://www.apache.org/ode/schemas/dd/2007/03"> ... <dd:process-events generate="none"> <dd:scope-events name="aScope"> <dd:enable-event>dataHandling</bpel:enable-event> <dd:enable-event>scopeHandling</bpel:enable-event> </dd:scope-events> <dd:scope-events name="anotherScope"> <dd:enable-event>activityLifecycle</bpel:enable-event> </dd:scope-events> </bpel:process-events> ... </dd:deploy>
Note that it's useless to enable an event associated with the process itself when filtering events on scopes.
The filter defined on a scope is automatically inherited by its inner scopes. So if no filter is defined on a scope, it will use the settings of its closest parent scope having event filters (up to the process). Note that what gets inherited is the full list of selected events, not each event definition individually.
Ode lets you register your own event listeners to analyze all produced events and do whatever you want to do with them. To create a listener you just need to implement the org.apache.ode.bpel.iapi.BpelEventListener
interface.
Then add your implementation in the server's classpath and add a property in ode-axis2.properties giving your fully qualified implementation class name. Something like:
ode-axis2.event.listeners=com.compamy.product.MyOdeEventListener
Start your server and you're done!
An endpoint reference holds information to call a service. The simplest endpoint reference is usually an URL but it can also be much more complex such as holding a message id, a reply-to address or custom properties.
In BPEL, endpoint references (aka EPRs) are modeled as partner link roles. When defining a partner link, two roles maybe defined, myRole and partnerRole:
<partnerLink name="responderPartnerLink" partnerLinkType="test:ResponderPartnerLinkType" myRole="main" partnerRole="responder" initializePartnerRole="yes"/>
Both partnerRole and myRole represent EPRs. So when assigning partner link roles or invoking partners, you are using EPRs behind the scene.
The ODE runtime supports 4 types of EPRs:
We recommend the two first solutions to interact with the engine. The first one is just the easiest and for the case where you need more robustness, WS-Addressing is the most popular second choice.
To show you how these EPRs look like and how they can be assigned to partner links roles here are some examples:
<assign> <!-- Simple URL --> <copy> <from> <literal>http://localhost:8080/ode/dynresponder</literal> </from> <to partnerLink="responderPartnerLink"/> </copy> <!-- URL in soap:address element --> <copy> <from> <literal> <service-ref> <soap:address location="http://localhost:8080/ode/dynresponder"/> </service-ref> </literal> </from> <to partnerLink="responderPartnerLink"/> </copy> <!-- WS-Addressing endpoint reference --> <copy> <from> <literal> <wsa:EndpointReference> <wsa:To>http://localhost:8080/ode/dynresponder</wsa:To> </wsa:EndpointReference> </literal> </from> <to partnerLink="responderPartnerLink"/> </copy> </assign>
Normally BPEL requires wrapping EPRs with inside a service-ref element, however ODE relaxes this requirement for ease of use and increased interoperability with existing services. ODE automatically detects the different EPR types when assigning to a partner link role. If you need to use WS-Addressing sessions (@see appropriate page), then you will have to use wsa:EndpointReference EPRs.
You can just as well assign EPRs to/from variables to pass them around and enable more dynamic communication patterns.
To pass endpoint references around and manipulate them, you usually need to assigne them to variables. The EPR can then be sent in a message and reassigned to another partner link. This lets you model complex scenarii where you don't know the address of your partner beforehand or where you select one partner among many others.
The type of the variable that will hold your EPR defines the type of the EPR that it will contain. For example if you define a message in your WSDL document that looks like this:
<wsdl:message name="EndpointMessage"> <wsdl:part name="payload" element="xsd:string"/> </wsdl:message>
ODE will automatically put a simple URL EPR when you assign this message part:
<variable name="myEndpoint" messageType="resp:EndpointMessage"/> ... <assign> <copy> <from partnerLink="mainPartnerLink" endpointReference="myRole"/> <to variable="myEndpoint" part="payload"/> </copy> </assign>
Now if you want to manipulate a WS-Addressing EPR, the only thing you have to change in the above examples is the message part type. So your message will then look like this:
<wsdl:message name="EndpointMessage">
<wsdl:part name="payload" element="wsa:EndpointReference"/>
</wsdl:message>
Once your EPR has been assigned to a variable and set, say, to another process, you just need to reassign it to a partner link partnerRole to use it:
<assign> <copy> <from variable="eprmessage" part="payload"/> <to partnerLink="mainPartnerLink"/> </copy> </assign> <invoke name="eprcall" partnerLink="mainPartnerLink" portType="resp:MSMainPortType" operation="call" inputVariable="eprmessage"/>
For a complete example check DynPartner
in the engine examples.
Since version 1.2, ODE supports HTTP Binding
. ODE is almost fully compliant with the WSDL 1.1 spec. The few limitations are related to MIME types.
Actually only the following MIME types
are supported:
mime:multipartRelated
, mime:soap:body
and mime:mimeXml
are not supported.
Considering how unsuitable WSDL 1.1 HTTP Binding is for a large majority of services – especially RESTful services – a set of extensions is available.
All the details you want to know are here.
The Resource-Oriented Architecture
defines four concepts:
and four properties:
HTTP binding as defined in WSDL 1.1 is not well suitable to describe services implementing these concepts and properties, mainly because a port type may access 4 different locations/resources but with only one HTTP method.
To better describe RESTful services, and turn a port type into a "resource type", ODE brings a set of 4 extensions:
Further details below.
In this page, we use an imaginary blog service as a use case to illustrate and make things more palpable. We will focus on the resources defined by the following URI template:
http://blog.org/post/{id}
| Check out unit tests! For a complete suite of working examples, please refer to unit tests |
According to the WSDL 1.1 specification, the verb describing the HTTP method has to be at the binding level
. Which implies that the same HTTP method is used by all operations of a given port type. But RESTful web services leverage HTTP methods as a uniform interface to describe operation on resources. So for instance, if you want to use the following HTTP operations – GET, POST, PUT, DELETE – for a given resource, four different bindings would be required. And consequently four port types and four ports. Quite verbose and unusable, isn't it?
So, this extension is to push down the HTTP verb at the operation level. And if an operation does not have its own verb, then the verb defined at the binding level will be used.
This extension is declared in the namespace: http://www.apache.org/ode/type/extension/http![]()
Please note that ODE supports GET, POST, PUT, DELETE only.
<definitions ...
xmlns:odex="http://www.apache.org/ode/type/extension/http"/>
<!-- many wsdl elements are ommited to highlight the interesting part -->
<binding name="blogBinding" type="blogPortType">
<operation name="GET">
<odex:binding verb="GET" />
</operation>
<operation name="DELETE">
<odex:binding verb="DELETE"/>
</operation>
</binding>
</definitions>
A RESTful service exposed a set of resources, each of them being accessible through a uniform interface: HTTP methods for a web service. So we need a way to define four operations (at most) for a single resource.
Moreover it's very likely that the resource URI actually describes a set of resources. For instance, the set of posts contained in our imaginary blog: http://blog.org/post/\{post_id}.
HTTP binding offers the http:operation
element to set the path of an operation. While the service address is set in the http:address
of the wsdl:port
element.
So one could imagine splitting the URL this way:
<definitions ...
xmlns:odex="http://www.apache.org/ode/type/extension/http"/>
<service name="blogService">
<port name="blogPort" binding="blogPortType">
<http:address location="http://blog.org"/>
</port>
</service>
<binding name="blogBinding" type="blogPortType">
<operation name="PUT">
<odex:binding verb="PUT" />
<http:operation location="post/(post_id)"/>
<input>
<http:urlReplacement/>
</input>
<output/>
</operation>
</binding>
</definitions>
However, here 3 issues show up:
To solve this, ODE allows http:operation
elements to be omitted or empty, and the full resource URI to be set in a single place, the http:address element.
| Please note that curly brackets '{}' are the preferred argument delimiters in URI templates. So that URLs can be dynamically composed using composeUrl, combineUrl and expandTemplate XPath Functions. |
In addition, the http:urlReplacement is relaxed: all parts are not required in the URI template anymore. One part could go in the URI, another in the request body.
<definitions ...
xmlns:odex="http://www.apache.org/ode/type/extension/http"/>
<service name="blogService">
<port name="blogPort" binding="blogPortType">
<!-- here is the full URI template, using curly brackets -->
<http:address location="http://blog.org/post/{post_id}"/>
</port>
</service>
<binding name="blogBinding" type="blogPortType">
<operation name="PUT">
<odex:binding verb="PUT" />
<!-- location attribute intentionally blank -->
<http:operation location=""/>
<input>
<http:urlReplacement/>
<!-- an additional part can be mapped to the request body even if urlReplacement is used-->
<mime:content type="text/xml" part="post_content"/>
</input>
<output/>
</operation>
</binding>
</definitions>
HHTP protocal convey a lot of information in Request/Response Headers. Caching information, Content description for instance. All this data is completely left out by WSDL 1.1 HTTP Binding. To fix this, ODE provides a header element. This element can be used to insert a part or a static value into a given HTTP request header (standard or custom). And the way around, a HTTP request header into a part. Also note that all HTTP response headers are inserted into the message headers, and thus are available from the BPEL process.
<definitions ...
xmlns:odex="http://www.apache.org/ode/type/extension/http"/>
<binding name="blogBinding" type="blogPortType">
<operation name="PUT">
<odex:binding verb="PUT" />
<http:operation location=""/>
<input>
<http:urlReplacement/>
<mime:content type="text/xml" part="post_content"/>
<!-- set a standard request header from a part -->
<odex:header name="Authorization" part="credentials_part"/>
<!-- set a custom request header with a static value -->
<odex:header name="MyCustomHeader" value="ode@apache.org" />
</input>
<output>
<mime:content type="text/xml" part="post_content"/>
<!-- set 1 response header to a part -->
<odex:header name="Age" part="age_part"/>
</output>
</operation>
</binding>
</definitions>
For every HTTP response, in addition to HTTP response headers, the Status-Line
is passed as a message header. To save some painful XPath string manipulations, the Status-line is already parsed into the following structure:
<Status-Line> <HTTP-Version>HTTP/1.1</HTTP-Version> <Status-Code>200</Status-Code> <Reason-Phrase>OK</Reason-Phrase> <!-- the original unaltered Status-Line --> <original>HTTP/1.1 200 OK</original> </Status-Line>
So that you can write the following BPEL lines:
<assign> <copy> <from variable="postMsg" header="Status-Line"/> <to>$statusLine</to> </copy> </assign> <if> <condition>number($statusLine/Status-Code) > 200 and number($statusLine/Status-Code) < 300</condition> <assign> <copy> <from>'Request successful!!!'</from> <to>$outputVar.TestPart</to> </copy> </assign> </if>
Another domain completely neglected by WSDL 1.1 HTTP Binding is Fault management. The word is not even mentioned in the HTTP Binding section
.
ODE allows you to bind a fault with HTTP Binding. If a 4xx or a 5xx
is returned, the following logic is applied:
A failure is thrown if the code is one of these:
| Status-Codes triggering a Failure |
|---|
| 3xx Redirections |
| 401_UNAUTHORIZED |
| 408_REQUEST_TIMEOUT |
| 503_SERVICE_UNAVAILABLE |
| 504_GATEWAY_TIMEOUT |
else ODE checks that a fault is declared in the WSDL for the current operation and that the response body contains the expected XML element then reply with a fault ; else reply with a failure.
| Status-Codes that may trigger a Fault | if the body element matches a fault declaration |
|---|---|
| 500_INTERNAL_SERVER_ERROR | 407_PROXY_AUTHENTICATION_REQUIRED |
| 501_NOT_IMPLEMENTED | 409_CONFLICT |
| 502_BAD_GATEWAY | 410_GONE |
| 505_HTTP_VERSION_NOT_SUPPORTED | 412_PRECONDITION_FAILED |
| 400_BAD_REQUEST | 413_REQUEST_TOO_LONG |
| 402_PAYMENT_REQUIRED | 414_REQUEST_URI_TOO_LONG |
| 403_FORBIDDEN | 415_UNSUPPORTED_MEDIA_TYPE |
| 404_NOT_FOUND | 411_LENGTH_REQUIRED |
| 405_METHOD_NOT_ALLOWED | 416_REQUESTED_RANGE_NOT_SATISFIABLE |
| 406_NOT_ACCEPTABLE | 417_EXPECTATION_FAILED |
| Useful Information Note that 3xx errors should be pretty rare since by default the first hundred redirections are followed. You can tweak this value by setting the property http.protocol.max-redirects in the enpoint-configuration.properties of your process. |
<definitions ...
xmlns:odex="http://www.apache.org/ode/type/extension/http"/>
<portType name="BlogPortType">
<operation name="PUT">
<input message="..."/>
<output message="..."/>
<fault name="UpdateFault" message="tns:UpdateFault"/>
</operation>
</portType>
<binding name="blogBinding" type="blogPortType">
<operation name="PUT">
<odex:binding verb="PUT" />
<http:operation location=""/>
<input> ... </input>
<output> ... </output>
<!-- fault binding -->
<fault name="UpdateException">
<!-- name attribute is optional if there is only one fault for this operation -->
<!-- <odex:fault name="UpdateFault"/> -->
<odex:fault/>
</fault>
</operation>
</binding>
</definitions>
ODE extends the WS-BPEL in areas that aren't covered by the spec. This page details these extensions.
BPEL process instances are stateful — therefore, a client interacting with the BPEL engine must identify the particular instance with which it intends to interact in all of its communications. The BPEL specification defines a mechanism — correlation — which allows the process designer to specify which parts of an incoming message (i.e. a message going from a client to the BPEL server) should be used to identify the target process instance. Correlation is a powerful mechanism — however it is a bit complicated and relies on "in-band" message data to associate a messages with a process instance.
To keep simple cases simple, ODE provides an alternative correlation mechanism — implicit correlation — that automatically handles correlation through "out-of-band" session identifiers. The mechanism is simple: a unique session identifier is associated with every every partner link instance. When a message is sent on a partner link, the session identifier is sent along with the message. The recipient is then able to use the received session identifier in subsequent communications with the process instance. Messages received by the BPEL engine that have a session identifier are routed to the correct instance (and partner link) by that session identifier.
There are several types of error conditions. In this document we introduce a class of error condition called failures, distinct from faults, and describe how failures are caught and handled by the process engine.
For example, when the process is unable to perform DNS resolution to determine the service endpoint, it generates a failure. An administrator can fix the DNS server and tell the process engine to retry the activity. Had the DNS error been reported as a fault, the process would either terminate or require complex fault handling and recovery logic to proceed past this point of failure.
In short, failures shields the process from common, non-terminal error conditions while retaining simple and straightforward process definitions that do not need to account for these error conditions.
Apache ODE extends the default XPath coverage provided by the WS-BPEL specification mostly by adding support for XPath 2.0
and by offering a few utility extension functions to make some assignments easier.
External variables are an easy way to share data between the process and external systems, by treating those independent entities as BPEL variables that can be used in expressions and assignments. External variables may be records stored in the database, REST resources, etc.
There are several situations where one would want to access headers form the wire format in their BPEL process. It could be to assign them to another message and pass them around or to execute some business logic that's header-dependent (often a bad idea but sometimes there's no choice). ODE supports the manipulation of wire format headers in two different ways.
Extends the invoke activity to handle RESTful Web services. This extension uses BPEL variables of type xsd:uri and xsd:string instead of partner links, and does away with the WSDL indirection, making it straightforward to develop processes that use RESTful Web services.
Extends receive and onEvent to expose RESTful resources that. This extension adds the ability to declare and instantiate resources, and specific handling for the HTTP methods GET, POST, PUT and DELETE.
In most ODE deployments, processes are only used once in a while and the time between each solicitation can be pretty long with respect to the actual execution time. However the default behavior for the engine is to load all processes permanently in memory, including their definition. For environments where memory is scarce or where a large number of processes are deployed, this isn't suitable.
ODE implements two mechanisms in order to reduce the memory footprint of the engine to the strict minimum:
In the Axis2 integration layer, activation of the policy can be done by setting the following property on the ode-axis2.properties located in the WEB-INF/conf directory of ODE's web application:
ode-axis2.process.dehydration=true
The default configuration is to dehydrate processes that haven't been used for 20mn or after the maximum of 1000 process definitions in memory is reached.
If you're using your own interface layer or want to do some customization at this level, the default hydration policy is implemented in CountLRUDehydrationPolicy
. It should be set on BpelServerImpl
and can been configured by setting the process max age or max count (either one will not influence the dehydration if set to 0). For example:
CountLRUDehydrationPolicy dehy = new CountLRUDehydrationPolicy(); dehy.setProcessMaxAge(60000); // Setting process max age to one minute dehy.setProcessMaxCount(100); // Setting maximum hydrated processes to 100 _server.setDehydrationPolicy(dehy);
The dehydration policy is polled every 10s to see if some processes should be dehydrated so a process max age of less than 10 seconds will be effectively of 10 seconds. Alternatively a custom dehydration policy can be used by implementing the DehydrationPolicy
interface.
SOAP and HTTP external endpoints can be tweaked using a property file named {$process_dir}/endpoint-configuration.properties. A common set of properties are available to configure external services. At run time, ODE will translate these properties and apply them to Axis2 or HttpClient depending of the targetted service uses SOAP binding or HTTP binding.
Properties are dynamically loaded and refreshed at run time.
The timing is the following:
On every request, if the file has not been polled during the last 30 seconds then check the file for updates. If any, reload it.
Consequently, if you have updated properties, you have to wait ~30 seconds and then trigger a request.
The property file is a regular property file except that service name and port name may be used to apply different default values to different services.
All properties follow this pattern:
[nsalias.servicename[.portname].ode.]property
If service name is mentioned but port name omitted, the value will apply to all ports of the service.
If service name and port name are omitted, the value will apply to all services.
A service has to qualified. To so you may define namespace aliases. Aliases will then prefixed the service local name.
alias.ode_ns=http://ode.apache.org ode_ns.dummyservice.ode.http.request.chunk=true
If your namespace does not collide with the property syntax
, you dont have to define an alias. This property file is accepted:
# Next line is commented # alias.ode_ns=http://ode.apache.org ode_ns.dummyservice.ode.http.request.chunk=true
For instance, considering 2 services:
and this property file:
alias.test_ns=http://test.org
http.protocol.max-redirects=5
test_ns.brel-service.ode.http.protocol.max-redirects=40
test_ns.brel-service.port-of-amsterdam.ode.http.protocol.max-redirects=100
The http.protocol.max-redirect property will have the following values:
Here the list of supported properties, and their descriptions. If the file contains some properties not listed here, they will be available in the property map nevertheless. Values will be strings.
Such unmanaged properties will also be passed to Axis2 options and HttpClient params, see below.
| Context | Property name | Accepted values | Description/Notes |
|---|---|---|---|
| Bpel-runtime | mex.timeout | a long | the Ode Message Exchange timout |
| Outbound Services | http.request.chunk | true/false | This will enable/disable chunking support. Will not apply to http-bound services TBD |
| http.protocol.version | HTTP/1.1 or HTTP/1.0 | the HTTP protocol version used | |
| http.request.gzip | true/false | Will not apply to http-bound services TBD | |
| http.accept.gzip | true/false | Append gzip to the Accept-Encoding header | |
| http.protocol.encoding | a string | ||
| http.default-headers.{your-header} | You must define one property per header, prefixed with 'http.default-headers'. These values will be appended to any previous value already set for a given header. |
||
| http.connection.timeout | an int | timeout in milliseconds until a connection is etablished | |
| http.socket.timeout | an int | timeout in milliseconds for waiting for data | |
| http.protocol.max-redirects | an int | the maximum number of redirects to be followed | |
| http.proxy.host=myproxy.org | To disable proxy set the host to null | ||
| http.proxy.port | |||
| http.proxy.domain | |||
| http.proxy.user | |||
| http.proxy.password |
endpoint-configuration.properties.txt![]()
Download, remove the '.txt' prefix, customize then enjoy...
The properties related to Outbound services have to be applied to Axis2 (for SOAP services) or HttpClient (for HTTP services).
Tables below sum up this information.
For Axis2, all properties are converted to meet the Options#setProperty()
requirements.
| Property name | Axis2 | Description/Notes |
|---|---|---|
| http.request.chunk | Options.setProperty(HTTPConstants.CHUNKED, ?) | |
| http.protocol.version | Options.setProperty(HTTPConstants.HTTP_PROTOCOL_VERSION, ?) | |
| http.request.gzip | Options.setProperty(HTTPConstants.MC_GZIP_REQUEST, ?) | |
| http.accept.gzip | Options.setProperty(HTTPConstants.MC_ACCEPT_GZIP, ?) | |
| http.protocol.encoding | Options.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING,?) | |
| http.default-headers.* | Options.setProperty(HTTPConstants.HTTP_HEADERS, ?) | |
| http.connection.timeout | Options.setProperty(HTTPConstants.CONNECTION_TIMEOUT, ?) | |
| http.socket.timeout | Options.setProperty(HTTPConstants.SO_TIMEOUT, ?) | |
| http.protocol.max-redirects | not applied to Axis2 services | |
| http.proxy.* | Options.setProperty(HTTPConstants.PROXY, ?); |
For HttpClient, all properties are defined by: HttpMethodParams
, HostParams, HttpClientParams, HttpConnectionParams and HttpConnectionManagerParams.
The idea is to convert properties into the expected type and push them in a DefaultHttpParams
. This property holder is then set as the parent of all other HttpParams used.
If a given property is not listed below, you are still able to set it as long as the expected value is a string. For instance "http.protocol.cookie-policy"
can be set seamlessly.
| Property name | HttpClient | Description/Notes |
|---|---|---|
| http.request.chunk | EntityEnclosingMethod.setContentChunked() | |
| http.protocol.version | HttpParams.setParameter(HttpMethodParams.PROTOCOL_VERSION, ?) | |
| http.request.gzip | not supported | |
| http.accept.gzip | not supported | |
| http.protocol.encoding | HttpParams.setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,?) | |
| http.default-headers.* | HttpParams.setParameter(HTTPConstants.HTTP_HEADERS, ?) | |
| http.connection.timeout | HttpParams.setParameter(HttpConnectionParams.CONNECTION_TIMEOUT, ?) | |
| http.socket.timeout | HttpParams.setParameter(HttpMethodParams.SO_TIMEOUT, ?) | |
| http.protocol.max-redirects | HttpParams.setParameter(HttpClientParams.MAX_REDIRECTS, ?) | |
| http.proxy.* | Cannot be set with simple properties. Custom code added. |
If you deploy ODE in an application server, the Axis2 Integration Layer allows ODE to communicate via Web Service interactions. SOAP Web Services will be managed by Axis2. These web services may be configured dynamically.
For each service, you need to place a [serviceLocalName].axis2 file at the root of the process bundle. Currently, this file can only be added on the server, under var/processes/{$process_dir}.
For example, given this wsdl:
<?xml version="1.0" encoding="utf-8"?> <wsdl:definitions ...> <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">A sample Time service</wsdl:documentation> ... <wsdl:service name="TimeService"> <wsdl:port name="TimeServiceSoap" binding="tns:TimeServiceSoap"> <soap:address location="http://ws.intalio.com/TimeService/" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
You would place a TimeService.axis2 file with:
<service name="TimeService" scope="application" targetNamespace="http://ws.intalio.com/TimeService/"> <module ref="sandesha2" /> <module ref="addressing" /> </service>
This will engage Engage sandesha2 and addressing modules.
On every request, if the config file has not been polled during th