Leveraging statefull webservices

Invoke Calling stateful web services from Oracle BPEL PM

Clemens Utschig - Utschig, SOA Product Management, March 2006
clemens.utschig@oracle.com

Contents

Preface

The following article is intended to showcase a BPEL process that leverages a stateful java web service. As part of this tutorial the reader will dive into the source code, both of the web service implementation and of the BPEL process that invokes it. This document uses snippets of code and screenshots to illustrate the concepts. The full source code for the web service project can be found here.

Prerequisites

This article requires an understanding of the following:

  • BPEL, its process types and language semantics
  • HTTP protocol, such as cookies and sessions
  • Java

Introduction to stateful web services & custom header handlers

Oracle Containers for Java (OC4J) offers developers of web services a feature to preserve state between calls. It ensures that all calls within the same session are routed to the same instance of the implementing class.

Consider the following code:

public class StatefullWebserviceClass
{
 /**
  * member to show whether a client called logon method
  */
  private boolean isLoggedIn = false;

 /**
  * logon method, sets the member isLoggedIn to true
  * @webmethod 
  */
  public void logon()
  {
    isLoggedIn = true;
  }

 /**
  * Returns Hello concatenated with <code>pName</code>
  * In case logon was not called, it will throw an exception
  * saying the user is not logged in!
  * 
  * @param pName the name passed
  * @return the concatenated String
  * @webmethod 
  */
  public String getGreeting(String pName) throws Exception 
  {
    if (!isLoggedIn) 
      throw new Exception("Not logged in");

    return "Hello " + pName;
  }
}

The point of this sample is that the client must call the logon() method before calling getGreeting(). Normally in the web services world, these two operations would be independent and the second call to getGreeting() would be against a new instance of the implementing class (and therefore the member variable would not retain it's value). Thus, when the getGreeting() operation is invoked, one of two things can happen:

  1. If the implementing class (i.e. the web service) is not stateful -- which is normally the case in the web services world -- invoking getGreeting() will return the exception ("Not logged in!"). That's because the member variable isLoggedIn did no retain its state over the two calls to logon() and getGreeting().

  2. If the implementing class is stateful, then the value of isLoggedIn will be retained after the call to logon(), and getGreeting() will execute fully and return the "Hello" message.

For stateful calls to work, the facade needs to be correctly configured, something you would usually have to do manually. However you can do this easily in JDeveloper using the java web services wizard, as shown in the screenshot below (highlighted by the red oval).

How do stateful web services work? It's based on HTTP sessions and cookies. Two important features enable this functionality:

  1. KeepAlive

    You can specify the KeepAlive property value in the HTTP header. By setting this property to true, the HTTP session is preserved over multiple calls to the server. This is nothing specific to web services, it's a general HTTP protocol option.

  2. When a client contacts the server for the first time through an HTTP request, the server will respond, set a cookie on the client, and issue a set-cookie command. This instructs the client to send the cookie with all subsequent requests within that session. Now the server is able to correlate requests from the client and associate them with a unique session.

    Here is a sample HTTP response header coming back from the server:

    Set-Cookie: JSESSIONID=90198e1525e4b03797f833ff4320af39f2bdabfb9d8d; path=/bpelsamples

After creating and deploying the webservice to OC4J, it's time to look at the client side, in this case, the BPEL process. In this tutorial our BPEL process will end up invoking the logon() method and then the getGreeting() method. Normally each web service invocation is an atomic call, and therefore stateless. Subsequent web service invocations have their own session, are separate and independent, and therefore don't share resources, data, connections, etc.

Oracle BPEL Process Manager 10.1.2.0.2 introduced a new feature called "Custom Header Handlers" which allows you to enhance the normal way of interacting with web services. For example, you can set protocol agnostic headers, or retrieve them from a partnerlink.

There are two typical use-cases for this feature:

  1. Setting specific data, such as HTTP header attributes, for an outgoing request from a partnerlink to the service.
     
  2. Retrieving incoming data from a partnerlink (such as HTTP cookies), based on a response from a service.

A custom header handler is based on the interface com.collaxa.cube.ws.HeaderHandler and offers one method that must be implemented:

public void invoke
(
  CXPartnerLink pPartnerLink
, String pOperationName
, Map pMsgPayload
, Map pMsgHeader
, Map pAgnosticCallProps
);

where pPartnerlink describes the partnerlink, and can be used to store values for ongoing calls (in our case the cookie), pOperationName is the name of the operation invoked on the partnerlink, and pAgnosticCallProps holds a map of elements for a specific call - such as the HTTP headers.

Let's visit the code again to discover how to retrieve HTTP headers sent by the server:

// Get the HTTP headers
Map httpHeaders = (Map)pAgnosticCallProps.get(HTTP_RESPONSE_HEADERS);


// Search for set-cookie header key
String cookieValue = (String)httpHeaders.get(HTTP_SET_COOKIE);

/*
 * This is what comes back on the first connection attempt (only!)
 * Set-Cookie: JSESSIONID=90198e1525e4b03797f833ff4320af39f2bdabfb9d8d; 
 * path=/bpelsamples
 *  
 * The format of the cookie specified as part of the HTTP standard.
 * We have to split it to get the real JSESSIONID,  
 * which is the cookie that needs to be forwarded in ongoing requests
 */
if
(
   cookieValue != null &&
   cookieValue.length() > 0 &&
   cookieValue.indexOf(";") > 0
) 
{
  // Split it
  cookieValue = cookieValue.substring(0, cookieValue.indexOf("; path"));
  System.out.println("Cookie to forward: " + cookieValue);

  // Persist the value in the partnerlink
  pPartnerLink.setProperty(HTTP_SET_COOKIE, cookieValue);
}
else
{
  // We must be on a subsequent connection, not the orignal connection attempt.
  System.out.println("This is an ongoing call, no set-cookie returned!");
}
The code above illustrates how to retrieve specified values, and how to store them in the partnerlink for further usage. Note: Values stored in a partner link during runtime get saved in the database as part of the dehydration process.

The next step is understanding the outbound part (in a nutshell, retrieving the cookie from the partnerlink and setting it into back into the call to be forwarded to server).


/*
 * Get the cookie back from the partnerlink
 */
String cookieValue = (String)pPartnerLink.getProperty(HTTP_SET_COOKIE);

/*
 * Get the HTTP header's map back from the call properties
 */
Map httpHeaders = (Map)pAgnosticCallProps.get(HTTP_REQUEST_HEADERS); 

/*
 * We need to populate the partnerlink's properties with the cookie, which means
 * updating the map.  But if the partnerlink has no other properties set, then it
 * won't have a map yet, so we need to create one.
 */
if (httpHeaders == null)
{ 
  httpHeaders = new HashMap();
}

/*
 * If the cookie is null don't set anything
 */
if (cookieValue != null)
{
  httpHeaders.put(HTTP_COOKIE, cookieValue);
}

/*
 * Always set the connection keepalive.
 */
httpHeaders.put(HTTP_CONN_ALIVE, HTTP_CONN_ALIVE_VAL);

/*
 * Now put the httpHeader map back into the call properties.
 * The normal call properties are BPEL-specific properties, not
 * HTTP protocol ones.
 */
pAgnosticCallProps.put(CALL_REQUEST_HEADERS, httpHeaders);

Now that the code is in place, let's modify our process to leverage these handlers.

Building your BPEL process and connect to the webservice

The important part of the BPEL process is to configure each invoke activity against the same partnerlink instance, as the cookie is stored in that instance only. Consider the following screenshot of the activities and partnerlinks involved:

After creating the process, the only thing left to do is to configure the "special" partnerlink properties in the deployment descriptor of the BPEL process (bpel.xml) as shown below, such as keepAlive, and to reference our handler classes: requestHeaderHandlers and responseHeaderHandlers.

<partnerLinkBinding name="StatefullWebservice">
<!-- this is the default configured location of the wsdl -->
<property name="wsdlLocation">StatefullWebservice.wsdl</property>
<!-- force keep alive, this is needed on top of the setting in the handler class--> <property name="httpKeepAlive">true</property>
<!-- this is the handler for the OUTGOING request TO the webservice -->
<property name="requestHeaderHandlers">
com.otn.samples.statehandler.OutboundHandlerForSendingState
</property>
<!-- this is the handler for the INCOMING response FROM the webservice -->
<property name="responseHeaderHandlers">
com.otn.samples.statehandler.InboundHandlerForCookieRetrieval
</property>
</partnerLinkBinding>

You can now deploy and run your process:

Had we not modifed the partnerlink properties in the bpel.xml to reference our handler classes, then the invocation would've run in a normal -- and therefore, stateless -- manner. You can test this by commenting out or removing those properties:

<partnerLinkBinding name="StatefullWebservice">
<!-- this is the default configured location of the wsdl -->
<property name="wsdlLocation">StatefullWebservice.wsdl</property>
</partnerLinkBinding>

Now re-deploy and run your process and it will run statelessly and therefore get the expected exception, "Not logged in":

           

Conclusion and next steps

In this tutorial we have demonstrated a practical, easy to implement approach for leveraging stateful webservices from within a BPEL process. As shown, even the stateless boundaries of BPEL invocations (as defined by the WSBPEL standard) can be overcome by using the custom header handlers feature, introduced in Oracle BPEL PM 10.1.2.0.2.

 

E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy