Arsenalist

The Toronto Raptors Blog with an Arsenal touch

Web Service Versioning with Endpoints using XFire

Posted by Arsenalist on March 1, 2007

I wanted to document an example of using web service versioning using endpoints. I posted on the XFire mailing list asking what other people do but got little in return. I read the XFire Versioning Wiki entry and that mentioned using namespaces and/or custom headers. Although the namespaces/custom header approach works fine, it has the significant drawback of your web service clients to actually modify the outgoing SOAP message, which is always nice to avoid.

Using endpoints also allows us to have nicer WSDL URLs based on version number, e.g.:

http://arsenalist.com/services/1_0_0/SportsService
http://arsenalist.com/services/1_0_1/SportsService

In this example, I want to host multiple versions of the service while maintaining a logical URL like shown above. There’s a simple little trick involved in doing this using XFire and it needs us to override the getService(HttpServletRequest) method of the XFireServletController class. In order to plug a different controller, we must override the createController() method in XFire’s main servlet. I’m using Spring so I would need to override XFireSpringServlet but if your’e not using Spring you would override XFireServlet instead.

public class CustomXFireServlet extends
                         XFireSpringServlet {
  public XFireServletController createController()
                      throws ServletException {
    return new CustomXFireServletController(getXFire(),
                                 getServletContext());
  }
}

So I’ve specified a new controller, CustomXFireServletController which is the primary entry point for all service requests. I’ll override the getService(HttpServletRequest) method which will parse the service name out of the URL as per your convention.

public class CustomXFireServletController extends
        XFireServletController {

  public CustomXFireServletController(XFire xFire) {
    super(xFire);
  }  

  public CustomXFireServletController(XFire xFire,
                     ServletContext servletContext) {
    super(xFire, servletContext);
  }

  /**
   * Override getService to look for a URL of the form:
   * http://.../services/1_0_0/SportsService
   * which would map to a service registered as
   * SportsService1_0_0
   *
   * @param request HttpServletRequest
   * @return Service name corresponding to URL
   */
  protected String getService(HttpServletRequest
                                          request) {
    String pathInfo = request.getPathInfo();
    if (pathInfo != null && pathInfo.startsWith("/") &&
        StringUtils.countMatches(pathInfo, "/") == 2) {
      int lastSlash = pathInfo.lastIndexOf("/");
      String version = pathInfo.substring(1, lastSlash);
      String name = pathInfo.substring(lastSlash+1);
      return name + version;
    } else {
      return super.getService(request);
    }
  }
}

So I’m counting on a service with the name of SportsService1_0_0 to be registered with XFire. This service would be accessible at http://arsenalist.com/services/1_0_0/SportsService

The last step is to modify your web.xml so that it uses the CustomXFireServlet:

<servlet>
 <servlet-name>XFireServlet</servlet-name>
 <servlet-class>
  com.arsenalist.xfire.CustomXFireServlet
 </servlet-class>
</servlet>
<servlet-mapping>
 <servlet-name>XFireServlet</servlet-name>
 <url-pattern>/services/*</url-pattern>
</servlet-mapping>

This is one of the cleanest solutions for having versioning capabilities for your web services. Your web service clients don’t need to specify any specific namespaces or create custom headers just to invoke a specific version of a service. There are no external dependencies (not even on Spring) The endpoint method works on top of the SOAP envelope and you have the power to customize it to any convention that you might want to use.  For example, you could actually specify the version number using the querystring if you like:

http://arsenalist.com/services/SportsService?v=1.1

All these are delectable options that you can choose from.

Update: Andrew Ochsner’s excellent comment on this post also shows how you can do versioning using Spring’s DispatcherServlet and SimpleUrlHandlerMapping.

Advertisements

4 Responses to “Web Service Versioning with Endpoints using XFire”

  1. Andrew Ochsner said

    We do something similar but different. We use org.springframework.web.servlet.DispatcherServlet as the XFireServlet. Then in our xfire-servlet.xml file (our Spring/XFire configuration file), we set up the following:

    web.xml

    <?xml version=”1.0″ encoding=”UTF-8″?>
    <!DOCTYPE web-app PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN” “http://java.sun.com/dtd/web-app_2_3.dtd”>

    <web-app>
    <!– For xfire context initialisation in a spring environment –>
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    WEB-INF/xfire-servlet.xml
    classpath:org/codehaus/xfire/spring/xfire.xml
    </param-value>
    </context-param>
    <!– Register the spring context listener –>
    <listener>
    <listener-class>
    org.springframework.web.context.ContextLoaderListener
    </listener-class>
    </listener>

    <servlet>
    <servlet-name>xfire</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
    <servlet-name>xfire</servlet-name>
    <url-pattern>/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
    <servlet-name>xfire</servlet-name>
    <url-pattern>/servlet/XFireServlet/*</url-pattern>
    </servlet-mapping>

    <mime-mapping>
    <extension>wsdl</extension>
    <mime-type>text/xml</mime-type>
    </mime-mapping>
    <mime-mapping>
    <extension>xsd</extension>
    <mime-type>text/xml</mime-type>
    </mime-mapping>
    </web-app>

    xfire-servlet.xml

    <?xml version=”1.0″ encoding=”UTF-8″?>

    <beans xmlns=”http://www.springframework.org/schema/beans”
    xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
    xmlns:aop=”http://www.springframework.org/schema/aop”
    xmlns:tx=”http://www.springframework.org/schema/tx”
    xsi:schemaLocation=”
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd“>

    <bean id=”ourFirstServiceBean” class=”com.example.OurFirstServiceImpl” />
    <bean id=”ourSecondServiceBean” class=”com.example.OurSecondServiceImpl” />

    <bean name=”jaxbServiceFactory”
    class=”org.codehaus.xfire.jaxb2.JaxbServiceFactory”>
    <constructor-arg ref=”xfire.transportManager” />
    </bean>

    <bean class=”org.springframework.web.servlet.handler.SimpleUrlHandlerMapping”>
    <property name=”urlMap”>
    <map>
    <entry key=”/2006/07/*”>
    <ref bean=”ourFirstService”/>
    </entry>
    <entry key=”/2007/01/*”>
    <ref bean=”ourSecondService”/>
    </entry>
    </map>
    </property>
    </bean>

    <bean id=”ourFirstService” class=”org.codehaus.xfire.spring.remoting.XFireExporter”>
    <property name=”serviceBean” ref=”ourFirstServiceBean” />
    <property name=”serviceFactory” ref=”jaxbServiceFactory” />
    </bean>
    <bean id=”ourSecondService” class=”org.codehaus.xfire.spring.remoting.XFireExporter”>
    <property name=”serviceBean” ref=”ourSecondServiceBean” />
    <property name=”serviceFactory” ref=”jaxbServiceFactory” />
    </bean>
    </beans>

    This way it’s configuration not some parsing logic in some code that can’t be changed. Works for us, but I suppose there’s some more complicated examples that might need parsing.

    Andy O

  2. Java Guy said

    Andy,

    Are you saying that if you have 200 services you will have 200 entries in the ? Is that map somehow generated or maintained manually. I like the idea otherwise.

  3. arsenalist said

    You can extend SimpleUrlHandlerMapping and override setUrlMap() to specify your own mapping. Then you can populate your map with whatever you want using Java code.

    The only thing I don’t like about the Spring way of doing this is that you have to modify your mappings every time a new service or service version is added.

    Also, you obviously need a dependency on Spring which is not needed in the method shown in the post.

  4. Nagesh said

    Do you guys have any ideas on how this can be applied to apache axis 1.x on tomcat?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: