How to use Handler Chain with JAX-RPC Web Service
Date: 30-Sept-2003
After reading this how-to document you should be able
to:
- Understand what is a JAX-RPC Web Service
- Write a JAX-RPC Web Service that uses Handler Chain mechanism to modify
SOAP messages.
Introduction
This document demonstrates how to use handlers with
the JAX-RPC Web Services.
Overview of JAX-RPC Web Services
The Java API for XML-based remote procedure calls (JAX-RPC)
provides standard way of building portable and interoperable SOAP based
Web Services. It simplifies the process of building Web services that
incorporate XML-based RPC. In JAX-RPC, a remote procedure call is represented
by an XML-based protocol such as SOAP. The SOAP specification defines
envelope structure, encoding rules, and a convention for representing
remote procedure calls and responses. These calls and responses are transmitted
as SOAP messages over HTTP. Read more on JAX-RPC Web Services here.
The Handler Mechanism
One of the most powerful features of JAX-RPC Web Services
is the Handler mechanism. A Handler can be used to modify SOAP request
or response message at the client and the server side. A Handler class
is tied to the service endpoint and can be configured to intercept the
SOAP message and perform various operations on it at any of the following
points in the client-service communication:
1. At the client, just after the SOAP request is created
and before it is sent to the Web Service.
2. At the Web Service end, before the SOAP request is processed by the
Web Service.
3. At the Web Service end before sending the SOAP reponse to the client.
4. At the client just before the JAX-RPC runtime processes the SOAP response
received from the Web Service.
The handler class can be used to perform any validations
on the SOAP request message, to encrypt or decrypt the message for security
reasons, to perform logging at various points while the SOAP request is
served.
An implementation of Web Service might require several
handler classes to be invoked one after the another before the request
or response is served. The JAX-RPC runtime is capable of invoking several
handler classes in a chain. This concept is called handler chain.
Software Requirements
-
Oracle Application Server Containers for J2EE
10g or later. You can download
the OC4J from Oracle Technology Network.
-
JDK1.4.x or above This can be downloaded
here .
Apache Ant v1.5 or later.
- How To Example Source code zip
Description
The Handler Implementation
The handler class should implement javax.xml.rpc.handler.Handler
Interface . The interface consists of following methods that should be
defined by the handler implementation class.
void init(java.util.Map config) :The
init method is used to initialize the handler instance.
void handleRequest(MessageContext context, HandlerChain
chain): This method handles RPC request messages.
void handleResponse(MessageContext context,
HandlerChain chain) : This method processes the RPC response
messages.
void destroy() : This methods ends the
life cycle of handler instance.
The MessageContext class provides access
to SOAP message passed in request or response. The class javax.xml.soap.SOAPMessage
can be used to perform various operations on the SOAP message.
Developers can extend javax.xml.rpc.handler.GenericHandler
class which provides default implementation of Handler interface and you
can override only the required methods.
The How-To Example
This how-to explains how to implement handler chain
mechanism in JAX-RPC services. The example's handler chain consists of
three handler classes called just before processing the request at the
Web Service end.
The example implements a simple Bank functionality
wherein the client can create a new account, enquire details of an existing
account, deposit amount and withdraw amount. The Web Service provides
implementation of all these operations using following important classes
and interfaces:
- BankMemDB : The class that stores all the details of accounts in
memory.
- Bank : The interface to BankMemDB class.
- BankBean : The class that provides implementation of operations that
can be performed on an account.
- BanBeanInterface : The interface to the BankBean class.
- BankFactory : The class that creates an instance of bank.
- Account : The class that represents an account.
The Web Service is configured to invoke three handlers one after the
other in a chain as follows:
- AccountTransactionHandler : This handler class logs all the operations
performed on the Bank Web Service.
- NewAccountHandler : This handler class verifies the SOAP request to
check if the initial balance is provided when creating the account.
- AccountWithdrawalHandler :This handler checks the limit of withdrawal
amount before withrawal operation is performed on the account.
The how to uses Oracle's Web Services Assembler(WSA) tool to generate the
service artifacts. The Web Service assembler tool takes as input, an XML
config file that describes the service. It uses the definitions in the WSDL
file to generate service artifacts like types, interfaces, tie and stub
classes, compiles all the classes and produces an Enterprise Archive file
that can be deployed to an application server to run the Web Service.
Listing 1 shows the WSA configuration file service-config.xml
that defines the handler classes.
<web-services > <output>./dist/oracleBank.ear</output> <context>/oracleBank</context> <web-service> <display-name>Oracle Bank HandlerChain Test</display-name> <description>A webservice that demonstrates HandlerChain
development in Oracle Application Server 10g</description> <input>build/classes/service</input> <source-output-dir>build/src/service</source-output-dir> <java-port> <interface-name>oracle.demo.handlerchain.service.BankBeanInterface
</interface-name> <class-name>oracle.demo.handlerchain.service.BankBean</class-name> <uri>/oracleBank</uri> <handler> <handler-name>Account Logger</handler-name> <handler-class>oracle.demo.handlerchain.service.AccountTransactionHandler
</handler-class> </handler> <handler> <handler-name>New Account Handler</handler-name> <handler-class>oracle.demo.handlerchain.service.NewAccountHandler
</handler-class> </handler> <handler> <handler-name>Withdrawal Handler</handler-name> <handler-class>oracle.demo.handlerchain.service.AccountWithdrawalHandler
</handler-class> </handler> </java-port> </web-service> </web-services>
|
|
Listing 1
Listing 2 shows the handleRequest
method definition of NewAccountHandler class
. As you can see, the class implements GenericHandler
class, the defalut implementation of Handler
interface. View complete code here.
public class NewAccountHandler extends GenericHandler{
...
...
public boolean handleRequest(MessageContext ctx){
// get the SOAPMessageContext SOAPMessageContext sctx = (SOAPMessageContext)ctx;
// retrieve SOAP message from the context SOAPMessage sm = sctx.getMessage();
// variable for soap body SOAPBody sb = null;
try{
// get soap body from soap message sb = sm.getSOAPBody();
// get all child nodes of soap body element NodeList nl = sb.getChildNodes();
// iterate through child elements for(int i = 0; i < nl.getLength(); i++){ // get the node Node node = nl.item(i);
// get the soap element if(node instanceof SOAPElement){ SOAPElement se = (SOAPElement)node;
// get the name of operation being performed
String operationName = se.getLocalName();
// if operation is to create new account if(operationName.equals("createAccount")){
// call method to check initial balance checkInitialBalance(sctx,se); } }
} } // catch any exceptions catch(SOAPException ex){ System.out.println(ex.getMessage()); } return true;
}
private void checkInitialBalance(SOAPMessageContext sctx,SOAPElement se) { SOAPFactory sf = null; Detail det = null; SOAPMessage sm = null; MessageFactory mf = null; try{ // create instance of SOAP factory sf= SOAPFactory.newInstance(); det = sf.createDetail(); // create instance of Message Factory mf = MessageFactory.newInstance();
// create message sm = mf.createMessage(); } catch(Exception ex){ ex.printStackTrace(); }
// get all child nodes of input SOAP element NodeList nl = se.getChildNodes();
float amt = 0f;
// iteratre through each element for(int i = 0; i < nl.getLength();i++){ // get a node Node node = nl.item(i); if(node instanceof SOAPElement){ // get SOAP element SOAPElement s = (SOAPElement)node; if(s.getLocalName().equals("float_2")){ // get the values of amount amt = new Float(s.getValue().trim()).floatValue(); } } } if(amt <= 0f){ throw new JAXRPCException(new SOAPFaultException(faultCode,"Insufficient
funds for new account. You must start an account with more
than $0", "",det)); } }
...
...
}
|
|
Listing 2
Listing 3 shows the part of Web Service implementation
class, that defines methods to create account, withdraw and deposit amount.
/* * Constructor definition */ public BankBean(){
// get an instance of Bank db m_bank = BankFactory.createBank(); }
/* * This method creates new account in the bank */ public String createAccount(String acctName,float initBalance)
throws RemoteException,AccountException {
// call method on BankMemDB to create new account return m_bank.addNewAccount(acctName,initBalance); }
/* * This method deposits amount in the existing account in bank */ public void deposit(String acctID, float amount) throws RemoteException, AccountException {
// get instance of existing account from bank Account theAccount = m_bank.getAccount(acctID);
// throw exception if account does not exist if(theAccount == null){ throw new AccountException("No account found for "+ acctID); }
// deposit the input amount theAccount.deposit(amount); }
/* * This method withdraws amount from existing account */ public void withdraw(String acctID, float amount) throws RemoteException, AccountException { // get instance of existing account from bank Account theAccount = m_bank.getAccount(acctID); // throw exception if account does not exist if(theAccount == null){ throw new AccountException("No account found for " + acctID); }
// withdraw amount from bank theAccount.withdraw(amount); }
|
|
Listing 3
The definition of class methods reduces to simply calling methods on
Account to perform operations on an account. All the verfication while
creating account or withdrawing amount is done in the Handler classes.
The complete Web Service code can be seen here.
Listing 4 shows the part of client code that calls the Web service method
to create a new account with insufficient initial balance. The request
is intercepted by NewAccountHandler class
which raises an exception.
private void demoInsufficientFunds() throws Exception{
// get bank service from Service Factory using the service url OracleBank bankService = (OracleBank)m_serviceFactory.loadService(new
URL(m_serviceURL ),OracleBank.class,null);
// get a handle to the service BankBeanInterface port = bankService.getBankBeanInterfacePort(); ((Stub)port)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY,m_serviceURL);
System.out.println("Attempting to create an account
for " + DEMO_USER2 + " with no initial funds");
try{
// try to create an account with 0 initial balance port.createAccount(DEMO_USER2,0f); } // catch any exceptions catch(Exception ex){ System.out.println("Could not createAccount for " + DEMO_USER2); System.out.println("Exception: " + ex.getMessage()); } }
|
|
Listing 4
The complete client code can be seen here.
Preparing and Running the Example
Extract the handlerchain.zip file. This
will create handlerchain directory containing
documentation and source code of the how-to.
In the following steps <OC4J> refers
to directory where OC4J is installed.
| Step a: |
Start OC4J from <OC4J>/j2ee/home
directory on a command promt as follows:
java -jar oc4j.jar
|
| Step b: |
Navigate to handlerchain, the
sample home directory on new command window . Include ANT_HOME/bin
in the PATH environment variable. Set the following properties required
by the ant build script
-
Set the ORACLE_HOME environment
variable to your <OC4J> root
directory.
Example:
Windows : set ORACLE_HOME=c:\oc4j10g
Unix : ORACLE_HOME=/home/oc4j10g
export J2EE_HOME
-
Set the J2EE_HOME environment
variable to your <OC4J>/j2ee/home
directory.
Example:
Windows : set J2EE_HOME=c:\oc4j10g\j2ee\home
UNIX : J2EE_HOME=/home/oc4j10g/j2ee/home
export J2EE_HOME
-
Set the JAVA_HOME environment
variable to your Java installation directory.
Example:
Windows : set JAVA_HOME=c:\j2sdk1.4
UNIX : JAVA_HOME=/home/j2sdk1.4
export JAVA_HOME
|
| Quick
Start |
|
Once you have the server configured and running, execute the following
command from the prompt in Step b:
>ant
The service artifacts and implementation class will be compiled
and placed into a WAR which is then placed into an EAR.
The EAR will be deployed to OC4J. Next the stubs and client will
be compiled. Finally the service will be invoked by the client and
you should see the output both in the server's message output and
on the stdout of the client.
|
| Step
by Step |
| Step 1: |
Compile service classes.
This step will compile the implementation and supporting classes
of the Web Service.
To compile the Web Service classes type: ant
compile-service-artifacts
|
| Step 2: |
Generate the service artifacts. Execute the following command:
This step will generate the service artifacts which include interfaces
and Tie classes. Examine the service-config.xml
file.
To generate the service artifacts type: ant
gen-service
The source code for the service-artifacts will be placed in the
handlerchain/build/src/service directory
using the package name oracle.demo.handlerchain.service.
Examine the handlerchain/config/type-mapping.xml
file. This file is used to instruct WSA how to map types to package
name. A namespace URI is mapped to a specific package name. Notice
that this example maps its types to the package oracle.demo.handlerchain.types.
This WSA tool finally builds a Web Service ear file, in handlerchain/dist
directory, that can be deployed to OC4J.
|
| Step 3: |
Deploy the Service
The service is now ready to be deployed. Examine the oracleBank.ear
in the handlerchain/dist directory
using WinZip or any zip file browser. It should contain a WAR file
named oracleBank-web.war. This WAR
file contains all the service artifacts, implementation classes,
as well as the web deployment descriptor (web.xml).
To deploy this ear to a running instance of oc4j type: ant
deploy-service
After this task is complete you can check your application by
typing in the following url into a web browser: http://localhost:8888/oracleBank/oracleBank
You should be able to invoke some of the methods on this service
from the web browser. Currently, invoking services using the browser
is restricted to simple types.
|
| Step 4: |
Generate the stubs (client artifacts)
This step will generate the stubs for the service. A client application
uses a stub to invoke operations on a remote service. Examine the
file handlerchain/config/client-config.xml.
This is the configuration file that the WebServices Assembler (WSA)
tool uses to generate the stubs.
To generate the stubs type: ant gen-stubs
The source for the stubs will be placed in handlerchain/build/src/client.
|
| Step 5: |
Run the demo.
You are now ready to run the client. Examine the file
BankAccountClient.java in handlerchain/src/client/oracle/demo/handlerchain.
Notice that this class uses the stubs to set an endpoint address
and to invoke methods on the remote service.
To run the client type: ant run-demo
You should see some sample output on client output console and
the server output console.
|
To undeploy the Web Service execute the command :
ant undeploy-service
Resources
Summary
This how-to document explained how write Handler class
and use Handler Chain in JAX-RPC Web Services.
|