ONVIF WSDL Operations with upper case names not being honoured by JAX-WS wsimport Java Code Generation

193 Views Asked by At

Problem:

I am compiling ONVIF WSDL which uses all capital letter operations names:

Here is extract of WSDL:

        <wsdl:portType name="Device">
                <wsdl:operation name="GetServices">
                        <wsdl:documentation>Returns information about services on the device.</wsdl:documentation>
                        <wsdl:input message="tds:GetServicesRequest"/>
                        <wsdl:output message="tds:GetServicesResponse"/>
                </wsdl:operation>
                <wsdl:operation name="GetServiceCapabilities">
                        <wsdl:documentation>Returns the capabilities of the device service. The result is returned in a typed answer.</wsdl:documentation>
                        <wsdl:input message="tds:GetServiceCapabilitiesRequest"/>
                        <wsdl:output message="tds:GetServiceCapabilitiesResponse"/>
                </wsdl:operation>
                <wsdl:operation name="GetDeviceInformation">
                        <wsdl:documentation>This operation gets basic device information from the device.</wsdl:documentation>
                        <wsdl:input message="tds:GetDeviceInformationRequest"/>
                        <wsdl:output message="tds:GetDeviceInformationResponse"/>
                </wsdl:operation>

But the generated Java code always has first letter lower case methods.

Here is sample of generated code:

/**
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 4.0.0-M4
 * Generated source version: 3.0
 *
 */
@WebService(name = "Device", targetNamespace = "http://www.onvif.org/ver10/device/wsdl")
@XmlSeeAlso({
    org.onvif.ver10.device.wsdl.ObjectFactory.class,
    org.oasis_open.docs.wsn.b_2.ObjectFactory.class,
    org.oasis_open.docs.wsn.t_1.ObjectFactory.class,
    org.oasis_open.docs.wsrf.bf_2.ObjectFactory.class,
    org.onvif.ver10.schema.ObjectFactory.class,
    org.w3._2003._05.soap_envelope.ObjectFactory.class,
    org.w3._2004._08.xop.include.ObjectFactory.class,
    org.w3._2005._05.xmlmime.ObjectFactory.class,
    org.w3._2005._08.addressing.ObjectFactory.class
})
public interface Device {


    /**
     * Returns information about services on the device.
     *
     * @param includeCapability
     * @return
     *     returns java.util.List<org.onvif.ver10.device.wsdl.Service>
     */
    @WebMethod(operationName = "GetServices", action = "http://www.onvif.org/ver10/device/wsdl/GetServices")
    @WebResult(name = "Service", targetNamespace = "http://www.onvif.org/ver10/device/wsdl")
    @RequestWrapper(localName = "GetServices", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", className = "org.onvif.ver10.device.wsdl.GetServices")
    @ResponseWrapper(localName = "GetServicesResponse", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", className = "org.onvif.ver10.device.wsdl.GetServicesResponse")
    public List<Service> getServices(
        @WebParam(name = "IncludeCapability", targetNamespace = "http://www.onvif.org/ver10/device/wsdl")
        boolean includeCapability);

    /**
     * Returns the capabilities of the device service. The result is returned in a typed answer.
     *
     * @return
     *     returns org.onvif.ver10.device.wsdl.DeviceServiceCapabilities
     */
    @WebMethod(operationName = "GetServiceCapabilities", action = "http://www.onvif.org/ver10/device/wsdl/GetServiceCapabilities")
    @WebResult(name = "Capabilities", targetNamespace = "http://www.onvif.org/ver10/device/wsdl")
    @RequestWrapper(localName = "GetServiceCapabilities", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", className = "org.onvif.ver10.device.wsdl.GetServiceCapabilities")
    @ResponseWrapper(localName = "GetServiceCapabilitiesResponse", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", className = "org.onvif.ver10.device.wsdl.GetServiceCapabilitiesResponse")
    public DeviceServiceCapabilities getServiceCapabilities();

    /**
     * This operation gets basic device information from the device.
     *
     * @param serialNumber
     * @param hardwareId
     * @param model
     * @param firmwareVersion
     * @param manufacturer
     */
    @WebMethod(operationName = "GetDeviceInformation", action = "http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation")
    @RequestWrapper(localName = "GetDeviceInformation", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", className = "org.onvif.ver10.device.wsdl.GetDeviceInformation")
    @ResponseWrapper(localName = "GetDeviceInformationResponse", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", className = "org.onvif.ver10.device.wsdl.GetDeviceInformationResponse")
    public void getDeviceInformation(
        @WebParam(name = "Manufacturer", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", mode = WebParam.Mode.OUT)
        Holder<String> manufacturer,
        @WebParam(name = "Model", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", mode = WebParam.Mode.OUT)
        Holder<String> model,
        @WebParam(name = "FirmwareVersion", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", mode = WebParam.Mode.OUT)
        Holder<String> firmwareVersion,
        @WebParam(name = "SerialNumber", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", mode = WebParam.Mode.OUT)
        Holder<String> serialNumber,
        @WebParam(name = "HardwareId", targetNamespace = "http://www.onvif.org/ver10/device/wsdl", mode = WebParam.Mode.OUT)
        Holder<String> hardwareId);

The result is that both Server Stub code and Client proxy do not confirm to WSDL and the exposed SOAP interace only accepts requests which match lower cased method names.

Environment is:

  • Ubuntu 22.04
  • OpenJDK 11
  • Embedded Jetty Version 10 for javax.* generated code
  • Embedded Jetty Version 11 for jakarta.* generated code
  • Maven JAX-WS Plugin: com.sun.xml.ws: jaxws-maven-plugin: 2.3.5 & 3.0.2 & 4.0.0

JAX-WS Version:

I have tested with both Java EE & Jakarta using multiple version of Maven JAX-WS Plugin

I have tested directly with curl (see results below).

Expectation:

Based on the Jakarta JAX-WS Specification it says:

  • "◊ Conformance (Method naming): In the absence of customizations, the name of a mapped Java method MUST be the value of the name attribute of the wsdl:operation element mapped according to the rules described in Section 2.8, “XML Names”."

Given that there is no customisation defined the Java should conform to WSDL.

What did I try ? :

I have tried this with wsimport generating older javax.* annotations and lastest jakarta.* (version 4 annocations).

So far in all cases I have only get generated results with lower case first letter in method name (for all upper case WSDL specified operations).

via Curl with "getDeviceInformation" (lower case):

curl --verbose  http://127.0.0.1:9080/onvif/device_service -H "Content-Type: text/xml" --data '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><getDeviceInformation xmlns="http://www.onvif.org/ver10"></getDeviceInformation></s:Body></s:Envelope>' | xmllint --format -
*   Trying 127.0.0.1:9080...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 127.0.0.1 (127.0.0.1) port 9080 (#0)
> POST /onvif/device_service HTTP/1.1
> Host: 127.0.0.1:9080
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Type: text/xml
> Content-Length: 273
> 
} [273 bytes data]
100   273    0     0  100   273      0      4  0:01:08  0:01:00  0:00:08     0* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sat, 07 Jan 2023 08:36:48 GMT
< Content-Type: text/xml;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(11.0.12)
< 
{ [114 bytes data]
100  1037    0   764  100   273     12      4  0:01:08  0:01:00  0:00:08   182
* Connection #0 to host 127.0.0.1 left intact
<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
  <S:Body>
    <ns3:getDeviceInformationResponse xmlns:ns3="http://www.onvif.org/ver10" xmlns:ns4="http://www.onvif.org/ver10/schema" xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:ns6="http://www.onvif.org/ver10/device/wsdl" xmlns:ns7="http://www.w3.org/2004/08/xop/include" xmlns:ns8="http://docs.oasis-open.org/wsrf/bf-2" xmlns:ns9="http://www.w3.org/2005/08/addressing" xmlns:ns10="http://docs.oasis-open.org/wsn/b-2" xmlns:ns11="http://docs.oasis-open.org/wsn/t-1" xmlns:ns12="http://www.w3.org/2003/05/soap-envelope">
      <arg0>john</arg0>
      <arg1>beta</arg1>
      <arg2>0.0.1</arg2>
      <arg3>1</arg3>
      <arg4>hw1</arg4>
    </ns3:getDeviceInformationResponse>
  </S:Body>
</S:Envelope>

via Curl with "GetDeviceInformation" (upper case):

curl --verbose  http://127.0.0.1:9080/onvif/device_service -H "Content-Type: text/xml" --data '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><tds:GetDeviceInformation xmlns:tds="http://www.onvif.org/ver10/device/wsdl"></GetDeviceInformation></s:Body></s:Envelope>'
*   Trying 127.0.0.1:9080...
* Connected to 127.0.0.1 (127.0.0.1) port 9080 (#0)
> POST /onvif/device_service HTTP/1.1
> Host: 127.0.0.1:9080
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Type: text/xml
> Content-Length: 293
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Server Error
< Date: Sat, 07 Jan 2023 08:40:16 GMT
< Content-Type: text/xml;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(11.0.12)
< 
* Connection #0 to host 127.0.0.1 left intact
<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope"><faultcode>S:Client</faultcode><faultstring>Cannot find dispatch method for {http://www.onvif.org/ver10/device/wsdl}GetDeviceInformation</faultstring></S:Fault></S:Body></S:Envelope> 

As I initially thought the problem was due to JAXB Mapping, I added inline configuration to turn off "Java Naming Convention" (see below - enableJavaNamingConventions = "false"):

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" targetNamespace="http://www.onvif.org/ver10/device/wsdl">
        <wsdl:types>
                <xs:schema targetNamespace="http://www.onvif.org/ver10/device/wsdl" xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb" jaxb:version="3.0" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" elementFormDefault="qualified" version="22.12">
                        <xs:annotation><xs:appinfo><jaxb:globalBindings
                          enableJavaNamingConventions = "false"
                          underscoreBinding = "asCharInWord"/>
                        </xs:appinfo></xs:annotation>
                        <xs:import namespace="http://www.onvif.org/ver10/schema" schemaLocation="../../../ver10/schema/onvif.xsd"/>
                        <!--===============================-->
                        <xs:element name="GetServices">
                                <xs:complexType>
                                        <xs:sequence>
                                                <xs:element name="IncludeCapability" type="xs:boolean">
                                                        <xs:annotation>
                                                                <xs:documentation>Indicates if the service capabilities (untyped) should be included in the response.</xs:documentation>
                                                        </xs:annotation>
                                                </xs:element>
                                        </xs:sequence>
                                </xs:complexType>
                        </xs:element>
                        <xs:element name="GetServicesResponse">
                                <xs:complexType>
                                        <xs:sequence>
                                                <xs:element name="Service" type="tds:Service" maxOccurs="unbounded">
                                                        <xs:annotation>
                                                                <xs:documentation>Each Service element contains information about one service.</xs:documentation>
                                                        </xs:annotation>
                                                </xs:element>
                                        </xs:sequence>
                                </xs:complexType>
                        </xs:element>
                        <!--===============================-->
1

There are 1 best solutions below

0
zebity On

Based on further "investigation" it appears that there are three ways around this problem:

  1. Post process to generated Java code to change all the method get/set methods to Get/Get (hack #1),
  2. Pre-process the WSDL to extract all the operation Get/Set names and feed this into JAX-WS Customization script which does "XPath" search for all Binding Operations and changes the "method" name to use verbatim Get/Set names and so stop Java naming convention being applied (this is official technique) or
  3. Modify WsImport code to use alternate "NameConverter" method (hack #2).

I have tested option (3) and this seems to be easiest way around this problem. So forking the Jakarta metro reference implementation (RI) to inject alternate method namer. More details are in github respository.

I have also created XLST utility to parse ONVIF WSDL and generate "XML Binding Customisation" file to to input into wsimport via -b option. The generator is working but is use might be failing due to in-correct wsdlLocation value (ie mismatch between offical location vs location on file system).Using this you should be able to use "official" JAXB approach then using hacked "wsimport?.