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>
$ mysql -u root mysql> create database ode; mysql> exit $ mysql -u root ode < ode_openjpa_mysql-1.3.2.sql
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 |
| ActivityFailureEvent | Scope | An activity failed | 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> </dd: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, without the wrapper --> <copy> <from> <literal>http://localhost:8080/ode/dynresponder</literal> </from> <to partnerLink="responderPartnerLink"/> </copy> <!-- Simple URL, wrapped in an 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 EPR, without the wrapper --> <copy> <from> <literal> <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing"> <wsa:To>http://localhost:8080/ode/dynresponder</wsa:To> </wsa:EndpointReference> </literal> </from> <to partnerLink="responderPartnerLink"/> </copy> <!-- WS-Addressing EPR, with the wrapper --> <copy> <from> <literal> <service-ref> <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing"> <wsa:To>http://localhost:8080/ode/dynresponder</wsa:To> </wsa:EndpointReference> <service-ref> </literal> </from> <to partnerLink="responderPartnerLink"/> </copy> <!-- WSDL1.1 EPR, without the wrapper --> <copy> <from> <literal> <wsdl:service xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" name="DynService" targetNamespace="http://org.apache.ode/examples/dynservice"> <wsdl:port name="DynPort"> <soap:address location="http://localhost:8080/ode/dynresponder"/> </wsdl:port> </wsdl:service> </literal> </from> <to partnerLink="responderPartnerLink"/> </copy> <!-- WSDL2.0 EPR, with the wrapper --> <copy> <from> <literal> <service-ref> <wsdl:service xmlns:wsdl="http://www.w3.org/2006/01/wsdl" name="DynService" targetNamespace="http://org.apache.ode/examples/dynservice"> <wsdl:port name="DynPort"> <soap:address location="http://localhost:8080/ode/dynresponder"/> </wsdl:port> </wsdl:service> </service-ref> </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. If the service-ref element is absent, the EPR is automatically wrapped inside one on the fly. Moreover, 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 compose-url, combine-url and expand-template 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 |
| 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. |
Here what ODE does, if the status code is one of those listed in the next table (500, 501, etc):
If so far everything is fine, the HTTP response body is parsed into an xml document. Then the fault to be thrown is inferred from the qname of the response root element, i.e the fault having a message part matching the root element. This matching process is exactly the same as for a SOAP service.
If one of these steps fails, a failure is thrown.
| 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 |
Note that you can't bind a given fault to a specific status code.
<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 provides a plug-in architecture for custom activity implementations and custom variable assignment logic. Such plug-ins can be registered to ODE and can be used according to the WS-BPEL 2.0 extensibility mechnisms (<extensionActivity> & <extensionAssignOperation>).
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.
Extends the <forEach> activity so that the counter variable iterates over the items contained in a given sequence. This extension provides an alternative and more dynamic way of generating M branches, where M is the size of the binding sequence. The semantics of the <completionCondition> stays the same, in that the loop is deemed to be complete if B out of the M branches complete (successfully), where B is its actual value.
Extends the <assign> activity so that it can be made to either ignore or insert data that is missing in the to-spec of a copy operation. This shortcut allows users to handle what would normally be fault scenarios, in a more graceful and intuitive way.
Extends BPEL and ODE to allow the circulation of transport metadata (like security tokens, correlation keys, or tracing informations) from messages to processes and then from processes to messages.
During its execution, a process instance can accumulate a significant amount of data. The running process itself isn't that much of an issue, when the instance is done there's nothing left to execute. But the process data can be rather big, mostly because of the messages it received and sent and its own variables. All of these are XML documents and in some cases, sizable ones.
This feature is only available in Ode 1.3 or later
The easiest approach to get started is simply to wait until the instance execution is finished and then cleanup everything that's related to it. That would include the instance state with its variables, scopes and correlation, but also all the messages it has received and sent. Execution events should also be disposed of. So this description defines 5 different categories: instance, messages and events. We should be able to turn on and off each level separately.
<xs:element name="cleanup" minOccurs="0" maxOccurs="3" type="dd:tCleanup" />
<xs:complexType name="tCleanup">
<xs:sequence>
<xs:element name="category" default="all" minOccurs="0" maxOccurs="unbounded">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="instance" />
<xs:enumeration value="variables" />
<xs:enumeration value="messages" />
<xs:enumeration value="correlations" />
<xs:enumeration value="events" />
<xs:enumeration value="all" />
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
<xs:attribute name="on" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="success" />
<xs:enumeration value="failure" />
<xs:enumeration value="always" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
1. no instance data cleanup
<process name="pns:HelloWorld2">
<active>true</active>
<provide partnerLink="helloPartnerLink">
<service name="wns:HelloService" port="HelloPort"/>
</provide>
</process>
2. cleaning up all data on either successful or faulty completions of instances
<process name="pns:HelloWorld2">
<active>true</active>
<provide partnerLink="helloPartnerLink">
<service name="wns:HelloService" port="HelloPort"/>
</provide>
<cleanup on="always" />
</process>
3. cleaning up all data on successful completions of instances and no data cleanup on faulty completions
<process name="pns:HelloWorld2">
<active>true</active>
<provide partnerLink="helloPartnerLink">
<service name="wns:HelloService" port="HelloPort"/>
</provide>
<cleanup on="success" >
<category>instance</category>
<category>variables</category>
<category>messages</category>
<category>correlations</category>
<category>events</category>
</cleanup>
</process>
4. cleaning up all data on successful completions of instances and only messages and correlations on faulty completions
<process name="pns:HelloWorld2">
<active>true</active>
<provide partnerLink="helloPartnerLink">
<service name="wns:HelloService" port="HelloPort"/>
</provide>
<cleanup on="success" >
<category>all</category>
</cleanup>
<cleanup on="failure">
<category>messages</category>
<category>correlations</category>
</cleanup>
</process>
5. an invalid configuration; the instance category should accompany the variable and correlations categories
<process name="pns:HelloWorld2">
<active>true</active>
<provide partnerLink="helloPartnerLink">
<service name="wns:HelloService" port="HelloPort"/>
</provide>
<cleanup on="success" >
<category>all</category>
</cleanup>
<cleanup on="failure">
<category>instance</category>
</cleanup>
</process>
WS-BPEL makes heavy use of scopes, those could be another hook in the execution lifecycle for the cleanup to take place. So instead of waiting until the instance is finished and clean up the whole state, we could proceed by smaller increments and delete the state scope by scope. For short running processes (say less than a few days) the advantages of this approach are minimal but for long running processes (say months), there's potentially a lot of unused state that's just sitting there and will never be used anymore.
When we continue along the lines of refining further when the cleanup should occur and what exactly should be cleaned up, we quickly start getting close to the transaction boundaries. Down the road, ideally, we shouldn't persist anything unnecessarily, so that no cleanup is needed when a given piece of data will never be reused. It's often the case for message variables for example, where a process will receive a message, assign some values from it and never use that message variable anymore. So this should never get written, minimizing the writes and deletes.
Ode automatically optimizes all process-to-process communication such that all message exchanges happen directly inside the engine and do not go through the integration layer (e.g. Axis2, JBI, ...).
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 in the ode-axis2.properties file, which is 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.
However, you may override the time that the process have to remain unused before they can be considered for dehydration by specifying a value, in milliseconds, for the following property in the ode-axis2.properties file:
# wait for 5 minutes instead of 20 minutes
ode-axis2.process.dehydration.maximum.age=300000
Similarly, you may override the maximum number of process definitions that may remain hydrated at any given point in time by specifying a value for the following property in the ode-axis2.properties file:
# allow not more than 500 processes to be in memory at once ode-axis2.process.dehydration.maximum.count=500
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 some property files. 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 if the targeted service uses SOAP binding or HTTP binding.
The extension of these property files must be *.endpoint. These files can be dropped in 2 different locations:
Two rules of precedence apply:
| Version Information This feature is available in ODE > 1.1. For version 1.1, a file named endpoint-configuration.properties may be dropped in process deployment unit directories. For backward compatibility, ODE > 1.1 supports this file too. |
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 be qualified. To so do 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 |
| mex.failure.verbose | true(default)/false | mute the details of a failure that might occur during a process invocation (to avoid information disclosure for instance) | |
| Outbound Services | address | a URL | overrides the soap:address or http:address. The order of precedence is: process, property files, wsdl. |
| 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.content-charset | 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 | |||
| jms.reply.destination | a string | the reply-to destination in an outbound message | |
| jms.reply.timeout | an int | timeout in milliseconds for waiting for two-way reply | |
| ws-adddressing.headers | true(default)/false | Enable/disable the WS-Addressing headers for outgoing soap requests |
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.content-charset | 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, ?); | |
| jms.reply.destination | Options.setProperty(JMSConstants.REPLY_PARAM, ?); | |
| jms.reply.timeout | Options.setProperty(JMSConstants.JMS_WAIT_REPLY, ?); |
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.content-charset | 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 the last 30 seconds then check the file for changes and (re)load it if any.
For further details about the type of parameters that can be set, see Axis2 Service Config.
This mechanism is available for external SOAP services – i.e. services that processes invoke – and for SOAP services exposed by ODE – i.e. services exposed by BPEL processes.
The service.xml could be quite powerful. However all options are not accessible this way. Many settings are available programmatically only using the Options class.. ODE exposes these settings through an additional property file described in the previous section.
This section explains how to perform authentication against Web services requiring HTTP basic, digest or NTLM authentication mechanisms.
| Non-Standard This mode of authentication is non-standard in the Web service world because the authentication data is passed outside of the SOAP message. This feature is still experimental and requires Ode >1.1 |
To perform authentication, you must pass an additional message part containing the general authentication element which contains the credentials (as plain-text strings)
<auth:authenticate xmlns:auth="urn:ode.apache.org/authentication"> <auth:username/>? <auth:password/>? <auth:domain/>? <!-- NTLM specific --> <auth:realm/>? <!-- NTLM specific --> <auth:token/>? </auth:authenticate>
This additional message part may be declared in the WSDL definition to allow tools to validate the data structure:
<?xml version='1.0' encoding='utf-8'?> <wsdl:definitions xmlns:tns="http://www.example.com" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:auth="urn:ode.apache.org/authentication" targetNamespace="http://example.com"> <wsdl:import namespace="urn:ode.apache.org/authentication" location="Authentication.xsd"/> <wsdl:message name="MyRequest"> <wsdl:part name="body" element="tns:HelloText"/> <wsdl:part name="authenticate" element="auth:authenticate"/> <!-- Additional part --> </wsdl:message> </wsdl:definitions>
Notes:
The schema of the authenticate element follows:
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:ode.apache.org/authentication" elementFormDefault="qualified"> <xs:element name="authenticate"> <xs:complexType> <xs:sequence> <xs:element name="username" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="password" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="domain" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="realm" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="token" minOccurs="0" maxOccurs="1"> <xs:complexType> <xs:sequence> <xs:any minOccurs="1"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
You can add this schema to your project to allow tools to display/validate the correct element structure.
These instructions will help you configure a JNDI DataSource for Apache Ode when running inside the ServiceMix JBI container.
Declare a managed connection factory pointing to your database:
<bean id="odeManagedConnectionFactory" class="org.jencks.tranql.DataSourceMCF">
<property name="driverName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://hostname/databaseName"/>
<property name="user" value="username"/>
<property name="password" value="myPassword"/>
</bean>
And register the DataSource in the JNDI registry by adding an <entry> under the <util:map> element:
<util:map id="jndiEntries"> <!-- Ode DataSource --> <entry key="java:comp/env/jdbc/ode"> <bean id="odeDataSource" class="org.jencks.factory.ConnectionFactoryFactoryBean"> <property name="managedConnectionFactory" ref="odeManagedConnectionFactory"/> <property name="connectionManager" ref="connectionManager"/> </bean> </entry> <!-- ... other entries follow... --> </util:map>
In ode-jbi.properties, set the following properties:
ode-jbi.db.mode=EXTERNAL ode-jbi.db.ext.dataSource=java:comp/env/jdbc/ode
(Be sure to match the JNDI lookup name to the one defined in $SERVICEMIX/conf/jndi.xml)
Copy jencks-2.0-all.jar under $SERVICEMIX/lib
And you're done! Don't forget to redeploy your service assemblies since they need to be re-synchronized with Ode.
If you want to manually configure the connection pool parameters, edit $SERVICEMIX/conf/tx.xml and update the "poolingSupport" object. For example,
<jencks:poolingSupport id="poolingSupport" connectionMaxIdleMinutes="5" connectionMaxWaitMilliseconds="10000" poolMaxSize="100" poolMinSize="20" />
ODE has a test framework to automatically run BPEL processes. A big part of our test harness is therefore many different BPEL processes that test specific BPEL configurations or interactions. If you run into a problem with one of your processes that seems to be a bug, the best way to get it fixed is to contribute a test case to the project. We'll run it and keep it to prevent regressions. The more test cases we have, the more robust ODE will be.
This small guide will just explain you how to write and structure a test case to include it in ODE's test suite. For those who rather have examples than explanations, you can already check all existing test cases.
An automated test system has to make some assumptions about your test cases to reduce the complexity of running them. Therefore ODE's test framework can't run absolutely any process. There are a couple of limitations to be aware of:
Other than that your process can do anything and can use all the WSDL, schemas and XSL stylesheets you need.
So to begin with your test process must at least have a BPEL file, a WSDL file and the standard deploy.xml deployment descriptor (links are provided for the HelloWorld test case). All of these should be included in a single directory.
Then for the test framework to know what it should do you will also need to write a simple test descriptor. It's a simple properties file saying which service is implemented by the process and which messages should be sent to start it and make it continue. It should me named test?.properties with the '?' being a increasing number. Here is the descriptor for the HelloWorld example, in test1.properties:
namespace=http://ode/bpel/unit-test.wsdl
service=HelloService
operation=hello
request1=<message><TestPart>Hello</TestPart></message>
response1=.*Hello World.*
The 3 first lines specify the namespace, the name and the operation to which messages will be sent. Then comes the request message. It should always be named request?, and should always be wrapped in a message element and another element named like the message part. Finally, and most importantly, comes the response test pattern. It's a regular expression that will be checked against the response produced by the process. If the expression can't be found, the test fails. In the HelloWorld example here we're just testing that our response includes 'Hello World'.
A test descriptor can contain more that one request/response couple, allowing several executions of a same process, testing different input/output combinations. You just need to increase the numbers in the request and response property names. For an example see the descriptor of the flow test.
Also if your process needs to receive several messages, you can have several test descriptors, each corresponding to a message. The files should just be named test1.properties, test2.properties, ... following the order of invocation. Here is an example with 2 descriptors used by the correlation test case:
test1.properties namespace=http://ode/bpel/unit-test/testCorrelation.wsdl service=testCorrelationService operation=request request1=<message><requestMessageData><testMessage><requestID>Start Test5.1</requestID><requestText>Event Start Test5.1</requestText><requestEnd>no</requestEnd></testMessage></requestMessageData></message> response1=ASYNC test2.properties namespace=http://ode/bpel/unit-test/testCorrelation.wsdl service=testCorrelationService operation=continue request1=<message><requestMessageData><testMessage><requestID>Start Test5.1</requestID><requestText>Event Start Test5.2.1</requestText><requestEnd>yes</requestEnd></testMessage></requestMessageData></message> response1=.*Event Start Test5.1 -> loop on receive until message includes requestEnd = yes -> received message -> process complete.*
Finally a response can be marked as being ASYNC in the case no reply is expected for a given receive. This only applies to non instantiating receives:
response1=ASYNC
Writing isolated BPEL processes isn't something easy and for more advanced test cases you often need a bit more. The test framework therefore includes 2 mocked services to help you: the probe service and the fault service. Be aware however that the usage of these services require a bit more understanding on the BPEL that you're going to execute.
The probe service makes it easy to track the path that has been taken by a process execution by appending strings that are specific to one execution case. It basically takes a string that you pass and appends it to a global process execution string that you can test in the end. Let's see with a pseudo-code example:
receive(foo) probe("received message " + foo.name) if (foo.value > 50) probe("big value") else probe("small value") end reply(probeStr)
Once this has been executed you can check whether the probeStr produced as a reply contains both "received message" and "big value" or "received message" and "small value" using a response regular expression.
Practically the probe service takes 2 parts: probeName and probeData. The probeName part should contain what you wnat to append, the probeData part will contain the appended string after the call and shouldn't be modified once it's been initialized. The probeData part will effectively contain the successive appended strings and that's what you're going to test at the end of the execution.
Here is a usage example extracted from the correlation test case:
<receive name="receive1" partnerLink="request" portType="wns:testCorrelationPT" operation="request" variable="request" createInstance="yes"> <correlations> <correlation set="testCorr1" initiate="yes"/> </correlations> </receive> <!-- Copy input variables to internal accumulators --> <assign name="assign1"> <copy> <from variable="request" property="wns:testProbeID"/> <to variable="probeInput" part="probeName"/> </copy> <copy> <from variable="request" property="wns:testProbeData"/> <to variable="probeInput" part="probeData"/> </copy> </assign> <assign> <copy> <from> <literal><![CDATA[loop on receive until message includes requestEnd = yes]]></literal> </from> <to variable="probeInput" part="probeName"/> </copy> </assign> <invoke name="probe" partnerLink="probe" portType="prb:probeMessagePT" operation="probe" inputVariable="probeInput" outputVariable="probeInput"/>
The first assign initializes the probe parts with the input message. The second one places in probeName the text that should be appended. After the call to the probe service, probeData will contain both information appended. Then to return the probeData at the end of the execution:
<assign name="assign2"> <copy> <from variable="probeInput" part="probeName"/> <to variable="reply" part="replyID"/> </copy> <copy> <from variable="probeInput" part="probeData"/> <to variable="reply" part="replyText"/> </copy> </assign> <reply name="reply" partnerLink="request" portType="wns:testCorrelationPT" operation="continue" variable="reply"/>
The returned data is finally tested by using a nice regular expression for the response:
response1=.*Event Start Test5.1 -> loop on receive until message includes requestEnd = yes ->
received message -> process complete.*
A complete usage example can be found with the correlation test case.
When invoked, the fault service (as the name says) will return a fault. It's mostly used to test fault handlers and compensation. To invoke the fault service just use:
<invoke name="throwTestFault" partnerLink="fault" portType="flt:faultMessagePT" operation="throwFault" inputVariable="fault" outputVariable="faultResponse"/>
The only types of fault that can be thrown for now are FaultMessage1, FaultMessage2 and UnknownFault in the http://ode/bpel/unit-test/FaultService.wsdl namespace. For more details see the example in the implicit fault handler test.
| Only in 1.3.2 The features describe in this section are shipped in ODE 1.3.2 and newer |
ODE 1.3.2 introduces support for WS-Security: secure services can now be invoked from a process, and the process service itself might be secured. A first part will explain how to invoke a secured service, a second part how to secure the process service.
ODE has an Integration Layer based on Axis2 so using Rampart, the Axis2 security modules, goes without saying. As a result this section will only focus on Rampart integration. Rampart and WS-Security specifications won't be detailed here. Please refer to their ad-hoc documentations for further details.
As any other Axis2 module, Rampart is configurable with Axis2 Service configuration files. For instance a service.xml document, using the parameter based configuration model, might be:
<service> <module ref="rampart" /> <parameter name="OutflowSecurity"> <action> <items>Timestamp Signature</items> <user>client</user> <signaturePropFile>TestRampartBasic/secured-services/client.properties</signaturePropFile> <passwordCallbackClass>org.apache.rampart.samples.sample04.PWCBHandler</passwordCallbackClass> <signatureKeyIdentifier>DirectReference</signatureKeyIdentifier> </action> </parameter> <parameter name="InflowSecurity"> <action> <items>Timestamp Signature</items> <signaturePropFile>TestRampartBasic/secured-services/client.properties</signaturePropFile> </action> </parameter> </service>
Another example using WS-Security Policy based configuration model is listed below. See the full document here.
<service> <module ref="rampart"/> <wsp:Policy wsu:Id="SecConvPolicy2" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"> <wsp:ExactlyOne> <wsp:All> <sp:SymmetricBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <!-- truncated, see original document ny following the link above --> </wsp:Policy> </sp:SymmetricBinding> <sp:Wss11 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <!-- truncated, see original document ny following the link above --> </wsp:Policy> </sp:Wss11> <sp:Trust10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <!-- truncated, see original document ny following the link above --> </wsp:Policy> </sp:Trust10> <sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <sp:Body/> </sp:EncryptedParts> <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> <ramp:user>client</ramp:user> <ramp:encryptionUser>service</ramp:encryptionUser> <ramp:passwordCallbackClass>org.apache.rampart.samples.policy.sample04.PWCBHandler</ramp:passwordCallbackClass> <ramp:signatureCrypto> <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin"> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type">JKS</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.file">TestRampartPolicy/secured-services/client.jks</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.password">apache</ramp:property> </ramp:crypto> </ramp:signatureCrypto> <ramp:encryptionCypto> <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin"> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type">JKS</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.file">TestRampartPolicy/secured-services/client.jks</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.password">apache</ramp:property> </ramp:crypto> </ramp:encryptionCypto> </ramp:RampartConfig> </wsp:All> </wsp:ExactlyOne> </wsp:Policy> </service>
The important thing to notice is that these documents are plain Axis2 Service configuration files. And as explained in the ODE User Guide, a mechanism to handle these files already exists. So all we have to do is reuse this mechanism, the rest is pure Rampart configuration.
Let's take an example and see the actual required steps.
Assuming your process needs to invoke the secure service {http://sample03.policy.samples.rampart.apache.org}Sample03, the first step is to prepare a service document named ${process_bundle_dir}/Sample03.axis2 and containing your desired Rampart configuration.
The second step is to to make sure the resources needed to invoke the services are available to Rampart through ODE webapp classpath. Typical resources are :
How you add these resources to ODE classpath might vary depending on your application server, your global architecture or other criteria. So it's up to you to figure this out. However typical locations are:
If you're using the policy base configuration model, an alternative is available to you: use the endpoint property mechanism to attach the policy to the service. In that configuration, ODE will engage the Rampart module and load the policy when the service is invoked.
To do so:
alias.sample03-ns=http://sample03.policy.samples.rampart.apache.org
sample03-ns.sample03-policy.ode.security.policy.file=mypolicy.xml
Applying security to a process service is no different from invoking a secured service. If the process service you're exposing is {http://mycompany.com}AbscenceRequest. All you have to do is prepare a service document named ${process_bundle_dir}/AbscenceRequest.axis2 and containing your Rampart configuration. Once again, it's up to you to add the required resources in ODE webapp classpath.
You can also use the property 'security.policy.file' to secure the process service.
No. ODE comes with the following Axis2 modules (and the jars they depend on): Rampart, Rahas and Addressing.
$ cd axis2-war $ buildr test:Secure
The executed processes are generated by the build, so run the tests once, then look into the following directories. Process directories are prefixed with "process-".
The integration with Rampart described in this section is tested with a decent suite of unit tests. These unit tests are based on the Rampart samples. The related resources were imported into ODE repository.
These tests are divided into two parts: tests using the parameter base configuration model aka "basic tests" and tests using the policy base configuration model aka "policy tests".
ODE test cases reuse these test cases in two different scenarii:
These partitions lead to four resource directories:
Everything describes for TestRampartBasic applies to TestRampartPolicy. So for now on we will mention only TestRampartBasic.
For the "secured-services" scenario, the "external" web services are Axis archives deployed in an Axis2 webapp.
The corresponding unit test classes are SecuredServicesTest.java and SecuredProcessTest.java. Each test class will start a list of processes that must succeed (as many processes as Rampart samples actually).
To avoid duplication these processes are generated by the build based on two process templates: one for the secured-services case, another for the secured-processes case.
The build generates processes into:
