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>
<!--===============================-->
Based on further "investigation" it appears that there are three ways around this problem:
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?.