Thursday, 27 October 2011

WebSphere AS: RAD vs WSDL2Java for JAX-RPC client SOAP bindings

I've been doing a bit of work recently with JAX-RPC on WebSphere Application Server 6.1. This is hardly cutting edge software (WAS 8 is out; JAX-RPC has been superceded by JAX-WS) but platforms can have a long shelf-life in the enterprise.

This post describes how to migrate from client bindings developed using RAD to automated generation via Ant. JAX-RPC isn't restricted to consuming SOAP in WARs, but this post confines itself to that topic.

For convencience, the sample code reuses the MaintainAddress.wsdl from a previous post.

Topics

JAX-RPC and Rational Application Developer

RAD includes a wizard to populate WAR projects with JAX-RPC client bindings. This allows services to be easily consumed in the web application:

import ws.iae.demo.MaintainAddress.MaintainAddress_PortType;
import ws.iae.demo.MaintainAddress.MaintainAddress_PortTypeProxy;
import ws.iae.demo.address.Address;
import ws.iae.demo.name.Name;

public class TestServlet extends HttpServlet {

    private MaintainAddress_PortType port;

    @Override
    public void init() throws ServletException {
        MaintainAddress_PortTypeProxy proxy = new MaintainAddress_PortTypeProxy();
        proxy.setEndpoint("http://myserver/MaintainAddress");
        port = proxy;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Name name = new Name();
        name.setForename("John");
        name.setForename("Smith");
        Address address = port.lookupAddress(name);
        // TODO: error handling; dispatch to JSP response; etc.

The tooling is nice for prototyping, but not great for source management:

  • What do you do with those generated source files?
    • Check them into source control? And tag them and branch them all the time and risk people editing them (I've seen it done.) There can be thousands of generated types.
    • Have each developer generate them in their workspace? And what about build automation and variations in IDE configuration?
  • What are all those generated metadata files, and how do we clean them out if we remove a dependency on a service?

These files should be generated at build time to reduce the number of files that are managed. Automation scripts should be used to ensure a consistent process.

This post leaves out the tricky subject of how and where you should maintain and version WSDLs. Such decisions are not easy to generalize.

WebSphere AS and WSDL2Java

WSDL2Java is another way to invoke the code generation. This is exposed by a shell script and an Ant task.

Using the command line:

$WAS_HOME/bin/WSDL2Java.sh -v -r client -c web -o genSrc MaintainAddress.wsdl

The equivalent Ant script:

<?xml version="1.0" encoding="UTF-8"?>
<!-- build.xml -->
<project name="jaxRpc" default="wsdl2java" basedir=".">
    <target name="wsdl2java">
        <taskdef name="wsdl2java" classname="com.ibm.websphere.ant.tasks.WSDL2Java" />
        <wsdl2java url="${basedir}/MaintainAddress.wsdl"
            output="${basedir}/genSrc"
            role="client" 
            container="web" />
    </target>
</project>

The script assumes the WSDL and its dependencies are in the same directory as the build.xml. It can be run without modification using WAS' own Ant:

$WAS_HOME/bin/ws_ant.sh

The taskdef will require modification to add WAS libraries to the classpath if an external Ant version is used.

Things RAD does that WSDL2Java doesn't

RAD will do a number of things that WSDL2Java doesn't:

  • Puts the sources and XML files into the appropriate places in the project
  • It generates a convenience class FooServiceProxy.java for service discovery (see MaintainAddress_PortTypeProxy above)
  • Merges the metadata into the XMI files and web.xml
  • Copies the WSDL and its dependencies to WEB-INF/wsdl

These tasks must be handled via alternative means.

Locating the JAX-RPC service without the generated proxy class

Fortunately, the *Proxy type generated by RAD doesn't do very much. The code below can acquire the port for the sample service. The JNDI name (e.g. service/MaintainAddress) can be found in the service-ref-name element in the generated web.xml.

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.rpc.ServiceException;
import javax.xml.rpc.Stub;

import ws.iae.demo.MaintainAddress.MaintainAddress_PortType;
import ws.iae.demo.MaintainAddress.MaintainAddress_Service;

public final class MaintainAddressLookup {
    private MaintainAddressLookup() {
    }

    /**
     * Acquire the service and set the endpoint URI.
     * 
     * @param endpoint the URI of the service
     * @return the service "port" instance
     * @throws NamingException 
     * @throws ServiceException
     */
    public static MaintainAddress_PortType managedService(String endpoint)
            throws NamingException, ServiceException {
        InitialContext context = new InitialContext();
        MaintainAddress_Service service = (MaintainAddress_Service) context
                .lookup("java:comp/env/service/MaintainAddress");
        MaintainAddress_PortType port = service.getMaintainAddressSOAP();
        return setEndpoint(port, endpoint);
    }

    private static <T> T setEndpoint(T port, String endpoint) {
        Stub stub = (Stub) port;
        stub._setProperty("javax.xml.rpc.service.endpoint.address", endpoint);
        return port;
    }
}

The *Proxy type also includes fallback code that invokes a *Locator type. This is a vendor-specific mechanism to acquire an unmanaged service. Adding this code may hide problems with the WAR configuration, though would allow the application to use the service in this case.

The new initialization code:

    @Override
    public void init() throws ServletException {
        try {
            port = MaintainAddress.managedService("http://myserver/MaintainAddress");
        } catch(ServiceException e) {
            throw new ServletException(e);
        } catch (NamingException e) {
            throw new ServletException(e);
        }
    }

Merging the XMI files

The XMI files are used to specify WebSphere-specific configuration.

<?xml version="1.0" encoding="UTF-8"?>
<com.ibm.etools.webservice.wscbnd:ClientBinding xmi:version="2.0"
    xmlns:xmi="http://www.omg.org/XMI"
    xmlns:com.ibm.etools.webservice.wscbnd="http://www.ibm.com/websphere/appserver/schemas/5.0.2/wscbnd.xmi"
    xmi:id="ClientBinding_1319280627407">
  <serviceRefs xmi:id="ServiceRef_1319280627408" serviceRefLink="service/MaintainAddress">
    <portQnameBindings xmi:id="PortQnameBinding_1319280627419" portQnameLocalNameLink="MaintainAddressSOAP"/>
  </serviceRefs>
</com.ibm.etools.webservice.wscbnd:ClientBinding>
<!-- sample ibm-webservicesclient-bnd.xmi -->

If an application consumes more than one JAX-RPC service, the serviceRef elements should be aggregated into one file. Note that the IDs are regenerated each time even if the WSDL hasn't changed, so any change detection will have to take that into account.

Updating the web.xml

As per the requirements for JSR 109, managed services must be declared in the web.xml:

    <service-ref>
        <description>WSDL Service MaintainAddress</description>
        <service-ref-name>service/MaintainAddress</service-ref-name>
        <service-interface>ws.iae.demo.MaintainAddress.MaintainAddress_Service</service-interface>
        <wsdl-file>WEB-INF/wsdl/MaintainAddress.wsdl</wsdl-file>
        <jaxrpc-mapping-file>WEB-INF/MaintainAddress_mapping.xml</jaxrpc-mapping-file>
        <service-qname xmlns:pfx="http://demo.iae.ws/MaintainAddress/">pfx:MaintainAddress</service-qname>
        <port-component-ref>
            <service-endpoint-interface>ws.iae.demo.MaintainAddress.MaintainAddress_PortType</service-endpoint-interface>
        </port-component-ref>
    </service-ref>

Notes and reference material

If WSDLs are likely to change on a regular basis it is suggested that merging of the XML files be scripted. Implementing such a script is beyond the scope of this post.

No comments:

Post a Comment

All comments are moderated