Sponsored Links


Resources

Enterprise Java
Research Library

Get Java white papers, product information, case studies and webcasts

Integrating Apache SOAP with an EJB Server
By Billy Newport, EJB Consultant.
Part 1 | Part 2 | Part 3 | Part 4

This article takes a break from the security aspect and provides more information about SOAP and using it with your applications. SOAP basically provides an RPC mechanism that allows a client to invoke methods on a service which resides on a server. This sounds like there is a singleton object on the server side but this is not true.

We implement our SOAP service using a Java class. The methods on the object can be static or normal methods. Lets assume from this point on that the method are not static. This means that the SOAP dispatcher must create an instance of the object before it can call a method on behalf of a SOAP client. If SOAP only supported singletons then it would create a single instance of this object for the lifetime of the server and this would not be terribly useful. You can how-ever, influence the life time of the service side object and make it sticky with clients by specifying a scope indicator when you deploy the service.

SOAP Scope Levels

When we deploy a SOAP service we specify the scope level of the service. The scope can be one of three values:
  • Request
    This means that a new object is created for every SOAP request and lives for a single method invocation before it is discarded.
  • Session.
    This means that one object is created per client session. This means a HTTP session for us. WebSphere needs cookies to manage sessions and our new transport handles these for us thereby making this option viable. The default HTTP transport with SOAP does not do this and hence you can't take advantage of session scope with it.
  • Application.
    Here one instance of the service is created and used to service all client requests for the lifetime of the servlet engine.
The session and application scopes seem most useful for me. Application scope lets us create singleton services. Session scope lets us create an instance of the service for each client that connects. We can store state in the service that is client specific using session scope. We would need to manage this our-selves with application scope.

We need to write a Java class that gets an instance of our session bean and stores the instance in a class variable and then delegates calls to the public methods to the corresponding ones on the session bean. We will use session level scope.

Scope Levels and session fault tolerance.

The only level that is fault tolerant will be session level scope. Almost all J2EE servers implement HTTP session fail-over. The service objects are kept in the HTTP session and so long as they can be serialized, they should survive fail-over. Application level scope is probably not going to survive fail-over. This is because SOAP keeps the object instance in the ServletContext object. This object is usually not fail-over enabled so if the server dies then the servlet context will be lost. But, if you need client state then you should be using session scoping in any case. When using application level scope then the only state you should keep is cache for reference data etc that you can reload on restart in any case without any loss of data. You should not keep any volatile data that you can lose as state in your service when using application level scope.

Transactions with SOAP

We should point something out here. IIOP has an extension called OTS. The Java version of this is JTS. Basically, JTS allows a client to create a transaction context that represents a single transaction. This transaction context is sent to the server using OTS with every method call. This allows the server to group the work done by the methods in to a single transaction even though each method called may have been executed in a different thread on the server side.

HTTP doesn't support OTS, it's a Corba thing. Our SOAP client can send multiple requests to the server in a single SOAP session. But, potentially each SOAP request may be processed by the server using a different thread. This server thread may have been processing other clients SOAP requests between your SOAP requests. This means that normally each method will run in its own transaction. This is the case even when we use session scoping. It may be the same object each time that is called but the point is that it is probably a different thread and hence the requests are not executed in a single transaction. The servlet engine will rollback any running transaction when the servlet request has been serviced in any case. Each servlet request is a new transaction. So, for now, we need to set the transaction settings for the methods we call to none, requires_new or required. Mandatory will fail unless we explicitly (and we could) make a transaction surrounding the calls to the beans in our SOAP adapter method.

Why are we using Session beans for the service implementation again?

We are doing this so that we can leverage the container security of our application server. Every method call to beans hosted in a container can be intercepted by the container. If we deploy the bean with security ACLs (WebSphere method groups) attached to the bean or methods then the container will limit access to these methods to authenticated clients that are authorized to invoke the method. So, we put a canCallSOAP ACL on the rpcrouter servlet and then put more specific access controls on the session bean implementing the server.

First, the ACL on the rpcrouter forces WebSphere to authenticate all HTTP clients that go to that URL. Our HTTP transport takes care of this. The client is now authenticated to WebSphere and if that client is authorized (present in the method group canCallSOAP) then the router processes the request. It invokes the corresponding method on our SOAP service. This is simple an adapter that forwards the call to the corresponding method on the session bean. The container will intercept the call to the session bean and only allow it if the authenticated user is authorized to call that method.

So, the session beans allow us to have method level security by leveraging the security support built in to the J2EE server. If you need better than method level security, for example, the method allows all traders to call it but depending on a parameter to the method we need further authorization in the logic. We might check if the trader is a bond trader or an fx trader, for example. We can use the J2EE security APIs to do this. We simply define method groups representing traders that are bond traders and then call the isCallerInRole method on the session context in the session bean method. This, unfortunately, is not implemented at this time (3.5.2) of WebSphere but may work in other application servers. This (if it works) is a cool feature as you can use the security infrastructure supplied by the app server vendor to administer these groups and query permissions. Normally, you'd need to implement this your-self using tables and implement a GUI to administer it so it's a time saver.

Writing the Adapter

OK, we'll now write a simple adapter. This adapter is our SOAP service. It will delegate the work to a session bean. The adapter simply creates an InitialContext, queries the home interface and the creates a session bean in its constructor. The session bean instance is stored in a class variable. Any exceptions that occur are wrapped in a SOAPException. The methods on the adapter then just invoke the correct method on the session bean instance.
import javax.naming.InitialContext;

public class HelloWorldAdapter {
  HelloWorld bean;
/**
 * Create the adapter.
 */
public HelloWorldAdapter() {
  super();
  try
  {
    InitialContext ic = new InitialContext();
    Object o = ic.lookup("com/ejbinfo/soap/ejb/HelloWorld");
    HelloWorldHome hwHome = (HelloWorldHome)
	  javax.rmi.PortableRemoteObject.narrow(o, HelloWorldHome.class);
    bean = hwHome.create();
  }
  catch(Exception e)
  {
    bean = null;
  }
}
public String getTwoStrings(String i)
  throws org.apache.soap.SOAPException
  {
    try
    {
      // Delegate to the method on the session bean.
      return  bean.getTwoStrings(i);
    }
    catch(Exception e)
    {
      throw new org.apache.soap.SOAPException("HelloWorldAdapter",
	       "cant invoke method", e);
    }
  }
}
It's worth pointing out that the above class as it is coded would also with well with application level scope. The container will give us a good session bean each time so long as the bean is stateless. If the bean is stateful then we need to mark the service as session level scope so that the bean instance is associated with a specific client. It's also worth remembering that stateful session beans are not re-entrant. If we made a mistake and gave two different clients the same instance of the bean and both clients tried to concurrently invoke a method then the container would stop the second guy to the race and throw an exception. Only one thread of execution is allowed at one time on a stateful session bean.

As regards performance, you may want to implement a home interface cache that is VM wide. This factory object should be used to get a home interface for an ejb. Its pretty much always safe to cache a home interface instance. WebSphere and WebLogic cluster these instances so that they can automatically handle failover without you needing to look it up again if the original box falls over. InitialContext lookups are usually expensive so it can pay to cache these in a HashMap that everybody shares.

Deploy the above service and run a test client

I deployed this using the admin page that comes with the SOAP package. I started a browser, and told it to deploy a new service with the name urn:HelloWorld, it had session level scoping, a single method getTwoStrings and no mappings. A new HelloWorldAdapter is created for each client that connects but each client keeps its instance on subsequent requests thanks to the cookie management provided by the HTTPClient package. Here is the test client that I used. It is basically the GetAddress client with some modifications:
package com.ejbinfo.soap.ejb;

import java.io.*;
import java.util.*;
import java.net.*;
import org.w3c.dom.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.*;
import org.apache.soap.encoding.*;
import org.apache.soap.encoding.soapenc.*;
import org.apache.soap.rpc.*;

import java.security.*;

public class HelloWorldSOAPClient
{
  public static void main(String[] args) throws Exception
  {
    if (args.length != 2
      && (args.length != 3 || !args[0].startsWith("-")))
    {
      System.err.println("Usage:");
      System.err.println("  java " + HelloWorldSOAPClient.class.getName() +
                            " [-encodingStyleURI] SOAP-router-URL nameToLookup");
      System.exit (1);
    }

    // Process the arguments.
    int offset = 3 - args.length;
    String encodingStyleURI = args.length == 3
          ? args[0].substring(1)
          : Constants.NS_URI_SOAP_ENC;
    URL url = new URL(args[1 - offset]);
    String param = args[2 - offset];
    SOAPMappingRegistry smr = new SOAPMappingRegistry();
    BeanSerializer beanSer = new BeanSerializer();

    // Build the call.
    Call call = new Call();
    call.setSOAPMappingRegistry(smr);
    call.setTargetObjectURI("urn:HelloWorld");
    call.setMethodName("getTwoStrings");
    call.setEncodingStyleURI(encodingStyleURI);
    // HTTPClient.AuthorizationInfo.
	//   addBasicAuthorization("p300",443,"EJBINFO","bnewport","secret");
    // Don't prompt whether to accept cookies.
    HTTPClient.CookieModule.setCookiePolicyHandler(null);

    // Use our HTTP transport.
    call.setSOAPTransport(new com.ejbinfo.soap.transport.http.SOAPHTTPConnection());

    Vector params = new Vector();

    params.addElement(new Parameter("input", String.class,
      param, null));
    call.setParams(params);

    // Invoke the call.
    Response resp;

    try
    {
      resp = call.invoke(url, "");
    }
    catch (SOAPException e)
    {
      System.err.println("Caught SOAPException (" +
         e.getFaultCode() + "): " +
         e.getMessage());
      return;
    }

    // Check the response.
    if (!resp.generatedFault())
    {
      Parameter ret = resp.getReturnValue();
      Object value = ret.getValue();

      System.out.println(value != null ? "\n" + value : "I don't know.");
    }
    else
    {
      Fault fault = resp.getFault();

      System.err.println("Generated fault: ");
      System.out.println ("  Fault Code   = " + fault.getFaultCode());
      System.out.println ("  Fault String = " + fault.getFaultString());
    }
  }
}

Stateless and Stateful session beans.

You could keep state in the adapter. We do have one per client after all. But, given that the business logic is contained in the session bean then if we do need to keep state between calls, a stateful session bean makes more sense, no? We just keep the EJBHandle in the service object. This is serializable. Stateful session beans are not guaranteed to be recoverable when a failure occurs. WebSphere and WebLogic 5.x don't support stateful session bean fail-over. WebLogic 6.0 does support this apparently. A portable solution may be to store your state in an entity bean and keep the key as state in your service object. The business logic should then be implemented using a stateless session bean and the adapter passes the key to the methods of the session bean. This allows the session bean to retrieve the entity bean and recover the state. But, you will now have the additional headache of purging entity beans from the database when a client dies unexpectedly how-ever.

It appears finally that if you need fail-over then use WebLogic 6.0 and replicated stateful session beans or keep your state in a serializable object that you keep in the SOAP adapter and pass to the session bean on each method call. Why use the session bean when all the state is in the adapter? Security. If we don't use the session bean then we lose security. The SOAP adapter has no access to the HTTPSession and therefore no access to the J2EE security APIs on that. This means it's difficult to implement method level security. You could patch SOAP to force it to make the HTTPSession object available to the adapter but you're probably better off just using a stateless session bean that accepts its state as a parameter and returns a new copy to be stored in the adapter afterwards.

Conclusion

We now have an adapter that we use to forward SOAP requests to a stateless session bean. It can leverage the J2EE security provided by the server and it is fault-tolerant as we're taking advantage of the HTTPSession HA features of the server also. The new HTTP transport we're using makes this possible because it supports cookies. This means that the server's session tracking works and can associate a server side HTTPSession with our HTTPClient. The next article should finally get around to showing how to do certificate authentication.

News | Blogs | Discussions | Tech talks | Patterns | Reviews | White Papers | Downloads | Articles | Media kit | About
Java Solutions
All Content Copyright ©2007 TheServerSide Privacy Policy
Site Map