Nov 8, 2011

RESTful Services with Oracle Service Bus

 Recently I was asked about REST (see http://en.wikipedia.org/wiki/Representational_State_Transfer)

support in Oracle Service Bus. OSB 10gR3 has full support for REST and
implementing a REST approach in a proxy service is very simple. In this
example, we will create a proxy service that provides a REST interface to a
product catalog.

Why use OSB for REST?

Using the Oracle Service Bus to provide an interface as
simple as REST may seem odd at first. There are several good reasons for this
approach though.
  1. You automatically get all of the monitoring, reporting, security and more provided
    by OSB for your RESTful services

  2. You keep the benefits of mediation. For example, of our company in this example
    purchased another company that provided different products, you could use the
    OSB to act as a façade over both product catalogs while providing a single,
    unchanged REST interface.

  3. You
    can REST-ify existing, non-service enabled assets like EJBs

The Products Proxy Service

Let's get started. First we will create a new OSB
Configuration Project to house our REST services. I named the configuration
project RESTConfig in the sample source code. You can download
the sample source code here.
Inside of the RESTConfig project I then
created a standard OSB project named SimpleREST. Once the OSB
project was created, I was ready to start creating my REST project.
Next I created a proxy service named Products.
I used the default endpoint URI of SimpleREST/Products but in a
production environment you'll most likely want to customize that. Be sure to
define your proxy service as an Any XML Service so you can accept
and return XML of any flavor.
Now it is time to create our message flow. Here is where
it gets interesting. One of the fundamental concepts in REST is the use of the
native HTTP methods to allow the service consumer to express their intent. The
specific HTTP methods that we are interested in are: GET, POST, PUT, HEAD,
OPTIONS and DELETE. In this example I don't make use of the TRACE method.
However, you will be able to add that to your projects easily after reading
this.
The handling of each of those methods is different, so we
begin our message flow with a Conditional Branch node (see figure 1). I added a
branch for each of the methods I was interested in. In the Conditional Branch
node I set the XPath field to ./ctx:transport/ctx:request/http:http-method/text()
and theIn Variable field to inbound. This is the equivalent to the XPath
expression $inbound/ctx:transport/ctx:request/http:http-method/text().
The text() operation at the end is important because I
want to deal with the method in its plain text form and not as an XML
expression.


image001.jpg

Figure 1 Standard OSB REST Pattern
When adding the branches it is important to enclose the
contents of the Value field in single quotes (Figure 2) to denote it is a
string value. If you forget this, you will get an error about an unbound XML
node when you run the proxy service because OSB will treat the contents of the
Value field as a variable instead of a string literal.
image002.jpg
Figure 2 Don't forget the quotes!
For each branch, add a pipeline pair and name it
appropriately, as shown in figure 1. Before we fill in the GET Pipeline with
the necessary logic, let's take a moment to decide how we want the HTTP GET method
to be handled. Ideally, we want to be able to point a web browser or other REST
service consumer to a URL like: http://localhost:7001/SimpleREST/Products/id=# to
retrieve a product representation by its ID. Since the proxy service is
listening at http://localhost:7001/SimpleREST/Products,
the remainder of the URL (i.e. the id=#) will be accessible in the message
flow as the relative-URI part of the $inbound variable.

The GET Method

Now that we know what the URL should look like for a GET,
we can fill in the logic. In the GET Pipeline (Figure 3) we will add two Assign
actions. The first Assign action will get the first part of the relative URL
(i.e. the id part). We won't do anything
with this part in this example, but you can use this technique to ensure that
the URL used had the correct format. The expression to use in the first Assign
statement is as follows:

fn:tokenize(data($inbound/ctx:transport/ctx:request/http:relative-URI),
'=')[1]
This expression will take the relative-URL value and
tokenize it using the '=' character, returning the first token. So if the
relative-URI is "id=27" this expression will return the "id" string. Why XPath
counts from 1 instead of 0 I don't know. Set the Variable field to attr and you are
done with the first Assign statement.
The second Assign statement is very similar to the first,
except we are retrieving the second part of the relative-URI. Its expression is
as follows:

fn:tokenize(data($inbound/ctx:transport/ctx:request/http:relative-URI),
'=')[2]
Set the Variable field to productID. In the sample code I include
a Log statement to output the contents of these variables to the console
image003.jpg
Figure 3 The GET Pipeline
In the Response Pipeline we will construct our response
message for the GET. We will just mock up a product document for this tutorial.
The following code snippet (which you will put into the Expression field
of the Replace action) shows the value that the Replace action will put into
the $body
variable, which is then returned to the caller.


{$productID}
Foobar
19.99
Listing 1 Constructing the response document
Set the XPath field to just a period (.) and be
sure to select the Replace Node Contents  radio button. If you deploy
the project you can test it using your web browser. Assuming your OSB server is
running on the default port 7001, you can use the following URL:
http://localhost:7001/SimpleREST/Products/id=2
Your web browser should show you the following:


2
Foobar
19.99
Note: Capitalization counts even with REST
Alternatively, you can use the OSB Test Console instead of
a web browser. I recommend using the Test Console (Figure 4) because it gives
you more control over the service call and visibility into the message flow. To
test the GET method, right-click on the Products.proxy file in the
Workshop IDE and select Debug As -> Debug on Server.... When the Test
Console appears, click on the down-arrow icon in the Transport section. This
will allow you to set the relative-URI and http-method fields directly.
image004.jpg
Figure 4 The Transport section of the Test Console
When you are happy with your settings, press the Execute
button to run the test. The Test Console (Figure 5) will return the results and
provide you with the response metadata and the invocation trace, which is
useful when debugging your proxy services.
image005.jpg
Figure 5 The Test Console response
Now that we have the GET branch taken care of. Let's take
a quick look at the other HTTP methods that we are supporting.

The POST Method

The HTTP POST method is traditionally used with REST to
create new entities. The message content that is POSTed can be in XML format, or
in the format used by HTML forms, which is still the most common mechanism for
submitting data to a web site in use today. However,, I highly recommend that
you keep your information in XML format instead of the HTML Form format simply
because the HTML Form only  supports the GET and POST verbs, whereas with REST
we also want to use the other verbs, like PUT and DELETE.
The POST Pipeline shows how to handle the POST method. It's
pretty simple in that it extracts the specific information it wants from the
$body and then creates and returns an XML representation, simulating the
creation of a product. You can use the OSB Test Console to test this method by
setting the http-method field to post and then typing in the payload as
follows:


Widget
99.99
The two Assign statements in the POST Pipeline simply
extract the name and price information and return a representation of the new
product, with a hard-coded ID of 777

The PUT Method

The PUT method is used to update an entity. It operates
very much like the POST method does, except this method will expect to see the
ID in the submitted product representation. You can test this operation in
pretty much the same way that you tested the POST, just change the http-method
to PUT and use the following as your payload:


999
Widget
99.99
Here we will add a little error checking to ensure that
the ID for the product has been specified. The If-Then action (Figure 6) uses
the expression: exists($body/Product/ID) to test
for the presence of the ID element.
image006.jpg
Figure 6 The PUT Pipeline
If the ID element is defined, we then use a single Assign
statement to create our entire response document as follows:


{$body/Product/ID/text()}
   {$body/Product/Name/text()}
   {$body/Product/Price/text()}
If the ID element is missing from the $body, then we throw
an error to alert the service consumer of the problem. In the response pipeline
we simply set $body
to the value of $responseDoc, what was created in our Assign statement.

The HEAD Method

The HEAD method is used to return metadata about a
representation to the caller. It's called the HEAD method because the metadata
is returned in the HTTP headers. This metadata can be anything you want it to
be, but a good place to start is the standard HTTP headers used by the
Inbound-Response to the Inbound-Request. Figure 6 shows the message flow for
HEAD Pipeline (Figure 7). All we do is populate the data of a single Transport
Header action in the response pipeline.
image007.jpg
Figure 7 Implementing the HEAD method
Its up to you and your specific needs to define which
headers you want to send back for the HEAD method, but I recommend sending at
least the HTTP Content-Type header with a value along the lines of 'text/xml; charset=utf-8'.
This will help the HTTP clients know how to parse the text that is returned.
Setting the Connection header to 'keep-alive' is also generally a good idea.

The OPTIONS Method

Finally, the OPTIONS method is used to convey to the
caller which HTTP methods are appropriate for the Products entity. In this
case, all we need to do is to specify a Transport Header for the outbound
response (see figure 8).
image008.jpg
Figure 8 Implementing the OPTIONS Pipeline
Configure the Transport Header as shown in figure 9.
image009.jpg
Figure 9 Transport Header Settings
The expression for the header is 'GET, HEAD, PUT, DELETE, OPTIONS'. It
may not be necessary to add the OPTIONS clause, since you have to call it in order to get
the response in the first place, but I show it for completeness. That covers
the basics of using OSB to implement RESTful service patterns. As you can see,
it's quick and simple to do. Using this technique you can wrap any
functionality in your enterprise using a REST service interface.

The DELETE Method

The DELETE method is very straight forward. It expects to
have a product ID specified on the URI and it simply deletes the product with
that ID. Due to its trivial nature, it is not implemented in the sample code.

Calling RESTful Services from OSB

In addition to providing a REST interface, OSB can also
invoke REST services. For this part of the exercise we will provide a standard
SOAP interface to OSB. OSB will then take those SOAP calls and invoke the REST
services accordingly. The WSDL for our SOAP interface defines the following
operations:

getProductDetail(productID)
createProduct(name)
updateProduct(productID)
deleteProduct(productID)
Each of these operations will be performed by the REST
services we created earlier in this tutorial.
We begin by creating a proxy service named CallREST based
on the Products.wsdl file (in the WSDL folder of the SimpleREST project). Since
the WSDL defines several SOAP operations, we start the message flow with an
Operational Branch node, and create branches for each of the operations we want
to handle. Once that is done, add a Pipeline Pair to the getProductDetail
branch, as show in Figure 10.
image010.jpg
Figure 10 The basic structure for the CallREST message flow
The getProduct Pipeline consists of a single Stage
node, as shown in Figure 11. The initial Assign statement is used to extract
the product ID from the request document and store it in the $productID
variable. The next node is a Service Callout
image011.jpg
Figure 11 The getProduct Pipeline stage
The Service Callout (Figure 12) is where most of the work
is done. To configure the Service Callout, be sure to check the Configure

Payload Document radio button since REST does not use a SOAP format. Set the Request
Variable field to requestBodyDocument

and the Response Variable field to responseBodyDocument. That's all you need to do for the Service Callout action.
image012.jpg
Figure 12 Service Callout contents
The first action is a Transport Header. Configuring this
action is simple. Set the Direction field to Outbound Request.
Also, be sure to check the Pass all Headers checkbox.
Immediately after the Transport Header action are two
Insert actions. The first insert action (Figure 13) is used to specify the HTTP
GET method. Configure it as shown in Figure 13 
image013.jpg
Figure 13 Specifying the HTTP GET method
The second and last Insert action (Figure 14) in the
request pipeline shows how to specify the relative-URI of the request. The expression:
id={$productID}
is used to create the value of the relative-URI of the outbound request
variable.
image014.jpg
Figure 14 Specifying the Relative-URI of the request
At this point our request of the REST service is properly
formatted. Once the REST service is invoked, the response will be returned to
our message flow in the responseBodyDocument
variable. In the Response Action pipeline of the service callout, we add a
single Replace action (see Figure 15) to take the response document from the
REST service and format it to meet the needs of our SOAP operation.
image015.jpg
Figure 15 Formatting the that our operation returns
The remaining branches follow a similar pattern so there
is no need to document them here. If you want to see the sample code for this
blog entry, you can download it from here:

Conclusion

As you can see from this sample, it is very easy to use
Oracle Service Bus to both call existing REST services, and to have OSB provide
a REST interface to existing SOAP services. The benefit of REST is the
simplicity of the interface design. By marrying OSB and REST, you get the
simplicity of REST, while maintaining the ability to define SLA's on each
service or operation, maintain visibility over these operations using OSBs
built in monitoring and reporting, and still retaining the necessary agility by
being able to quickly alter the OSB message flows and aggregating/orchestrating,
multiple services in the "backend" without the implicit knowledge of the
service consumer.

1 comment:

  1. Anonymous2:51 PM

    Great post....Is REST supports all security features as compared to Jax-WS?

    ReplyDelete