Recently a client needed to create some SOAP based web services. When I came into the project they had already selected Spring WS as their technology choice but we needed a way to be able to control our endpoints in different environments. In this blog I'll outline what we did to make this a simple and automated process.

What is Spring WS?

Spring Web Services or Spring WS is a sub-project of the ubiquitous Spring Framework that makes it easy to create and expose SOAP based web services. By creating an XSD and a class that implements the endpoint you have a complete web service with very little code.

The Problem

With Spring WS you get a really nice feature set. Among other things you get automatic generation of the WSDL file by just adding some configuration. This feature is not recommended to use in production however since it could potentially change your WSDL every time you deploy new code which would drive the people consuming your services crazy. So the recommended way is to use a static WSDL. The problem with that is that the WSDL contains a URL endpoint. This endpoint is most likely different in every environment you deploy in; local, dev, test, stage, prod, etc. You would end up creating a separate build for each of the environments- hardly a streamlined approach.
To add to the problem my client's project is planned to receive enough volume that a single application server is not going to be enough. The client already has the infrastructure to set up load balancers and multiple application servers. So where is your endpoint going to point? Each individual application server? Hardly a clear way to go here either.

The Approach

We decided that we would like to be able to build one EAR file that could be deployed in several environments and still be correctly configured. We also wanted to be able to specify the load balancer URL in our WSDL for stage and production but at the same time keep our local development environments flexible and easy to do our work on.

The Solution

We created a new class called ConfigurableWsdlDefinitionHandlerAdapter that extends the package default WsdlDefinitionHandlerAdapter. Our new class overrides a single method of its parent called transformLocation. This method is called every time a WSDL is generated by Spring WS to rewrite the URL of the endpoint. The default adapter creates the endpoint URL based on the request but in our case we want to be able to explicitly set it.

ConfigurableWsdlDefinitionHandlerAdapter
public class ConfigurableWsdlDefinitionHandlerAdapter extends WsdlDefinitionHandlerAdapter {
 
 private String endpointUrl;
 
 @Override
 protected String transformLocation(String location, HttpServletRequest request) {
 
 StringBuilder url = new StringBuilder(endpointUrl);
 if (location.startsWith("/") && endpointUrl.endsWith("/")) {
 url.append(location.substring(1));
 } else {
 url.append(location);
 }
 return url.toString();
 }
 
 public void setEndpointUrl(String endpointUrl) {
 this.endpointUrl = endpointUrl;
 }
}

You might be thinking "how is this helping, you would still have to configure the values for each environment?". You would be right but this is where Spring's profiles come to good use.

spring-ws.xml
<> xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 
 xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
 <> profile="local, dev">
 <> resource="spring-wsdl-dynamic.xml"/>
 >
 
 <> profile="test">
 <> location="classpath:config-test.properties"/>
 <> resource="spring-wsdl-static.xml"/>
 >
 <> profile="stage">
 <> location="classpath:config-stage.properties"/>
 <> resource="spring-wsdl-static.xml"/>
 >
 <> profile="prod">
 <> location="classpath:config-prod.properties"/>
 <> resource="spring-wsdl-static.xml"/>
 >
 
>

All that is needed now is that you start each environment with a system property set, called spring.active.profiles. Adding this property you can control which bean definition is read during startup.
The dynamic generation is straight out of Spring WS documentation but our static config is where we are making the changes.

spring-wsdl-static.xml
<> xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:sws="http://www.springframework.org/schema/web-services"
 
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
 http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd">
 
 <> id="wsdlDefinitionHandlerAdapter" class="com.captechconsulting.webservice.adapter.ConfigurableWsdlDefinitionHandlerAdapter">
 <> name="endpointUrl" value="${services.endpoint.url}"/>
 >
 
 <> id="package" location="classpath:/wsdl/package.wsdl"/>
 
>

Conclusion

Spring WS does a lot of the grunt work when creating SOAP based web services but it needs some additional tweaking when it comes to bigger deployments. This is one example of how you can handle it without very much extra code. In my client's environments we now have dynamically generated WSDL's in our local development environments as well as in our dev server. For each of the upper level of environments we can configure what values should be used for the endpoint configuration. This is especially important for the stage and prod environments where we can create endpoints that use the load balancer.

Full source code can be found on GitHub.