Showing posts with label JAX-WS. Show all posts
Showing posts with label JAX-WS. Show all posts

Tuesday, March 9, 2010

Bookmark and Share

When working with XML-based web services, it usually a good idea to validate all requests and responses against their associated XML schema in order to ensure the integrity of the incoming/outgoing messages.

Unfortunately JAX-WS, the standard API for SOAP-based web services of the Java Platform doesn't specify a standard way for schema validation. Therefore most of the JAX-WS implementations such as Metro or CXF provide a proprietary mechanism to perform that task.

Using Metro, this is simply done by annotating the endpoint class with @SchemaValidation:

1
2
3
4
5
@WebService(endpointInterface = "...")
@SchemaValidation
public class VideoRentalPort implements VideoRentalPortType {
    //...
}

This works as expected, but has one big flaw in my eyes: to enable or disable validation for a given endpoint, the application code must be modified, followed by a re-deployment of the application.

This is not viable in many scenarios. Taking a large enterprise application with a whole lot of services for example, schema validation might be turned off by default for performance reasons. But for the purpose of bug analysis it might be required to temporarily enable validation for single endpoints. Re-deploying the application is not an option in this scenario.

That's why I had a look into Metro's implementation of the validation feature and tried to find a way to make schema validation configurable during runtime.

Custom Metro tubes

Schema validation in Metro is realized using in form of a so-called "tube". Metro tubes are conceptually similar to JAX-WS message handlers, but a magnitude more powerful. Since Metro 2.0 the tubes to be applied for a given application can be configured by providing so-called custom "tubelines".

So the basic idea is to extend the standard validation tube provided by Metro in a way which makes it runtime-configurable and register this customized tube instead of the original one.

Note: Before going into details, it should be said that as of Metro 2.0 the tube-related APIs are regarded as Metro-internal APIs, so they could be changed in future versions, causing the approach described here not to work anymore. But as we don't write very much code, this shouldn't really scare us.

Managing configuration state

First of all, some sort of data structure is needed, which will store whether validation is enabled for a given web service endpoint. For this purpose a simple map is sufficient (as this class must be accessible for multiple threads at the same time, a ConcurrentMap is used):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ValidationConfigurationHolder {

    private static ConcurrentMap<String, Boolean> configuration = 
        new ConcurrentHashMap<String, Boolean>();

    public static boolean isValidationEnabled(String portName) {

        Boolean theValue = configuration.get(portName);
        return theValue != null ? theValue : Boolean.TRUE;
    }

    public static void setValidationEnabled(String portName, boolean enabled) {
        configuration.put(portName, enabled);
    }
}

Next we need a way to manipulate this structure during application runtime. Java's standard API for management purposes as the one at hand is JMX. The following listing shows a JMX MBean, which later can be invoked to enable or disable validation for a given port:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//MBean interface
public interface EndpointConfigurationMBean {

    public String getName();

    public boolean isSchemaValidationEnabled();

    public void setSchemaValidationEnabled(boolean enabled);

}

//MBean implementation
public class EndpointConfiguration implements EndpointConfigurationMBean {

    private String name;

    public EndpointConfiguration() {}

    public EndpointConfiguration(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public boolean isSchemaValidationEnabled() {
        return ValidationConfigurationHolder.isValidationEnabled(name);
    }

    public void setSchemaValidationEnabled(boolean enabled) {
        ValidationConfigurationHolder.setValidationEnabled(name, enabled);
    }

}

The customized validation tube

The schema validation support of Metro is basically realized in three classes: AbstractSchemaValidationTube and ServerSchemaValidationTube resp. ClientSchemaValidationTube which extend the former. To make schema validation configurable on the server side, we create a sub-class of ServerSchemaValidationTube:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ConfigurableServerSchemaValidationTube extends ServerSchemaValidationTube {

    private String name;

    public ConfigurableServerSchemaValidationTube(WSEndpoint<?> endpoint, WSBinding binding,
            SEIModel seiModel, WSDLPort wsdlPort, Tube next) {

        super(endpoint, binding, seiModel, wsdlPort, next);

        name = seiModel.getServiceQName().getLocalPart() + "-" + wsdlPort.getName().getLocalPart();
        ValidationConfigurationHolder.setValidationEnabled(name, true);
        registerMBean();
    }

    private void registerMBean() {

        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 

        try {
            mbs.registerMBean(
                new EndpointConfiguration(name),
                new ObjectName("de.gmorling.moapa.videorental.jmx:type=Endpoints,name=" + name));
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected boolean isNoValidation() {
        return !ValidationConfigurationHolder.isValidationEnabled(name);
    }

    protected ConfigurableServerSchemaValidationTube(ConfigurableServerSchemaValidationTube that, TubeCloner cloner) {
        super(that,cloner);
        this.name = that.name;
    }

    public ConfigurableServerSchemaValidationTube copy(TubeCloner cloner) {
        return new ConfigurableServerSchemaValidationTube(this, cloner);
    }

}

The important thing here is, that the method isNoValidation() is overridden. This method is defined within the base class AbstractSchemaValidationTube and is invoked whenever a request/response is processed by this tube. The implementation of this method simply delegates to the ValidationConfigurationHolder shown above.

Within the constructor an instance of the EndpointConfiguration MBean for the given endpoint is registered with the platform MBean server, which later can be used to toggle validation for this endpoint.

Providing a TubeFactory

Metro tubes are instantiated by implementations of the TubeFactory interface. The following implementation allows the Metro runtime to retrieve instances of the ConfigurableServerSchemaValidationTube:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ConfigurableValidationTubeFactory implements TubeFactory {

    //...

    public Tube createTube(ServerTubelineAssemblyContext context)
            throws WebServiceException {

        ServerTubeAssemblerContext wrappedContext = context.getWrappedContext();
        WSEndpoint<?> endpoint = wrappedContext.getEndpoint();
        WSBinding binding = endpoint.getBinding();
        Tube next = context.getTubelineHead();
        WSDLPort wsdlModel = wrappedContext.getWsdlModel();
        SEIModel seiModel = wrappedContext.getSEIModel();

        if (binding instanceof SOAPBinding && binding.isFeatureEnabled(SchemaValidationFeature.class) && wsdlModel != null) {
            return new ConfigurableServerSchemaValidationTube(endpoint, binding, seiModel, wsdlModel, next);
        } else
            return next;
    }
}

The code resembles Metro's instantiation logic for the standard validation tube, the only difference being that a ConfigurableServerSchemaValidationTube is created. Note that we also check whether the SchemaValidationFeature is enabled or not. That way given endpoints can be completey excluded from validation by simply not annotating them with @SchemaValidation.

To register the tube factory with Metro as part of a custom tubeline the configuration file META-INF/metro.xml has to be provided:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<metro  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
   xmlns='http://java.sun.com/xml/ns/metro/config'
   version="1.0">
    <tubelines default="#custom-tubeline">
        <tubeline name="custom-tubeline">
            <client-side>
                ...
            </client-side>
            <endpoint-side>
                ...
                <tube-factory className="com.sun.xml.ws.assembler.jaxws.HandlerTubeFactory" />
                <tube-factory className="de.gmorling.moapa.videorental.metro.ConfigurableValidationTubeFactory" />
                <tube-factory className="com.sun.xml.ws.assembler.jaxws.TerminalTubeFactory" />
            </endpoint-side>
        </tubeline>
    </tubelines>
</metro>

As suggested in a post by Metro developer Marek Potociar it's a good idea to take Metro's standard tube configuration, metro-default.xml, as template and adapt it as needed. In our case the ConfigurableValidationTubeFactory is registered instead of the standard ValidationTubeFactory.

Configuring Schema Validation using VisualVM

Having registered the tube factory, it's time to give the whole thing a test run. A complete project containing all sources of this post together with an examplary video rental web service can be found in my Git repo.

When starting the project's main class the service will be fired up and waiting for requests. Any JMX client such as VisualVM can now be used to connect to the running JVM. The following screenshot shows VisualVM connected to the video rental application:

By setting the value of the "SchemaValidationEnabled" property of the MBean named "VideoRentalService-VideoRentalPort", schema validation now can be enabled or disabled for the corresponding endpoint.

Update (march 10, 2010): I created an request for enhancement for that feature: Allow schema validation to be enabled/disabled at runtime. So let's see, whether this will be part of some future Metro version :-)

Saturday, February 6, 2010

Bookmark and Share

JAX-WS as Java's standard API for web services comes with a close integration of JAXB as XML binding framework. More precisely JAXB is actually the only binding technology directly supported by JAX-WS. As JAXB basically does its job pretty well, that's nothing bad per se.

Nevertheless there might be situations where you would like to integrate JAX-WS with other XML binding solutions such as Apache XmlBeans.

For instance you might have used another web service stack and binding framework in the past and now want to migrate your services to JAX-WS. Depending on the number of services, changing your XML binding framework can create quite a lot of work, as you have to rewrite the mapping code which converts between the types generated by the binding framework and your domain objects.

Provider-based endpoints

The key idea for integrating JAX-WS with another binding framework is to create a Provider based endpoint for your web service. Contrary to standard SEI (service endpoint interface) implementations, which solely operate on JAXB generated classes, a provider endpoint has access to the raw XML of incoming and outgoing messages.

That way you can take care of XML marshalling yourself, e.g. by converting incoming XML messages into their corresponding XmlBeans types and the other way around.

In the following a web service for a video rental store shall be built. The service has a single operation, getMovieById, with request and response types in an accompanying XML schema (the service's WSDL and all other sources for this post can be found in my Git repository):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<schema 
    targetNamespace="http://www.gunnarmorling.de/moapa/videorental/types"
    xmlns="http://www.w3.org/2001/XMLSchema"
    xmlns:videorental="http://www.gunnarmorling.de/moapa/videorental/types">

    <element name="GetMovieByIdRequest" type="videorental:GetMovieByIdRequest" />
    <element name="GetMovieByIdResponse" type="videorental:GetMovieByIdResponse" />

    <complexType name="GetMovieByIdRequest">
        <sequence>
            <element name="Id" type="long" />
        </sequence>
    </complexType>

    <complexType name="GetMovieByIdResponse">
        <sequence>
            <element type="videorental:Movie" name="Movie" minOccurs="0" />
        </sequence>
    </complexType>

    <complexType name="Movie">
        <sequence>
            <element name="Id" type="long" />
            <element name="Title" type="string" />
            <element name="RunTime" type="int" />
        </sequence>
    </complexType>
</schema>

XmlBeans will generate two document types for the elements (GetMovieByIdRequestDocument, GetMovieByIdResponseDocument) as well as types for each of the complex types.

For the service implementation I recommend to work only with the classes representing the complex types (instead of the document types). An interface representing the service's port type and its implementation therefore might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface VideoRentalPortType {

    public GetMovieByIdResponse getMovieById(GetMovieByIdRequest request);

}

...

public class VideoRentalPort implements VideoRentalPortType {

    private MovieRepository movieRepository = new MovieRepositoryMockImpl();

    public GetMovieByIdResponse getMovieById(GetMovieByIdRequest request) {

        GetMovieByIdResponse response = GetMovieByIdResponse.Factory.newInstance();

        de.gmorling.moapa.videorental.domain.Movie movie = movieRepository.getMovieById(request.getId());

        if(movie != null) {
            response.setMovie(convert(movie));
        }       

        return response;
    }

    private Movie convert(de.gmorling.moapa.videorental.domain.Movie movie) {

        Movie theValue = Movie.Factory.newInstance();

        theValue.setId(movie.getId());
        theValue.setTitle(movie.getTitle());
        theValue.setRunTime(movie.getRunTime());

        return theValue;
    }
}

This looks pretty similar to a typical SEI-based implementation. The only difference is, that the types in use are generated by XmlBeans instead of JAXB. The implementation is straight forward. It takes the movie id from the incoming request, invokes some backend business service and creates a GetMovieByIdResponse from the result.

Next we need to create the actual Provider implementation which will be called by the JAX-WS runtime, whenever the service is invoked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@WebServiceProvider
@ServiceMode(value=Mode.MESSAGE)
public class VideoRentalProvider implements Provider<SOAPMessage> {

    private VideoRentalPortType videoRentalPort = new VideoRentalPort();

    public SOAPMessage invoke(SOAPMessage request) {

        try {

            Node root = request.getSOAPBody().getFirstChild();

            if(root.getNodeName().contains(GetMovieByIdRequest.class.getSimpleName())) {

                GetMovieByIdRequestDocument requestDocument = GetMovieByIdRequestDocument.Factory.parse(root);
                GetMovieByIdResponseDocument responseDocument = GetMovieByIdResponseDocument.Factory.newInstance();
                responseDocument.setGetMovieByIdResponse(videoRentalPort.getMovieById(requestDocument.getGetMovieByIdRequest()));

                return createSOAPMessage(responseDocument);
            }
            else {
                throw new UnsupportedOperationException();
            }
        }
        catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    private SOAPMessage createSOAPMessage(XmlObject responseDocument) {

        try {

            SOAPMessage message = MessageFactory.newInstance().createMessage();
            Node node = message.getSOAPBody().getOwnerDocument().importNode(responseDocument.getDomNode().getFirstChild(), true);
            message.getSOAPBody().appendChild(node);
            return message;
        }
        catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

}

A provider implementation must be annotated with @WebServiceProvider. By binding the type parameter to SOAPMessage (a comparison with the alternatives Source and DataSource can be found here) and specifying Mode.MESSAGE within the @ServiceMode annotation we achieve, that we can access complete SOAP messages within the implementation.

The Provider interface defines only one method, invoke(). This method is called whenever any of the service's operations is invoked. Therefore we first have to determine the currently invoked operation, which can be done by peeking at the node name of the first body element.

In case of the getMovieById operation then a GetMovieByIdRequestDocument instance representing the contents of the message body is created using a XmlBeans-generated factory class. From that document we retrieve the actual request element and pass it to the port implementation.

The result is put into a new GetMovieByIdResponseDocument instance, which itself is finally wrapped into a SOAPMessage by importing the root DOM node into the message body.

Conclusion

Creating Provider-based endpoints makes it possible to use JAX-WS together with binding frameworks other than JAXB. Whether it should be done depends as always on the specific circumstances.

For new projects working with SEI endpoints and therefore JAXB should be preferred. The JAX-WS tooling will smoothly generate JAXB binding classes as well as port type interfaces, making the creation of new web services pretty simple.

In migration scenarios things can be different though. Re-writing a whole lot of mapping code originally created for another binding framework such as XmlBeans can be quite a time-consuming and error-prone task. In that case implementing Provider-based services can be an interesting alternative.

When pursuing that approach some performance tests should be executed first. A short load test on my machine didn't show significant execution time differences between JAXB/XmlBeans for the video rental service. But things might look different for larger message types due to the involved DOM manipulations.

Thursday, April 9, 2009

Bookmark and Share

JAX-WS based web services normally run on a JEE web container or application server. That's great for production purposes, as those servers provide a very high level of scalability, security infrastructure, monitoring facilities etc.

But during development repeated deployments to a JEE container can be rather time consuming. JAX-WS' Endpoint class provides an interesting alternative here, as it allows to host JAX-WS web services within plain Java SE applications.

A web service hosted that way can very easily be launched and debugged from within your IDE for instance, which is great for development and testing purposes.

If you go with the code-first approach for web service development, things are really straight-forward:

1
2
3
4
5
6
7
8
9
10
11
public class SimpleServer {

    public static void main(String[] args) throws Exception {

        Endpoint endpoint = Endpoint.create(new ExamplePort());
        endpoint.publish("http://localhost:8080/example_app/ExampleService");

        System.out.println("Published service at http://localhost:8080/example_app/ExampleService");
    }

}

All you have to do is to create the end point by passing an instance of the port to be published (a class annotated with @WebService) and call the end point's publish() method.

Things are slightly more complicated if you prefer the contract-first web service style, meaning you start building your service with a WSDL file describing the service's interface and then generate service/port classes for it. In that case you have to specify the service's WSDL file as well as the QNames of the service and port to be published:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class SimpleServer {

    public static void main(String[] args) throws Exception {

        URL wsdlResource = 
            SimpleServer.class.getResource("/WEB-INF/wsdl/example.wsdl");

        List<Source> metadata = new ArrayList<Source>();

        Source source = new StreamSource(wsdlResource.openStream());
        source.setSystemId(wsdlResource.toExternalForm());
        metadata.add(source);

        Map<String, Object> properties = new HashMap<String, Object>();

        properties.put(
            Endpoint.WSDL_PORT, 
            new QName(
                "http://www.yournamespace.com/example",
                "ExamplePort"));
        properties.put(
            Endpoint.WSDL_SERVICE, 
            new QName(
                "http://www.yournamespace.com/example",
                "ExampleService"));

        Endpoint endpoint = Endpoint.create(new ExamplePort());
        endpoint.setMetadata(metadata);
        endpoint.setProperties(properties);

        endpoint.publish("http://localhost:8080/example_app/ExampleService");

        System.out.println("Published service at http://localhost:8080/example_app/ExampleService");
    }

}

Having launched the service it's a good idea to inspect the WSDL published by the JAX-WS runtime (e.g. at http://localhost:8080/example_app/ExampleService?WSDL). When using the JAX-WS reference implementation (RI) the file always starts with the comment "Published by JAX-WS RI ...", but in the contract-first scenario this shouldn't be followed by "Generated by JAX-WS RI ...".

JAX-WS RI is rather picky with its configuration, and if something is wrong, the runtime silently switches over to code-first mode, meaning the WSDL file is not the original one but was dynamically created from the port class. A common reason for this is not specifying the service and port QNames properly.

Here it would be great to have some kind of "force contract-first mode" option, which prevents the service from starting at all, if something is wrong with the configuration.

Monday, March 23, 2009

Bookmark and Share

JAX-WS is the standard API of the Java platform not only for the creation of web service providers but also for building web service clients. In the following I will show how to build and test a web service client using the JAX-WS reference implementation (RI) in conjunction with the Spring framework.

The example: A client for a simple shop web service

As example a simple client for an exemplary shop web service shall be built, that allows to search for products by their id. The WSDL looks as follows (this is a slightly simplified version of the WSDL from the shop service example which is part of the maven-instant-ws project):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<definitions 
    name="Products" 
    targetNamespace="http://www.gmorling.de/jaxwsonspring/products"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://www.gmorling.de/jaxwsonspring/products"
    xmlns:products="http://www.gmorling.de/jaxwsonspring/products/types"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

    <types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <xsd:import namespace="http://www.gmorling.de/jaxwsonspring/products/types"
                schemaLocation="products.xsd" />
        </xsd:schema>
    </types>

    <message name="GetProductByIdRequestMessage">
        <part name="body" element="products:GetProductByIdRequest" />
    </message>
    <message name="GetProductByIdResponseMessage">
        <part name="body" element="products:GetProductByIdResponse" />
    </message>

    <portType name="ProductsPortType">
        <operation name="GetProductById">
            <input message="tns:GetProductByIdRequestMessage" />
            <output message="tns:GetProductByIdResponseMessage" />
        </operation>
    </portType>

    <binding name="ProductsSoapBinding" type="tns:ProductsPortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
        <operation name="GetProductById">
            <soap:operation soapAction="GetProductById" />
            <input>
                <soap:body use="literal" />
            </input>
            <output>
                <soap:body use="literal" />
            </output>
        </operation>
    </binding>

    <service name="ProductsService">
        <port name="ProductsPort" binding="tns:ProductsSoapBinding">
            <soap:address location="TODO" />
        </port>
    </service>
</definitions>

The WSDL basically defines a single operation, GetProductById, that takes a GetProductByIdRequest object and returns a GetProductByIdResponse object. These types are specified in a separate XML schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="http://www.gmorling.de/jaxwsonspring/products/types"
    xmlns="http://www.w3.org/2001/XMLSchema" xmlns:products="http://www.gmorling.de/jaxwsonspring/products/types">

    <!-- GetProductById -->
    <element name="GetProductByIdRequest">
        <complexType>
            <sequence>
                <element name="Id" type="int" />
            </sequence>
        </complexType>
    </element>
    <element name="GetProductByIdResponse">
        <complexType>
            <sequence>
                <element type="products:Product" name="Product" minOccurs="0" />
            </sequence>
        </complexType>
    </element>

    <!-- General-purpose types -->
    <complexType name="Product">
        <sequence>
            <element name="Id" type="int" />
            <element name="Name" type="string" />
            <element name="Price" type="decimal" />
            <element name="Size" type="string" minOccurs="0" />
        </sequence>
    </complexType>
</schema>

The request type is just a wrapper for an int parameter representing a product id, while the response type contains a Product element, which itself has a name, price etc.

Generating proxy classes

JAX-WS provides a tool called wsimport which takes the WSDL of a web service and generates proxy classes for the WSDL's service and port definitions. These can then be used to access the web service endpoint.

With the help of the JAX-WS Maven plugin the wsimport tool can easily be used in Maven based projects. Just configure the wsimport goal of the plugin in your project's pom.xml as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxws-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>wsimport</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <wsdlDirectory>${basedir}/src/main/resources/wsdl</wsdlDirectory>
            </configuration>
        </plugin>
        ...     
    </plugins>
    ...
</build>
...

The WSDL to be processed can either be fetched directly from the actual web service endpoint or from a local directory (by specifying the wsdlDirectory property as shown in the example). I recommend to stick with the latter approach. That way your project can be built even if the service to be accessed is not available from your development environment.

During the "generate-sources" build lifecycle phase the plugin will generate

  • proxy classes for all service and port type declarations contained within the WSDL files in the specified directory
  • JAXB binding classes for all schema types used in the operations of that services

Using the proxy classes generated from the shop service WSDL it is not too hard to build a simple shop client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package de.gmorling.jaxwsonspring;

import javax.xml.ws.BindingProvider;

import de.gmorling.jaxwsonspring.shop.products.ProductsPortType;
import de.gmorling.jaxwsonspring.shop.products.ProductsService;
import de.gmorling.jaxwsonspring.shop.products.types.GetProductByIdRequest;

public class ShopClient {

    private final static String END_POINT_URL = "http://localhost:8080/shopserver/Products";

    public String getProductNameByid(int productId) {

        GetProductByIdRequest request = new GetProductByIdRequest();
        request.setId(productId);

        ProductsService productsService = new ProductsService();
        ProductsPortType productsPort = productsService.getProductsPort();
        ((BindingProvider) productsPort).getRequestContext().put(
            BindingProvider.ENDPOINT_ADDRESS_PROPERTY, END_POINT_URL);

        return productsPort.getProductById(request).getProduct().getName();
    }
}

All you have to to do is to instantiate the ProductsService, retrieve the ProductsPort from it, set the endpoint address and call any of the port's operations.

Portability issues

This works basically pretty well, but I ran into a problem, as I tried to execute my project's binary on a different machine – suddenly the WSDL of the service couldn't be found. Searching the web a little bit I found out, that JAX-WS RI parses the WSDL each time a Service instance is created.

The WSDL's location is taken from an annotation of the generated Service class (ProductsService in this case), where it is unfortunately specified as an absolute path. That causes the Service initialization to fail if the project is moved to another directory or to another system, where the WSDL doesn't exist at the expected location.

This problem can be solved by specifying the WSDL location relatively when instantiating the Service class. This complicates the process of obtaining web service port references a little bit, therefore I thought it might be a good idea to make use of dependency injection (DI). That way the rather ugly API for setting the endpoint address can be hidden from the caller as well and DI finally allows to inject mock port implementations in unit tests.

Spring's JaxWsPortProxyFactoryBean

At first I considered building a custom solution using the Spring DI container, but then I stumbled upon Spring's JaxWsPortProxyFactoryBean that already provides DI services for JAX-WS client ports.

Using that bean a JAX-WS port can be configured within a Spring application context as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="productsPort" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
        <property name="serviceInterface" value="de.gmorling.jaxwsonspring.shop.products.ProductsPortType" />
        <property name="wsdlDocumentUrl" value="wsdl/products.wsdl" />
        <property name="namespaceUri" value="http://www.gmorling.de/jaxwsonspring/shop/products" />
        <property name="serviceName" value="ProductsService" />
        <property name="endpointAddress" value="http://localhost:8080/jaxws-on-spring-server/Products" />
    </bean>
</beans>

So basically you have to configure the type of the port to be injected (the attribute name "serviceInterface" seams a bit irritating to me), the URL of the WSDL (e.g. identifying a classpath resource), namespaceUri and serviceName as specified in the WSDL file and finally the address of the endpoint to be used.

A word on thread safety

When configured as shown above Spring will use the singleton scope for the productsPort bean. That means the bean will exist only once and potentially be accessed from multiple threads at the same time.

Unfortunately the JAX-WS specification doesn't clearly say whether the generated service and port classes are thread-safe or not. Therefore one generally should assume that they aren't.

But after some searching, I found a comment by one of the JAX-WS RI developers stating, that the proxy classes of JAX-WS RI are thread-safe, as long as the request context of a port instance isn't modified. Assuming that no user of the bean modifies its request context (e.g. by re-setting the endpoint address) we are fine with the singleton scope for now.

Injecting JAX-WS client ports

Now let's rewrite the ShopClient class by having the productsPort bean injected into it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package de.gmorling.jaxwsonspring;

import javax.annotation.Resource;

import de.gmorling.jaxwsonspring.shop.products.ProductsPortType;
import de.gmorling.jaxwsonspring.shop.products.types.GetProductByIdRequest;

public class ShopClient {

    @Resource
    private ProductsPortType productsPort;

    public String getProductNameByid(int productId) {

        GetProductByIdRequest request = new GetProductByIdRequest();
        request.setId(productId);

        return productsPort.getProductById(request).getProduct().getName();
    }

}

Of course we need to configure ShopClient as Spring bean as well:

1
2
3
...
<bean id="shopClient" class="de.gmorling.jaxwsonspring.ShopClient" />
...

When creating the shopClient bean Spring will process the @Resource annotation (which stems from JSR 250: "Common Annotations for the JavaTM Platform") by populating the productsPort field with the bean of the same name.

Mocking web service requests in unit tests

Leveraging dependency the ShopClient class is greatly simplified now. As last step let's create a unit test for it.

To do so we should work with a mock implementation of the ProductsPortType interface. Working with a mock instead of accessing the real shop web service does not only increase the performance of the unit test. It also ensures, that the test result isn't dependent on the service's availability or its proper functioning.

For creating the mock we will use the freely available Mockito framework in the following (alternatively we could work with EasyMock or JMockit).

In the Spring application context for the unit test we specify that the productsPort bean shall be created by calling the method org.mockito.Mockito#mock(), which expects the class object of the object to be mocked as parameter:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="productsPort" class="org.mockito.Mockito" factory-method="mock" >
        <constructor-arg index="0" value="de.gmorling.jaxwsonspring.shop.products.ProductsPortType" />
    </bean>

    <bean id="shopClient" class="de.gmorling.jaxwsonspring.ShopClient" />
</beans>

Next we have to define, how the mock shall behave, when it is called by the code under test. For doing so we inject the productsPort bean into our test class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package de.gmorling.jaxwsonspring;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import java.math.BigDecimal;

import javax.annotation.Resource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import de.gmorling.jaxwsonspring.shop.products.ProductsPortType;
import de.gmorling.jaxwsonspring.shop.products.types.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ShopClientTest {

    @Resource
    private ProductsPortType productsPort;

    @Resource
    private ShopClient shopClient;

    @Before
    public void instructMock() {
        GetProductByIdResponse response = new GetProductByIdResponse();
        Product product = new Product();
        product.setId(1);
        product.setName("Jeans-Hose");
        product.setPrice(new BigDecimal("89.99"));
        product.setSize("L");
        response.setProduct(product);

        when(productsPort.getProductById(
            any(GetProductByIdRequest.class))).thenReturn(response);
    }

    @Test
    public void getProductName() {
        assertEquals("Jeans-Hose", shopClient.getProductNameByid(1));
    }

}

In the instructMock() method we first create a sample response object. Then we specify, that whenever the getProductById() of the mock is called, this response object shall be returned.

As the productsPort bean has singleton scope, the same instance of the bean will be injected into the shopClient bean. That way the shopClient bean will finally return the expected product name within the actual test method.

Conclusion

JAX-WS is a powerful API not only for the creation of web service providers but also for building web service clients. Unfortunately the devil is in the details – when not handled properly, JAX-WS will try to read WSDL files using absolute file pathes and setting end point addresses is not very intuitive as well.

Luckily the Spring framework comes to the rescue by enabling the creation of web service ports using dependency injection. That way obtaining port references is not only greatly simplified, it also allows mocking actual web service requests within unit tests.

The complete source code from this post can be downloaded here. As always I'd be happy about any comments and ideas for improvement.

Saturday, March 7, 2009

Bookmark and Share

My recently published article "Web Services: Auf die Plätze, fertig, los!" is now available for download as PDF file (it originally appeared in edition 3/09 of the German-speaking journal Java Magazin). I'd be happy on any comments and feedback.

Saturday, February 7, 2009

Bookmark and Share

I am so happy – this week my first article ever in a printed publication appeared. It's named "Web Services: Auf die Plätze, fertig, los!" and is published in edition 3/09 of the German-speaking journal Java Magazin.

The article (written by my colleague Ralf Ebert and me), is a tutorial, that shows how to get started with building contract-first web services using JAX-WS and Maven. We take the reader by the hand and show from scratch, how to create a new Maven project, how to write an XML schema for the service and how to run the web service using the Jetty plugin for Maven.

To make things easier, we don't make the reader write the WSDL for the service by hand, but rather provide a Maven plugin, that creates the WSDL from the XSD with the request/response types relying on some naming conventions. That greatly simplifies things (as writing WSDLs is pretty time-consuming and error prone), while keeping full control over the WSDL, being the big advantage of the contract-first approach for creating web services.

I will show in detail, how to make use of that plugin – named Maven WSDL builder plugin – in a subsequent post.

Having a basic examplary service up and running, the article continues by showing how to perform schema validation of web service requests and responses. Finally we explain how to implement cross-cutting concerns using handler chains.

So if you ever wanted to get started with contract-first web services the real simple way, don't hesistate and get your copy of Java Magazin now and let us know your opinion on the article.

Sunday, December 14, 2008

Bookmark and Share

While trying to build a Maven based project using JAX-WS RI 2.1.5, I noticed, that Maven tried to download the artifact wstx-asl-3.2.3.pom upon each build. Unfortunately without much success, but resulting in an increased build time.

As it turned out, the dependency to the Woodstox XML processor in JAX-WS RI's pom.xml is apparently wrong. There is also an item at JAX-WS RI's issue tracker commenting on that problem.

Until this is fixed, you can help yourself by excluding the flawed dependency in your project's pom.xml and adding the correct one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
<dependencies>
    ...
    <dependency>
        <groupId>com.sun.xml.ws</groupId>
        <artifactId>jaxws-rt</artifactId>
        <version>2.1.5</version>
        <exclusion>
            <exclusion>
                <groupId>woodstox</groupId>
                <artifactId>wstx-asl</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.codehaus.woodstox</groupId>
        <artifactId>wstx-asl</artifactId>
        <version>3.2.3</version>
    </dependency>
    ...
</dependencies>
...

In the likely case, that you are also using the JAX-WS Maven plugin, you have to circumvent the bug in the plugin's dependencies as well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
...
<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxws-maven-plugin</artifactId>
            <executions>
            ...
            </executions>
            <dependencies>
                <!-- Use current JAX-WS RI version -->
                <dependency>
                    <groupId>com.sun.xml.ws</groupId>
                    <artifactId>jaxws-tools</artifactId>
                    <version>2.1.5</version>
                </dependency>
                <dependency>
                    <groupId>com.sun.xml.ws</groupId>
                    <artifactId>jaxws-rt</artifactId>
                    <version>2.1.5</version>
                    <exclusions>
                        <exclusion>
                            <groupId>woodstox</groupId>
                            <artifactId>wstx-asl</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
                <dependency>
                    <groupId>org.codehaus.woodstox</groupId>
                    <artifactId>wstx-asl</artifactId>
                    <version>3.2.3</version>
                </dependency>
            </dependencies>
        </plugin>
        ...
    </plugins>
    ...
</build>
...

Having pimped your pom.xml as described above, Maven should stop wasting your time by trying to resolve the flawed dependency over and over again.