Chapter 3. Compiling WSDL
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
Jakarta XML Binding 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:
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.
For the schema for Schema, potential name conflicts may
arise. This was discussed here
at length and a Jakarta XML Binding 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 internally uses XJC tool from Eclipse Implementation
of JAXB to achive XML
Schema to Java binding. The default behaviour is strictly as per
Jakarta XML Binding 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 Jakarta XML Binding 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:portType | As a Javadoc on the generated Service Endpoint
Interface (SEI) class |
wsdl:portType/wsdl:operation | As a Javadoc on the corresponding method of the
generated SEI class |
wsdl:service | As a Javadoc on the generated Service
class |
wsdl:service/wsdlport | As 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.