This project has retired. For details please refer to its Attic page.
Apache ODE – WSDL 1.1 Extensions for REST

WSDL 1.1 Extensions for REST

Overview

The Resource-Oriented Architecture defines four concepts:

  1. Resources
  2. Their names (URIs)
  3. Their representations
  4. The link bet ween them

and four properties:

  1. Addressability
  2. Statelesness
  3. Connectedness
  4. A uniform interface

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:

  1. one HTTP method per operation (instead of one per binding)
  2. a unique URI Template for all operations
  3. access to HTTP headers
  4. fault support

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}

Let's assume that such a resource accept four operations:

  • GET to retrieve a post
  • DELETE to delete it
  • PUT to update the post
  • POST to add a comment to a post

Check out unit tests!

One verb per operation

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>

URI Template

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:

  • the location is not accessible from the End Point Reference. => ODE cannot process it before invoking the external service.
  • http:urlReplacement is only accepted for GET => what about the uniform interface?!
  • http:urlReplacement requires all parts of the operation input message to be mentioned in the operation location. Meaning that:
  • => the resource id (in the present example) must be a part of the message.
  • => no parts may be stored in the HTTP body. this conflicts with a PUT operation for instance. With a PUT you would like to set the id in the url and the resource data in the HTTP request body.

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>

HTTP Headers manipulation

HTTP 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>

Fault Support

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
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):

  • check that the operation has at least one fault in its abstract part, and one fault binding
  • check that the Content-type header describes an xml document ('application/xml', 'application/atom+xml' etc)
  • check that the body is not empty

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>