Search code examples
javaweb-servicessoapwsdljax-ws

javax.xml.ws.WebServiceException: Method X is exposed as WebMethod, but there is no corresponding wsdl operation


I'm integrating with a third party web service (Adyen) using JAX-WS RI. I have downloaded a copy of their wsdl and have used jaxws:wsdl2java in my build to generate the web service implementation source code. At runtime, when I attempt to setup a port by calling the getPort() method of my auto-generated Payment service class, I get the following exception claiming that there is a method exposed, but it is not present in the wsdl portType element:

javax.xml.ws.WebServiceException: Method adjustAuthorisation is exposed as WebMethod, but there is no corresponding wsdl operation with name {http://payment.services.adyen.com}adjustAuthorisation in the wsdl:portType{http://payment.services.adyen.com}PaymentPortType

But, it is present in the portType element. Here's the relevant snipped of the wsdl:

<wsdl:portType name="PaymentPortType">
  <wsdl:operation name="adjustAuthorisation">
    <wsdl:input name="adjustAuthorisationRequest" message="tns:adjustAuthorisationRequest" />
    <wsdl:output name="adjustAuthorisationResponse" message="tns:adjustAuthorisationResponse" />
      <wsdl:fault name="ServiceException" message="tns:ServiceException" />
  </wsdl:operation>
  ...
</wsdl:portType>

The full wsdl can be seen here: https://pal-live.adyen.com/pal/servlet/Payment/v30?wsdl

The wsdl is included in the target jar with classpath /wsdl/Payment.wsdl. I am loading it at runtime using this code in a config class:

URL wsdl = getClass().getResource(wsdlLocation);
onlineService = new Payment(wsdl, new QName(serviceUrl, serviceName));

Where serviceUrl = "http://payment.services.adyen.com" and serviceName = "Payment" which matches the wsdl.

Finally, here's the code snippet where I attempt to open the port and ultimately get the exception:

ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
try
{
  port = config.getOnlineService().getPaymentHttpPort(); // exception thrown here
}
finally
{
  Thread.currentThread().setContextClassLoader(oldClassLoader);
}

Any ideas why it seems to be misreading the wsdl? Another potentially important piece of info is that I recently updated the wsdl, previously my app used version 12 of Adyen's API with the corresponding wsdl, now I am upgrading to version 30. The application worked fine with the same code previously.


Solution

  • The stack trace for the javax.xml.ws.WebServiceException shows what's happening under the hood when constructing a new Payment service using the code generated from the wsdl:

        at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(RuntimeWSDLParser.java:265) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:246) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:209) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:178) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.client.WSServiceDelegate.parseWSDL(WSServiceDelegate.java:363) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:321) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:230) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:211) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:207) ~[jaxws-rt.jar:2.2.10]
        at com.sun.xml.ws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:114) ~[jaxws-rt.jar:2.2.10]
        at javax.xml.ws.Service.<init>(Service.java:77) ~[?:1.8.0_252]
        at com.amazon.adyen.payment.Payment.<init>(Payment.java:58) ~[?:?]
    

    RuntimeWSDLParser.parse calls java.net.URL.openStream, which calls sun.net.www.protocol.jar.JarURLConnection.getInputStream since in this case, the wsdl in part of the jar.

    When calling getInputStream on a URLConnection, the default behavior is to check the cache. This can be disabled for a given connection by opening a connection to the resource, setting useCaches to false, and then opening the input stream. However, the code which opens the stream to the resource is auto-generated in this case, and modifying the generated classes wouldn't be a good solution for future changes.

    URLConnection also has a setDefaultUseCaches method, which will set the behavior for the current connection and all future connections. See the documentation in the URLConnection source code: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/net/URLConnection.java#l245

    I was able to force the wsdl to be loaded from the jar by setting this default flag, and then reverting it after loading the resource.

    The working code is below which loads the resource from the jar if there is an error fetching it from cache:

    URL wsdl = getClass().getResource(wsdlLocation);
    
    URLConnection connection = wsdl.openConnection();
    try {
        onlineService = new Payment(wsdl, new QName(serviceUrl, serviceName));
    } catch (WebServiceException wsException) {
        // if wsdl resources in the jar have changed, an exception will be thrown due to
        // loading the old version from cache. Attempt to load without using the cache.
        LOG.warn("Failure initializing service. Trying again without using URLConnection caching. Caught exception: ", wsException);
        connection.setDefaultUseCaches(false);
        onlineService = new Payment(wsdl, new QName(serviceUrl, serviceName));
    } finally {
        connection.setDefaultUseCaches(true);
    }
    

    If you are able to load the input stream directly, you could do the following without touching the default caching behavior that could affect other connections:

    URL wsdl = getClass().getResource(wsdlLocation);
    URLConnection connection = wsdl.openConnection();
    connection.setUseCaches(false);
    InputStream inputStream = connection.getInputStream();