[Java] Testing SOAP Headers with a Simple Axis Handler

This is not a tutorial, but just some sharing of my brief learnings when testing a simple Axis Handler (somewhat similar to ASP.NET Soap Extensions). I wrote 2 programs - one simple web service client as a Java console program and one web service. The client calls a web service method called test() but adds a simple SOAP header containing a username and a password. The web service has a BasicHandler, defined in the deployment descriptor as a request handler, which checks each request and makes sure it contains the correct SOAP header with the correct username and password. The handler also sets the validated username in a ThreadLocal variable, which is then picked up by the web service method. If the request does not contain the correct SOAP header, username and password, a fault is thrown.

There are several ways for the handler class to set data which can be picked up by the web service method, and I know there is a debate going on around the use of ThreadLocals, but I thought it could be useful in this particular case.

I don't think it matters much which version of Axis you use, but I went for Axis version 1.3. Note that this is test-code, not tested for production. If you find something smelly in the code, I would love to hear about it. Another note is the way I build up the SOAP header and my somewhat sloppy usage of XML namespace. I know it can be done much better, but the intention was to test the handler code :)

Note that the web service client code is auto-generated from the WSDL and I've not bothered to show the code for it here.

First, the client code:

package test;

import org.apache.axis.message.*;

public class MyClient {

 public static void main(String[] args) {
 
  System.out.println("Hello, this is the client!");
 
  MyServiceServiceLocator wsloc = new MyServiceServiceLocator();
 
  SOAPHeaderElement oHeaderElement;
  javax.xml.soap.SOAPElement oElement;
  MyServiceSoapBindingStub ws;
 
   try {
    ws = (MyServiceSoapBindingStub)wsloc.getMyService();

    oHeaderElement = new SOAPHeaderElement("http://test",
     "securityHeader");
    oHeaderElement.setPrefix("sec");
    oHeaderElement.setMustUnderstand(false);
    oElement = oHeaderElement.addChildElement("username");
    oElement.addTextNode("johan");
    oElement = oHeaderElement.addChildElement("password");
    oElement.addTextNode("secret");
     
    ws.setHeader(oHeaderElement);
    System.out.println(ws.test());
   } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
 
 }

}


Then, the web service code (most comments and javadoc removed):

package test.service;

import test.service.handler.MyHandler;

public class MyService
{
 public String test()
 {
  String username = MyHandler.getUsername();
  MyHandler.setUsername(null); //clean up
 
  return "Hello from Service! User is = " + username;
 }
}

Finally, the Handler code looks something like this:

package test.service.handler;

import org.apache.axis.AxisFault;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.handlers.BasicHandler;

import java.util.Iterator;

import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;

import org.apache.axis.message.SOAPEnvelope;

/**
 * Simple Axis handler which looks for a SOAP header
 * containing two elements - username and password. Invoke() method
 * checks these...
 *
 * @author Johan Danforth
 */
public class MyHandler extends BasicHandler {

 private static final long serialVersionUID = 1L;
 private static ThreadLocal _username = new ThreadLocal();

    public static String getUsername() {
        return ((String) (_username.get())).toString();
    }
   
    public static void setUsername(String value) {
        _username.set(value);
    }
 
 /**
  * Method called by Axis handler, checks SOAP header with
  * username and password.
  *
  * @throws AxisFault if header is missing or invalid or wrong username or password
  */
 public void invoke(MessageContext msgContext) throws AxisFault {

  boolean processedHeader = false;
 
  try {
   Message msg = msgContext.getRequestMessage();
   SOAPEnvelope envelope = msg.getSOAPEnvelope();
   SOAPHeader header = envelope.getHeader();
   Iterator it = header.examineAllHeaderElements();
   SOAPHeaderElement hel;
  
   while (it.hasNext()) {
    hel = (SOAPHeaderElement) it.next();
    String headerName = hel.getNodeName();
    if(headerName.equals("sec:securityHeader"))
    {
     checkUsername(hel);
     processedHeader = true;
    }
   }
  } catch (SOAPException e) {
   //capture and wrap any exception.
   throw new AxisFault("Failed to retrieve the SOAP Header or it's details properly.", e);
  }

  if(!processedHeader)
   throw new AxisFault("Failed to retrieve the SOAP Header");
 
 }

 private void checkUsername(SOAPHeaderElement hel) throws AxisFault {
  String username = getUsername(hel);
  String password = getPassword(hel);
 
  if(!(username.equals("johan") && password.equals("secret")))
  {
   throw new AxisFault("Access Denied");
  }
  else
  {
   //set username as threadlocal variable
   _username.set(username);
  }
 }

 private String getPassword(SOAPHeaderElement hel) throws AxisFault {
  org.w3c.dom.Node passwordNode = hel.getLastChild();
  String nodename = passwordNode.getNodeName();
  if(!nodename.equals("sec:password"))
   throw new AxisFault("Missing password element in SOAP header.");
  String password = passwordNode.getFirstChild().getNodeValue();
  System.out.println("password = " + password);
  return password;
 }

 private String getUsername(SOAPHeaderElement hel) throws AxisFault {
  org.w3c.dom.Node usernameNode = hel.getFirstChild();
  String nodename = usernameNode.getNodeName();
  if(!nodename.equals("sec:username"))
   throw new AxisFault("Missing username element in SOAP header.");
  String username = usernameNode.getFirstChild().getNodeValue();
  System.out.println("username = " + username);
  return username;
 }
}


I need to share with you the deployment descriptors and the web.xml as well. I run the Axis Servlets in the same web app as my web service code. It's up to you how you prefer to do it really. I thought it was easier this way when I used the latest version of Eclipse. That's why the web.xml is full of Axis stuff and I'm using a separate server-config.wsdd instead of registering the service through the Axis admin tool pointing at a separate deploy.wsdd file. I'm attaching a sample deploy.wsdd file as well, just in case someone is unfamiliar with the server-config file.

My web.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="
http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 <display-name>
 HelloWorld2</display-name>
 <servlet>
  <display-name>
  Apache-Axis Servlet</display-name>
  <servlet-name>AxisServlet</servlet-name>
  <servlet-class>
  org.apache.axis.transport.http.AxisServlet</servlet-class>
 </servlet>
 <servlet>
  <display-name>
  Axis Admin Servlet</display-name>
  <servlet-name>AdminServlet</servlet-name>
  <servlet-class>
  org.apache.axis.transport.http.AdminServlet</servlet-class>
  <load-on-startup>100</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>AxisServlet</servlet-name>
  <url-pattern>/servlet/AxisServlet</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
  <servlet-name>AxisServlet</servlet-name>
  <url-pattern>*.jws</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
  <servlet-name>AxisServlet</servlet-name>
  <url-pattern>/services/*</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
  <servlet-name>AdminServlet</servlet-name>
  <url-pattern>/servlet/AdminServlet</url-pattern>
 </servlet-mapping>
 <welcome-file-list>
  <welcome-file>index.html</welcome-file>
  <welcome-file>index.htm</welcome-file>
  <welcome-file>index.jsp</welcome-file>
  <welcome-file>default.html</welcome-file>
  <welcome-file>default.htm</welcome-file>
  <welcome-file>default.jsp</welcome-file>
 </welcome-file-list>
</web-app>


My server-config.wsdd looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="
http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
 <globalConfiguration>
  <parameter name="sendMultiRefs" value="true"/>
  <parameter name="disablePrettyXML" value="true"/>
  <parameter name="adminPassword" value="admin"/>
  <parameter name="attachments.Directory" value="C:\Java\eclipse\workspace\HelloWorld2\.deployables\HelloWorld2\WEB-INF\attachments"/>
  <parameter name="dotNetSoapEncFix" value="true"/>
  <parameter name="enableNamespacePrefixOptimization" value="true"/>
  <parameter name="sendXMLDeclaration" value="true"/>
  <parameter name="sendXsiTypes" value="true"/>
  <parameter name="attachments.implementation" value="org.apache.axis.attachments.AttachmentsImpl"/>
  <requestFlow>
   <handler type="java:org.apache.axis.handlers.JWSHandler">
    <parameter name="scope" value="session"/>
   </handler>
   <handler type="java:org.apache.axis.handlers.JWSHandler">
    <parameter name="scope" value="request"/>
    <parameter name="extension" value=".jwr"/>
   </handler>
  </requestFlow>
 </globalConfiguration>
 <handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/>
 <handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/>
 <handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/>
 <service name="AdminService" provider="java:MSG">
  <parameter name="allowedMethods" value="AdminService"/>
  <parameter name="enableRemoteAdmin" value="true"/>
  <parameter name="className" value="org.apache.axis.utils.Admin"/>
  <namespace>http://xml.apache.org/axis/wsdd/</namespace>
 </service>
 <service name="Version" provider="java:RPC">
  <parameter name="allowedMethods" value="getVersion"/>
  <parameter name="className" value="org.apache.axis.Version"/>
 </service>
 <service name="MyService" provider="java:RPC" style="wrapped" use="literal">
  <operation name="test" qname="ns3:test" returnQName="ns3:testReturn" returnType="xsd:string" soapAction="" xmlns:ns3="
http://test" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
  <parameter name="allowedMethods" value="test"/>
  <requestFlow>
   <handler type="java:test.service.handler.MyHandler">
    </handler>
  </requestFlow>
  <parameter name="typeMappingVersion" value="1.2"/>
  <parameter name="wsdlPortType" value="MyService"/>
  <parameter name="className" value="test.service.MyService"/>
  <parameter name="wsdlServicePort" value="MyService"/>
  <parameter name="schemaQualified" value="
http://test"/>
  <parameter name="wsdlTargetNamespace" value="
http://test"/>
  <parameter name="wsdlServiceElement" value="MyServiceService"/>
 </service>
 <transport name="http">
  <requestFlow>
   <handler type="URLMapper"/>
   <handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/>
  </requestFlow>
  <parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler"/>
  <parameter name="qs:wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/>
  <parameter name="qs.list" value="org.apache.axis.transport.http.QSListHandler"/>
  <parameter name="qs.method" value="org.apache.axis.transport.http.QSMethodHandler"/>
  <parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler"/>
  <parameter name="qs.wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/>
 </transport>
 <transport name="local">
  <responseFlow>
   <handler type="LocalResponder"/>
  </responseFlow>
 </transport>
</deployment>


A sample deploy.wsdd could look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Use this file to deploy some handlers/chains and services      -->
<!-- Two ways to do this:                                           -->
<!--   java org.apache.axis.client.AdminClient deploy.wsdd          -->
<!--      after the axis server is running                          -->
<!-- or                                                             -->
<!--   java org.apache.axis.utils.Admin client|server deploy.wsdd   -->
<!--      from the same directory that the Axis engine runs         -->
<deployment xmlns="
http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

  <service name="MyService" provider="java:RPC" style="wrapped" use="literal">
      <parameter name="wsdlTargetNamespace" value="
http://test"/>
      <parameter name="wsdlServiceElement" value="MyServiceService"/>
      <parameter name="schemaQualified" value="
http://test"/>
      <parameter name="wsdlServicePort" value="MyService"/>
      <parameter name="className" value="test.service.MyService"/>
      <parameter name="wsdlPortType" value="MyService"/>
      <parameter name="typeMappingVersion" value="1.2"/>
      <operation xmlns:operNS="
http://test" xmlns:retNS="http://test" xmlns:rtns="http://www.w3.org/2001/XMLSchema" name="test" qname="operNS:test" returnQName="retNS:testReturn" returnType="rtns:string" soapAction="">
      </operation>
      <parameter name="allowedMethods" value="test"/>
  <requestFlow>
   <handler type="java:test.service.handler.MyHandler">
    </handler>
  </requestFlow>
  </service>
</deployment>

 

1 Comment

Comments have been disabled for this content.