Links: Table of Contents | Single HTML | Single PDF

Chapter 3. Compiling WSDL

Table of Contents

3.1. Compiling multiple WSDLs that share a common schema
3.2. Dealing with schemas that are not referenced
3.3. Customizing XML Schema binding
3.3.1. How to get simple and better typed binding
3.4. Generating Javadocs from WSDL documentation
3.5. Passing Java Compiler options to Wsimport

3.1. Compiling multiple WSDLs that share a common schema

Occasionally, a server will expose multiple services that share common schema types. Perhaps the "common schema types" are from an industry-standard schema, or perhaps the server was developed by a Java-first web service toolkit and the services all use the same Java classes as parameter/return values. When compiling such a WSDL, it's desirable for the shared portion to produce the same Java classes to avoid duplicates. There are two ways to do this.

The easy way is for you to compile all the WSDLs into the same package:

$ wsimport -p org.acme.foo first.wsdl
$ wsimport -p org.acme.foo second.wsdl

The Java classes that correspond to the common part will be overwritten multiple times, but since they are identical, in the end this will produce the desired result. If the common part is separated into its own namespace, you can use a JAXB customization so that the common part will go to the overwritten package while everything else will get its own package.

$ cat common.jaxb
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" version="2.1">
  <bindings scd="x-schema::tns" xmlns:tns="http://common.schema.ns/">
    <schemaBindings>
      <package name="org.acme.foo.common" />
    </schemaBindings>
  </bindings>
</bindings>
$ wsimport -p org.acme.foo.first  first.wsdl  -b common.jaxb
$ wsimport -p org.acme.foo.second second.wsdl -b common.jaxb

You can also compile the schema upfront by xjc, then use its episode file when later invoking wsimport. For this to work, the common schema needs to have a URL that you can pass into xjc. If the schema is inlined inside the WSDL, you'll have to pull it out into a separate file.

$ xjc -episode common.episode common.xsd
$ wsimport wsdl-that-uses-common-schema.wsdl -b common.episode

This will cause wsimport to refer to classes that are generated from XJC earlier.

For more discussion on this, please see this forum thread.

3.2. Dealing with schemas that are not referenced

Because of ambiguity in the XML Schema spec, some WSDLs are published that reference other schemas without specifying their locations. This happens most commonly with the reference to the schema for XML Schema, like this:

Example 3.1. Location-less reference to a schema

<!-- notice there's no schemaLocation attribute -->
<xs:import namespace="http://www.w3.org/2001/XMLSchema" />

When you run wsimport with such a schema, this is what happens:

$ wsimport  SecureConversation.wsdl
[ERROR] undefined element declaration 'xs:schema'
line 1 of http://131.107.72.15/Security_WsSecurity_Service_Indigo/WSSecureConversation.svc?xsd=xsd0

To fix this, two things need to be done:

  1. Run wsimport with the -b option and pass the URL/path of the actual schema (in the case of XML Schema, it's here. This is to provide the real resolvable schema for the missing schema.

  2. For the schema for Schema, potential name conflicts may arise. This was discussed here at length and a JAXB customization has been created to resolve such conflicts.

So your wsimport command will be:

$ wsimport -b  http://www.w3.org/2001/XMLSchema.xsd  -b  customization.xjb  SecureConversation.wsdl

You can do the same with NetBeans 5.5.1 by providing local copies of these schema and customization files. If you are facing this issue try it and let us know if you have any problems.

3.3. Customizing XML Schema binding

3.3.1. How to get simple and better typed binding

wsimport uses JAXB RI's XJC tool internally to achive XML Schema to Java binding. The default behaviour is strictly as per JAXB 2.x specification. However it does not work for everyone, for example if you want to map xs:anyURI to java.net.URI instead of java.lang.String (default mapping).

There is a JAXB global customization that can help you achieve these tasks:

  • Eliminating JAXBElements as much as possible

  • Giving you a better, more typed binding in general

  • Using plural property names where applicable

<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
  xmlns:xjc= "http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc">

  <jaxb:globalBindings>
    <xjc:simple />
  </jaxb:globalBindings>
</jaxb:bindings>

Then simply run your wsimport and pass this binding customization file

wsimport -p mypackage -keep -b simple.xjb myservice.wsdl

See Kohsuke's blog for more details.

3.4. Generating Javadocs from WSDL documentation

wsimport can map the documentation inside the WSDL that can map as corresponding Javadoc on the generated classes. The documentation inside the WSDL should be done using standard WSDL 1.1 element: <wsdl:documentation>.

It is important to note that not everythign in the WSDL maps to Java class, the table below shows wsdl:documentation to Javadoc mapping for various WSDL compoenents that correspond to the generated Java class.

Table 3.1. wsdl:documentation to Javadoc mapping

WSDL documentation (wsdl:documentation)Javadoc
wsdl:portTypeAs a Javadoc on the generated Service Endpoint Interface (SEI) class
wsdl:portType/wsdl:operationAs a Javadoc on the corresponding method of the generated SEI class
wsdl:serviceAs a Javadoc on the generated Service class
wsdl:service/wsdlportAs a Javadoc on the generated getXYZPort() methods of the Service class

Let us see a sample wsdl with documentation and the generated Java classes:

Example 3.2. WSDL with documentation

<wsdl:portType name="HelloWorld">
   <wsdl:documentation>This is a simple HelloWorld service.
   </wsdl:documentation>
   <wsdl:operation name="echo">
      <wsdl:documentation>This operation simply echoes back whatever it
         receives
      </wsdl:documentation>
      <wsdl:input message="tns:echoRequest"/>
      <wsdl:output message="tns:echoResponse"/>
   </wsdl:operation>
</wsdl:portType>

<service name="HelloService">
   <wsdl:documentation>This is a simple HelloWorld service.
   </wsdl:documentation>
   <port name="HelloWorldPort" binding="tns:HelloWorldBinding">
      <wsdl:documentation>A SOAP 1.1 port</wsdl:documentation>
      <soap:address location="http://localhost/HelloService"/>
   </port>
</service>

In the above WSDL the documentation is mentioned using standard WSDL 1.1 element: <wsdl:documentation>. Running wsimport on this will generate Javadoc on the SEI and Service class.

Example 3.3. Generated SEI - HellowWorld.java

/**
 * This is a simple HelloWorld service.
 *
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 2.1.3-11/27/2007 02:44 PM(vivekp)-
 * Generated source version: 2.1
 *
 */
@WebService(name = "HelloWorld",
        targetNamespace = "http://example.com/wsdl")
@XmlSeeAlso({
        ObjectFactory.class
})
public interface HelloWorld {


    /**
     * This operation simply echoes back whatever it receives
     *
     * @param reqInfo
     * @return
     *     returns java.lang.String
     */
    @WebMethod
    @WebResult(name = "respInfo",
            targetNamespace = "http://example.com/types")
    @RequestWrapper(localName = "echo",
            targetNamespace = "http://example.com/types",
            className = "sample.EchoType")
    @ResponseWrapper(localName = "echoResponse",
            targetNamespace = "http://example.com/types",
            className = "sample.EchoResponseType")
    public String echo(
            @WebParam(name = "reqInfo",
                    targetNamespace = "http://example.com/types")
            String reqInfo);

}

Example 3.4. Generated Service class HelloWorldService.java

/**
 * This is a simple HelloWorld service.
 *
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 2.1.3-11/27/2007 02:44 PM(vivekp)-
 * Generated source version: 2.1
 *
 */
@WebServiceClient(name = "HelloService",
        targetNamespace = "http://example.com/wsdl",
        wsdlLocation = "file:/C:/issues/wsdl/sample.wsdl")
public class HelloService
        extends Service
{

    private final static URL HELLOSERVICE_WSDL_LOCATION;
    private final static Logger logger =
            Logger.getLogger(sample.HelloService.class.getName());

    static {
        URL url = null;
        try {
            URL baseUrl;
            baseUrl = sample.HelloService.class.getResource(".");
            url = new URL(baseUrl, "file:/C:/issues/wsdl/sample.wsdl");
        } catch (MalformedURLException e) {
            logger.warning("Failed to create URL for the wsdl Location: " +
                    "'file:/C:/issues/wsdl/sample.wsdl', " +
                    "retrying as a local file");
            logger.warning(e.getMessage());
        }
        HELLOSERVICE_WSDL_LOCATION = url;
    }

    public HelloService(URL wsdlLocation, QName serviceName) {
        super(wsdlLocation, serviceName);
    }

    public HelloService() {
        super(HELLOSERVICE_WSDL_LOCATION,
                new QName("http://example.com/wsdl", "HelloService"));
    }

    /**
     * A SOAP 1.1 port
     *
     * @return
     *     returns HelloWorld
     */
    @WebEndpoint(name = "HelloWorldPort")
    public HelloWorld getHelloWorldPort() {
        return super.getPort(
                new QName("http://example.com/wsdl", "HelloWorldPort"),
                HelloWorld.class);
    }

3.5. Passing Java Compiler options to Wsimport

wsimport invokes Javac to compile the generated classes. There is no option currently to pass any options to the compiler. You can use -Xnocompile option of wsimport to not compile the generated classes. But, this would require you to compile the generated sources separately in your project.

Note

This would be useful, if you are developing the Web service/Client on JDK 6 and you want to deploy it on JDK 5. Since there is no option to pass Javac tool option "-target 1.5" directly, you can use -Xnocompile option of wsimport and further compile it yourself.