XFire

Home
Bug/Issue Reporting
Download
FAQ
Get Involved
License
News
Performance
Stack Comparison
Support
Who uses XFire\?
XFire Team

Documentation

Javadocs
Reports
User's Guide
Release Notes

Quicklinks

Aegis Binding
Client
JAXB 2.0
JSR 181 Annotations
Spring

Developers

Developer Space
CVS
Building
Architecture
Interesting Projects
Roadmap
Release Process
JAX\-WS

One thing you'll often need to do when developing services, is develop a new version while keeping the old version running. This is but one instance where you may want a service router of some sort. This guide shows how to develop a Handler which will read in a version header, and direct it to the appropriate service.

Handlers are just interceptors which are able to be invoked before an after an endpoint is invoked. In this case where going to develop a handler which sits at the in flow. Handlers can participate in various "phases" of the message pipeline. In this case we're going to write a handler which is part of the pre-dispatch phase, meaning that it'll be invoked before we dispatch to our service.

Lets see the code:

import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.fault.XFireFault;
import org.codehaus.xfire.handler.AbstractHandler;
import org.codehaus.xfire.handler.Phase;
import org.codehaus.xfire.service.Service;
import org.jdom.Element;
import org.jdom.Namespace;

public class ServiceRouterHandler
    extends AbstractHandler
{
    public final static String VERSION_NS = "http://xfire.codehaus.org/examples/router";
    public final static String VERSION_NAME = "Version";

    
    public ServiceRouterHandler() 
    {
        super();
        setPhase(Phase.PRE_DISPATCH);
    }

    public void invoke(MessageContext context)
        throws Exception
    {
        Element header = context.getInMessage().getHeader();
        if (header == null) return;
        
        Element versionEl = header.getChild(VERSION_NAME, Namespace.getNamespace(VERSION_NS));
        if (versionEl == null) return;
        
        String version = versionEl.getValue();
        if (version == null || version.length() == 0)
        {
            throw new XFireFault("An empty version element is not allowed.", XFireFault.SENDER);
        }
        
        setVersion(version, context);
    }

    /**
     * Looks up the appropriate service version using referenced by "Echo" plus the version string.
     */
    private void setVersion(String version, MessageContext context) 
        throws XFireFault
    {
        Service service = context.getXFire().getServiceRegistry().getService("Echo" + version);
        
        if (service == null)
        {
            throw new XFireFault("Invalid version: " + version, XFireFault.SENDER);
        }
        
        context.setService(service);
   }
}

Just a couple notes on something that should be pretty self explanatory:

  1. getPhase() tells XFire which phase we're participating in..
  2. We throw XFireFaults when we get unexpected values to help the user out.
  3. We are using the ServiceRegistry to resolve the service we want to invoke, then setting it in on the MessageContext so XFire knows where to send the message to.

Now lets look at a bit of set up. Here is a simple JUnit test.

import org.codehaus.xfire.DefaultXFire;
import org.codehaus.xfire.service.EchoImpl;
import org.codehaus.xfire.service.Service;
import org.codehaus.xfire.test.AbstractXFireTest;
import org.jdom.Document;

public class ServiceRouterTest
        extends AbstractXFireTest
{
    Service serviceRouter;
    Service service1;
    Service service2;
    String service1Namespace = "http://xfire.codehaus.org/Echo1";
    String service2Namespace = "http://xfire.codehaus.org/Echo2";
    
    public void setUp()
            throws Exception
    {
        super.setUp();

        // This is just an endpoint which doesn't really do anything
        serviceRouter = getServiceFactory().create(ServiceRouter.class);
        
        service1 = getServiceFactory().create(EchoImpl.class, "Echo1", service1Namespace, null);
        service2 = getServiceFactory().create(EchoImpl.class, "Echo2", service2Namespace, null);

        getServiceRegistry().register(serviceRouter);
        getServiceRegistry().register(service1);
        getServiceRegistry().register(service2);
        
        ((DefaultXFire) getXFire()).addInHandler(new ServiceRouterHandler());
    }

    public void testInvoke()
            throws Exception
    {
        Document response = invokeService(serviceRouter.getSimpleName(), 
                                          "/org/codehaus/xfire/examples/router/Echo2.xml");

        addNamespace("m", "http://xfire.codehaus.org/Echo2");
        assertValid("//m:echo", response);
        
        response = invokeService(serviceRouter.getSimpleName(),
                                 "/org/codehaus/xfire/examples/router/Echo1.xml");

        addNamespace("m", "http://xfire.codehaus.org/Echo1");
        assertValid("//m:echo", response);
    }
    
    public static interface ServiceRouter {}
}

Lets see whats going on here. First, we're creating a ServiceRouter service. We create this with just an empty interface. Technically this isn't needed in the test, but we need to establish an service for certain transports like HTTP where XFire expects a service name in the URL.

Secondly, we're creating two versions of the Echo service in different namespaces. Then we register our three services. Finally, we add our ServiceRouterHandler to the list of global in handlers to be run.

The testInvoke() method just tries out our handler for a spin. We send two different documents. Echo1.xml and Echo2.xml. Via simple XPath assertions we're able to tell we got the correct document back!