wsimport


Thrown into a large, legacy EE code base, I needed to create new SOAP web services, and update some existing ones. To make life interesting, there lacked a repeatable process (e.g. via Ant or Maven) for processing WSDL’s and generating the associated JAXWS and JAXB source code artifacts in a consistent manner.

First things first, a repeatable and scriptable procedure. wsimport to the rescue. wsimport has been around so long now, the com.sun.tools.ws.ant.WsImport Ant task is guarenteed to be kicking around in your environment. It’s also a best practice to use the vendor specific wrapper to wsimport, in my case WebSphere.

WebSphere Application Server provides Java API for XML-Based Web Services (JAX-WS) and Java Architecture for XML Binding (JAXB) tooling. The wsimport, wsgen, schemagen and xjc command-line tools are located in the app_server_root\bin\ directory in full profile. Similar tooling is provided by the Java SE Development Kit (JDK). On some occasions, the artifacts generated by both the tooling provided by WebSphere Application Server and the JDK support the same levels of the specifications. In general, the artifacts generated by the JDK tools are portable across other compliant runtime environments. However, it is a best practice to use the tools provided with this product to achieve seamless integration within the WebSphere Application Server environment and to take advantage of the features that may be only supported in WebSphere Application Server. To take advantage of JAX-WS and JAXB V2.2 tooling, use the tools provided with the application server that are located in the app_server_root\bin\ directory.

wsimport Parameters

From the IBM documentation:

  • -b <path> - Specifies the external JAX-WS or JAXB binding files. You can specify multiple JAX-WS and JAXB binding files by using the -b option; however, each file must be specified with its own -b option.
  • -B <jaxbOption> - Specifies to pass this option to the JAXB schema compiler.
  • -catalog - Specifies the catalog file to resolve external entity references. It supports the TR9401, XCatalog, and the OASIS XML Catalog formats
  • -d <directory> - Specifies where to place the generated output files.
  • -extension - Specifies whether to accept custom extensions for functionality that are not specified by the JAX-WS specification. The use of custom extensions can result in applications that are not portable or do not interoperate with other implementations.
  • -httpproxy:<host>:<port> - Specifies an HTTP proxy. The default port value is 8080.
  • -keep - Specifies whether to keep the generated source files.
  • -p <package_name> - Specifies a target package with this command-line option and overrides any WSDL file and schema binding customization for the package name and the default package name algorithm defined in the JAX-WS specification.
  • -quiet - Specifies to suppress the wsimport output.
  • -s <directory> - Specifies the directory to place the generated source files.
  • -target <version> - Specifies to generate code that is compliant with a specific JAX-WS specification level. Specify version 2.0 or 2.1 to generate code that is compliant with the JAX-WS 2.0 or JAX-WS 2.1 specification respectively. Specifying version 2.1 indicates to generate code that is compliant with the JAX-WS 2.1 specification. The default value is version 2.2 and generates compliant code for the JAXB 2.2 specification.
  • -verbose - Specifies to output messages about what the compiler is doing.
  • -version - Prints the version information. If you specify this option, only the version information is included in the output and normal command processing does not occur.
  • -wsdlLocation - Specifies the @WebServiceClient.wsdlLocation value.

And some WebSphere extensions:

  • `-XadditionalHeaders1 - map headers not bound to request or response message to Java method parameters
  • -Xauthfile - file to carry authorization information in the format http://username:password@example.org/stock?wsdl
  • -Xdebug - print debug information
  • -Xno-addressing-databinding - enable binding of W3C EndpointReferenceType to Java
  • -Xnocompile - do not compile generated Java files
  • -XdisableSSLHostnameVerification - disable the SSL Hostname verification while fetching wsdls

Time to hack

Some initial requirements I had, generate the server side Java source files into a directory called wsimportcache. The -keep switch is needed if you want to keep the generated Java source code around.

C:\IBM\WebSphere85\AppServer\bin\wsimport.bat -d wsimportcache -keep -verbose c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl

By default wsimport will compile Java source, and you’ll end up with lots of .class files lying around. I didn’t want these, so added -Xnocompile:

C:\IBM\WebSphere85\AppServer\bin\wsimport.bat -Xnocompile -d wsimportcache -keep -verbose c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl

OK. Things are looking good. The -verbose switch is really important as you’ll get full details about errors/warnings. I scrolled to the top of the report to find:

[WARNING] Ignoring SOAP port "JAVAROCKSv20160117Port": it uses non-standard SOAP 1.2 binding.
You must specify the "-extension" option to use this binding.
  line 89 of file:/C:/foo/jaxws/WebContent/WEB-INF/wsdl/net/bencode/concrete/v20160117/net.bencode.harmony.v20160305.binding.wsdl

It seems my WSDL uses some specifications that lay outside of the JAX-WS spec. So included the -extension switch too:

C:\IBM\WebSphere85\AppServer\bin\wsimport.bat -extension -Xnocompile -d wsimportcache -keep -verbose c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl

OK, nearly there. That left a bunch of warnings like below to deal with:

[WARNING] Simple type "StreetTypeType" was not mapped to Enum due to EnumMemberSizeCap limit. Facets count: 405, current limit: 256. You can use customization attribute "typesafeEnumMaxMembers" to extend the limit.
  line 16 of file:/C:/foo/jaxws/WebContent/WEB-INF/wsdl/schema/as4590/Codes/Address/AS4819.Address.codes.2003.xsd

Big XSD restrictions blow out a threshold (default of 256 members) when translating them to a java enum.

To tweak the threshold setting, I created file jaxb-custom.xml (or name it whatever you like):

<jaxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" version="2.1">
  <jaxb:bindings>
    <jaxb:globalBindings typesafeEnumMaxMembers="1000" />
  </jaxb:bindings>
</jaxb:bindings>

Add it to the wsimport command using the -b switch, which allows you to load up a number of external JAX-WS or JAXB binding files:

C:\IBM\WebSphere85\AppServer\bin\wsimport.bat -b jaxb-custom.xml -extension -Xnocompile -d wsimportcache -keep -verbose c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl

Excellent, no more warnings or errors being reported by wsimport. It just gave birth to a huge number of .java files.

After diffing the directory/file tree of the existing artefacts with the newly generated (Beyond Compare thankyou for making diff life more bearable; the XML Tidied with Attributes Sorted format continues to be a life saver), noticed that the old code base I’m dealing with uses JAX-WS 2.1. To prevent breaking things, by using the -target switch, you can ask wsimport to target prior versions (2.0 and 2.1) of the spec, as opposed to the default of 2.2, so I went with 2.1:

C:\IBM\WebSphere85\AppServer\bin\wsimport.bat -target 2.1 -b jaxb-custom.xml -extension -Xnocompile -d wsimportcache -keep -verbose c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl

Time to compile

Great. After synchronising my source tree with the ton of newly wsimport generated code, I discovered code legacy that was previously compiling before I touched it, was now giving compile time errors. The existing WSDL that was modified makes heavy use of SOAP header parameters. These wern’t being mapped as parameters in the Java port type. -XadditionalHeaders to the rescue, this switch map headers not bound to request or response message to Java method parameters.

C:\IBM\WebSphere85\AppServer\bin\wsimport.bat -XadditionalHeaders -target 2.1 -b jaxb-custom.xml -extension -Xnocompile -d wsimportcache -keep -verbose c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl

Brilliant, everything compiling happily again.

Time to test

Now I was finally ready to start running some SoapUI tests on the new and modified web services. I built, cleaned and repackaged a pristine EAR. Nervously I hit the Submit Request button in SoapUI, and crossed my fingers, only to see a java.lang.NullPointerException. Sigh.

[1/03/16 16:29:50:736 EST] 000000a9 AxisEngine    E org.apache.axis2.engine.AxisEngine receive java.lang.NullPointerException
                                 org.apache.axis2.AxisFault: java.lang.NullPointerException
    at org.apache.axis2.AxisFault.makeFault(AxisFault.java:430)
    at org.apache.axis2.jaxws.server.JAXWSMessageReceiver.receive(JAXWSMessageReceiver.java:242)
    at org.apache.axis2.engine.AxisEngine.receive(AxisEngine.java:212)
    at org.apache.axis2.transport.http.HTTPTransportUtils.processHTTPPostRequest(HTTPTransportUtils.java:172)
    at com.ibm.ws.websvcs.transport.http.WASAxis2Servlet.doPost(WASAxis2Servlet.java:1606)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:595)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1230)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:779)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:478)
    at com.ibm.ws.webcontainer.servlet.ServletWrapperImpl.handleRequest(ServletWrapperImpl.java:178)
    at com.ibm.ws.webcontainer.filter.WebAppFilterManager.invokeFilters(WebAppFilterManager.java:1111)
    at com.ibm.ws.webcontainer.webapp.WebApp.handleRequest(WebApp.java:3901)
    at com.ibm.ws.webcontainer.webapp.WebGroup.handleRequest(WebGroup.java:304)
    at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:981)
    at com.ibm.ws.webcontainer.WSWebContainer.handleRequest(WSWebContainer.java:1662)
    at com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:200)
    at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:463)
    at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewRequest(HttpInboundLink.java:530)
    at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.processRequest(HttpInboundLink.java:316)
    at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.ready(HttpInboundLink.java:287)
    at com.ibm.ws.tcp.channel.impl.NewConnectionInitialReadCallback.sendToDiscriminators(NewConnectionInitialReadCallback.java:214)
    at com.ibm.ws.tcp.channel.impl.NewConnectionInitialReadCallback.complete(NewConnectionInitialReadCallback.java:113)
    at com.ibm.ws.tcp.channel.impl.AioReadCompletionListener.futureCompleted(AioReadCompletionListener.java:175)
    at com.ibm.io.async.AbstractAsyncFuture.invokeCallback(AbstractAsyncFuture.java:217)
    at com.ibm.io.async.AsyncChannelFuture.fireCompletionActions(AsyncChannelFuture.java:161)
    at com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:138)
    at com.ibm.io.async.ResultHandler.complete(ResultHandler.java:204)
    at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:775)
    at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:905)
    at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1865)
Caused by: javax.xml.ws.WebServiceException: java.lang.NullPointerException
    at org.apache.axis2.jaxws.ExceptionFactory.createWebServiceException(ExceptionFactory.java:175)
    at org.apache.axis2.jaxws.ExceptionFactory.makeWebServiceException(ExceptionFactory.java:70)
    at org.apache.axis2.jaxws.ExceptionFactory.makeWebServiceException(ExceptionFactory.java:128)
    at org.apache.axis2.jaxws.marshaller.impl.alt.DocLitWrappedPlusMethodMarshaller.demarshalRequest(DocLitWrappedPlusMethodMarshaller.java:423)
    at org.apache.axis2.jaxws.server.dispatcher.JavaBeanDispatcher.createRequestParameters(JavaBeanDispatcher.java:322)
    at org.apache.axis2.jaxws.server.dispatcher.JavaBeanDispatcher.invoke(JavaBeanDispatcher.java:102)
    at org.apache.axis2.jaxws.server.EndpointController.invoke(EndpointController.java:111)
    at org.apache.axis2.jaxws.server.JAXWSMessageReceiver.receive(JAXWSMessageReceiver.java:161)
    ... 29 more
Caused by: java.lang.NullPointerException
    at org.apache.axis2.jaxws.marshaller.impl.alt.DocLitWrappedPlusMethodMarshaller.demarshalRequest(DocLitWrappedPlusMethodMarshaller.java:410)
    ... 33 more

It turns out the WSDL makes heavy use of SOAP header parameters. Several IBM articles like this one implied:

A NullPointerException error may occur while running a JAX-WS application. This error is likely to occur if the wsdl operation has a part that is mapped to a header element.

I also noticed that the complex types had been broken down into individual parts/members, so instead of getting a nice high level class repesenting the entire request, the Java port definition had dozens of parameters, one for each individual component within the request. This article helped me realise I was dealing with wrapped input parameters problem. Basically the JAXB serializer will take care of wrapping things up for you, exposing you to only to the raw members. Disabling this wrapping mode functionality was also my key to solving the NullPointerException with the header elements.

To disable wrapping, I created file jaxws-custom.xml (or name it whatever you like):

<jaxws:bindings
  wsdlLocation="c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl"
  xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
  <jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
</jaxws:bindings>

And fed it in to my growing wsimport command via a second -b argument:

C:\IBM\WebSphere85\AppServer\bin\wsimport.bat -b jaxws-custom.xml -XadditionalHeaders -target 2.1 -b jaxb-custom.xml -extension -Xnocompile -d wsimportcache -keep -verbose c:\foo\jaxws\WebContent\WEB-INF\wsdl\net\bencode\concrete\v20160117\net.bencode.harmony.v20160305.binding.wsdl

With Wrapper Styling (default)

First a snippet highlighting the result with wrapping enabled (default). Notice the use of the @RequestWrapper and @ResponseWrapper annotations, and the large number of parameters.

//
// Generated By:JAX-WS RI IBM 2.2.1-07/09/2014 01:53 PM(foreman)- (JAXB RI IBM 2.2.3-07/07/2014 12:56 PM(foreman)-)
//

// lots omitted for brevity

@WebMethod
@RequestWrapper(localName = "doImportantThing", targetNamespace = "http://net.bencode.services/importantthing/schema/messages/2016/03/02", className = "net.bencode.services.importantthing.schema.core._2016._03._05.ImportantThingChangeType")
@ResponseWrapper(localName = "doImportantThingResponse", targetNamespace = "http://net.bencode.services/importantthing/schema/messages/2016/03/02", className = "net.bencode.services.importantthing.schema.messages._2016._03._05.doImportantThingResponseType")
@Action(input = "http://ns.bencode.net/importantthing/concrete/svc/2016/03/02/doImportantThing", output = "http://ns.bencode.net/importantthing/concrete/svc/2016/03/02/doImportantThingresponse", fault = {
    @FaultAction(className = StandardErrorMsg.class, value = "http://net.bencode.services/svc/commonmessages/2016/03/02/standardfault")
})
public void doImportantThing(
    @WebParam(name = "coreData", targetNamespace = "http://net.bencode.services/common/schema/2016/03/02/elements")
    CoreDataType coreData,
    @WebParam(name = "importantThing", targetNamespace = "http://net.bencode.services/importantthing/schema/core/2016/03/02")
    ImportantThingType importantThing,
    @WebParam(name = "additionalDetails", targetNamespace = "http://net.bencode.services/importantthing/schema/core/2016/03/02")
    AdditionalDetailsType additionalDetails,
    @WebParam(name = "changeDetails", targetNamespace = "http://net.bencode.services/importantthing/schema/core/2016/03/02")
    ChangeDetailsType changeDetails,
    @WebParam(name = "doImportantThingResult", targetNamespace = "http://net.bencode.services/importantthing/schema/core/2014/10/25", mode = WebParam.Mode.OUT)
    Holder<SuccessFailureEnum> doImportantThingResult,
    @WebParam(name = "serviceMessages", targetNamespace = "http://net.bencode.services/importantthing/schema/messages/2016/03/02", mode = WebParam.Mode.OUT)
    Holder<ServiceMessagesType> serviceMessages,
    @WebParam(name = "Security", targetNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", header = true, partName = "securityHeader")
    SecurityHeaderType securityHeader,
    @WebParam(name = "subjectId", targetNamespace = "http://net.bencode.services/common/schema/2016/03/02/elements", header = true, partName = "subjectIdHeader")
    QualifiedId subjectIdHeader,
    @WebParam(name = "audit", targetNamespace = "http://net.bencode.services/common/schema/2016/03/02/elements", header = true, partName = "auditHeader")
    AuditType auditHeader,
    @WebParam(name = "product", targetNamespace = "http://net.bencode.services/common/schema/2016/03/02/elements", header = true, mode = WebParam.Mode.INOUT, partName = "productHeader")
    Holder<ProductType> productHeader,
    @WebParam(name = "Action", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.INOUT, partName = "wsaActionHeader")
    Holder<AttributedURIType> wsaActionHeader,
    @WebParam(name = "MessageID", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.INOUT, partName = "wsaMessageIDHeader")
    Holder<AttributedURIType> wsaMessageIDHeader,
    @WebParam(name = "To", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.INOUT, partName = "wsaToHeader")
    Holder<AttributedURIType> wsaToHeader,
    @WebParam(name = "RelatesTo", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.OUT, partName = "wsaRelatesToHeader")
    Holder<RelatesToType> wsaRelatesToHeader)
    throws StandardErrorMsg
;

Without Wrapper Styling

With wrapping disable the number of parameters has reduced significantly, with parameters for individual headers, and two top level classes for the request and response.

//
// Generated By:JAX-WS RI IBM 2.2.1-07/09/2014 01:53 PM(foreman)- (JAXB RI IBM 2.2.3-07/07/2014 12:56 PM(foreman)-)
//

// lots omitted for brevity

@WebMethod
@WebResult(name = "doImportantThingResponse", targetNamespace = "http://net.bencode.services/importantthing/schema/messages/2016/03/02", partName = "response")
@Action(input = "http://ns.bencode.net/importantthing/concrete/svc/2016/03/02/doImportantThing", output = "http://ns.bencode.net/importantthing/concrete/svc/2016/03/02/doImportantThingresponse", fault = {
    @FaultAction(className = StandardErrorMsg.class, value = "http://net.bencode.services/svc/commonmessages/2016/03/02/standardfault")
})
public DoImportantThingResponseType doImportantThing(
    @WebParam(name = "doImportantThing", targetNamespace = "http://net.bencode.services/importantthing/schema/messages/2016/03/02", partName = "request")
    ImportantThingChangeType request,
    @WebParam(name = "Security", targetNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", header = true, partName = "securityHeader")
    SecurityHeaderType securityHeader,
    @WebParam(name = "subjectId", targetNamespace = "http://net.bencode.services/common/schema/2016/03/02/elements", header = true, partName = "subjectIdHeader")
    QualifiedId subjectIdHeader,
    @WebParam(name = "audit", targetNamespace = "http://net.bencode.services/common/schema/2016/03/02/elements", header = true, partName = "auditHeader")
    AuditType auditHeader,
    @WebParam(name = "product", targetNamespace = "http://net.bencode.services/common/schema/2016/03/02/elements", header = true, mode = WebParam.Mode.INOUT, partName = "productHeader")
    Holder<ProductType> productHeader,
    @WebParam(name = "Action", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.INOUT, partName = "wsaActionHeader")
    Holder<AttributedURIType> wsaActionHeader,
    @WebParam(name = "MessageID", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.INOUT, partName = "wsaMessageIDHeader")
    Holder<AttributedURIType> wsaMessageIDHeader,
    @WebParam(name = "To", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.INOUT, partName = "wsaToHeader")
    Holder<AttributedURIType> wsaToHeader,
    @WebParam(name = "RelatesTo", targetNamespace = "http://www.w3.org/2005/08/addressing", header = true, mode = WebParam.Mode.OUT, partName = "wsaRelatesToHeader")
    Holder<RelatesToType> wsaRelatesToHeader)
    throws StandardErrorMsg
;