Some 'more, in-depth' information on Oracle BPEL PM, ESB and other SOA, day2day things

Friday, February 24, 2006

Consuming statefull webservices from bpel

Oracle offers a nice way of creating state within webservices, this means if you create your webservice from a java class, you can turn a nce flag, and suddenly, given you hold the http session open (keep alive'd) you can call more then one method on it ..

Lets consider a small sample java class, which represents you webservice implementation.

package com.otn.samples;

/**
* Just a simple class, doing nothing more then setting a
* value in a member and checking it the next time, when a
* method is called.
*
* @author clemens utschig
*/
public class StatefullWebserviceClass
{
/**
* member to show wheter a client called logon
*/
private boolean isLoggedIn = false;

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

/**
* Returns Hello concatinated with pName
* 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 concatinated String
* @webmethod
*/
public String getGreeting(String pName) throws Exception
{
if (!isLoggedIn)
throw new Exception("Not logged in");

return "Hello " + pName;
}

}


In short, as long as you have not called logon, the call to getGreeting will return
you an exception.

The solution to this problem is the introduction of custom header handlers in bpel.
HeaderHandlers can be applied to any partnerlink, and are used to provide/retrieve specific (incl.protocol agnostic) information to/from the partnerlink.

Drawback: They are configured in the deployment descriptor (bpel.xml) and are not in the bpelstandard!

Here is the configuration, that needs to be done within the partnerlinkbinding section:


<partnerLinkBinding name="StatefullWebservice">
<property name="requestHeaderHandlers">
com.otn.samples.statehandler.OutboundHandlerForSendingState
</property>
<property name="responseHeaderHandlers">
com.otn.samples.statehandler.InboundHandlerForCookieRetrieval
</property>
</partnerLinkBinding>


Besides this you need to ensure to keep the session alive, this can be done applying this property, within the partnerlink binding section:


<property name="httpKeepAlive">true</property>


So the questions comes up: and what's now in this famouse handler classes?
Basically 3 things


a) on inbound (response from the partnerlink)
- get the session cookie, and store it in the partnerlink
- force the session to stay alive by setting keep-alive

b) on outbound calls (requests)
- get the cookie from the partnerlink, and store it in the request, with
the right key



here is the code
Inbound (retrieving the cookie):


package com.otn.samples.statehandler;
import com.collaxa.cube.engine.types.bpel.CXPartnerLink;
import java.util.Map;

/**
* Custom Handler that traces the return call from the Webservice
* for the JSESSIONID cookie and stores it in the partnerlink
* for later retrieval
*
* @author clemens utschig
*/
public class InboundHandlerForCookieRetrieval implements com.collaxa.cube.ws.HeaderHandler
{
/**
* PUBLIC CONSTANT: the command from the server to set a cookie in the ongoing
* requests
*/
public static final String HTTP_SET_COOKIE = "set-cookie";

/**
* CONSTANT: internal map key to get the http headers from the response
*/
private static final String HTTP_RESP_HEADERS = "httpResponseHeaders";

/**
* Constructor, to show that the class was instantiated
*/
public InboundHandlerForCookieRetrieval()
{
System.out.println("Started callback trace for cookie! <- receive");
}

/**
* Overwritten method to get the cookie value and store it
* persistent in the partnerlink instance
* @param pPartnerLink the partnerlink
* @param pOperationName the operation name
* @param pMsgPayload payload of the msg
* @param pMsgHeader the header of the process instance
* @param pCallProps the map coming from the partner
*/
public void invoke( CXPartnerLink pPartnerLink,
String pOperationName,
Map pMsgPayload,
Map pMsgHeader,
Map pCallProps)
{
System.out.println("[Response] from " + pOperationName + " operation");

// this should never happen, but to ensure we never fail here
if (pCallProps == null)
return;

// get the http headers - in case a different protocoll is used
// null could happen here
Map httpHeaders = (Map)pCallProps.get(HTTP_RESP_HEADERS);
if (httpHeaders == null)
return;

// we search for set-cookie header key here
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
*
* and is spec'ed in the http rfc doc.
* So we have to split it, to get the real JSESSIONID,
* which is the cookie that needs to be set 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 set: " + cookieValue);

// persist the value in the plnk
pPartnerLink.setProperty(HTTP_SET_COOKIE, cookieValue);
} else
{
System.out.println("This is an ongoing call, no set-cookie returned!");
}
}

}


and outbound (setting the values)


package com.otn.samples.statehandler;
import com.collaxa.cube.engine.types.bpel.CXPartnerLink;

import java.util.HashMap;
import java.util.Map;

/**
* Custom Handler that sets the needed cookies for statefull communication
*
* @author clemens utschig
*/
public class OutboundHandlerForSendingState
implements com.collaxa.cube.ws.HeaderHandler
{
/**
* CONSTANT: internal map key to get the http headers from the calling
* properties
*/
private static final String HTTP_REQUEST_HEADERS
= "http-request-headers";
/**
* CONSTANT: internal key to set the http headers in the call properties
* that are filled into the protocoll
*/
private static final String CALL_REQUEST_HEADERS
= "httpRequestHeaders";
/**
* CONSTANT: Http Header Key for main cookie
*/
private static final String HTTP_COOKIE = "Cookie";
/**
* CONSTANT: Http Header key for Oracle agnostic cookie
*/
private static final String HTTP_COOKIE_ALIVE = "Cookie2";
/**
* CONSTANT: Value for Oracle agnostic cookie
*/
private static final String HTTP_COOKIE_ALIVE_VAL = "$Version=\"1\"";
/**
* CONSTANT: Http Header key for connection Type
*/
private static final String HTTP_CONN_ALIVE = "Connection";
/**
* CONSTANT: Value for connection Type, in this case to keep the
* connection alive
*/
private static final String HTTP_CONN_ALIVE_VAL = "Keep-Alive";

/**
* Public constructor
*/
public OutboundHandlerForSendingState()
{
System.out.println("Started alive handler for statefull support -> out");
}

/**
* Overwritten method to set all the cookies needed
*
* @param pPartnerLink the partnerlink
* @param pOperationName the operation name that is called, eg logon
* @param pMsgPayload payload of the msg
* @param pMsgHeader the header of the process instance
* @param pCallProps the map coming from the partner
*/
public void invoke( CXPartnerLink pPartnerLink,
String pOperationName,
Map pMsgPayload,
Map pMsgHeader,
Map pCallProps)
{
System.out.println("[Request] to " + pOperationName + " operation");

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

/*
* get the httpHeader's map back from the call props
*/
Map httpHeaders = (Map)pCallProps.get(HTTP_REQUEST_HEADERS);

/*
* if nothing is specified/and assigned during process this will be empty so
* create one
*/
if (httpHeaders == null) {
httpHeaders = new HashMap();
}

/*
* if the cookie is null don't set anything, there is no need
* to set the agnostic one too, we just need to set the
* Keep-Alive signal
*/
if (cookieValue != null) {
httpHeaders.put(HTTP_COOKIE, cookieValue);
httpHeaders.put(HTTP_COOKIE_ALIVE, HTTP_COOKIE_ALIVE_VAL);
}

/*
* Set the connection alive in all cases
*/
httpHeaders.put(HTTP_CONN_ALIVE, HTTP_CONN_ALIVE_VAL);

/*
* now put the httpHeader map back into the call props.
* The normal call props are bpel specific properties, not protocol ones
*/
pCallProps.put(CALL_REQUEST_HEADERS,
httpHeaders);

System.out.println("Outgoing call props: " + pCallProps);
}
}


The last thing you need to do in your process, is to do the invokes, against the same partnerlink ..

Finally, after compiling these classes and putting them into $BPEL_HOME/system/classes everything should work nicely, and you can trace the soap messages by using the obtunnel.bat/sh we suppply ..

Note: I cannot upload the complete sample here, so if you don't want to do everything yourself jsut email me and I send you the code

1 Comments:

Anonymous Brad Moreland said...

I cannot get this to work in BPELPM 10.1.3 I have an SR open with Oracle for over 2 weeks with no traction at all. Can anyone help out with this? Thanks!

brad.moreland@activant.com
brad_moreland@yahoo.com

4:05 PM

 

Post a Comment

<< Home