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.
4 Comments
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
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.
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.
Do you guys have any ideas on how this can be applied to apache axis 1.x on tomcat?