I am trying to develop a quick SOAP server to use during testing using Spring-Boot and Spring-WS. It is contract first as I have existing WSDL and XSD files and have my java classes generated at build time from them. However, when I send a request through using either curl
or SOAPUI, I get the following error:
$ curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">No adapter for endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>) throws org.csapi.wsdl.parlayx.common.v3_0.faults.ServiceException,org.csapi.wsdl.parlayx.common.v3_0.faults.PolicyException]: Is your endpoint annotated with @Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
(Highlights: No adapter for endpoint [...] Is your endpoint annotated with @Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
The answer to this question is yes, my endpoint is annotated with @Endpoint
. Full setup below:
build.gradle
plugins {
id 'org.springframework.boot'
}
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-log4j2'
}
}
version = '0.0.1-SNAPSHOT'
dependencies {
compile("org.springframework.boot:spring-boot-starter-web-services")
compile("wsdl4j:wsdl4j:1.6.1")
compile("org.glassfish.jaxb:jaxb-xjc:2.2.11")
compile('javax.xml.soap:javax.xml.soap-api:1.4.0')
compile('com.sun.xml.messaging.saaj:saaj-impl:1.5.1')
compile project(':meanwhile-in-hell-volume-charging-v3-1-wsdl')
testCompile("org.springframework.boot:spring-boot-starter-test")
}
MockSoapConfig.java
package com.meanwhileinhell.mock.soap.config;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
@EnableWs
@Configuration
public class MockSoapConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean messageDispatcherServlet(final ApplicationContext applicationContext) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(applicationContext);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(messageDispatcherServlet, "/ws/*");
}
}
MockVolumeChargingEndpoint.java
package com.meanwhileinhell.mock.soap;
import java.util.List;
import org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation;
import org.csapi.schema.parlayx.bpxg.common.v3_1.ObjectFactory;
import org.csapi.schema.parlayx.payment.v3_0.Property;
import org.csapi.wsdl.parlayx.common.v3_0.faults.PolicyException;
import org.csapi.wsdl.parlayx.common.v3_0.faults.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import generated.org.csapi.wsdl.parlayx.bpxg.payment.volume_charging.v3_1._interface.VolumeCharging;
@Endpoint
public class MockVolumeChargingEndpoint implements VolumeCharging {
private static final Logger logger = LoggerFactory.getLogger(MockVolumeChargingEndpoint.class);
private static final String NAMESPACE = "http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/service";
@Autowired
public MockVolumeChargingEndpoint() {
}
@Override
@PayloadRoot(namespace = NAMESPACE, localPart = "getAmount")
@ResponsePayload
public ChargingInformation getAmount(final String endUserIdentifier,
final long volume,
final List<Property> parameters) throws
ServiceException,
PolicyException {
logger.debug("GetAmount for endUserIdentifier=[{}], volume=[{}], properties=[{}]",
endUserIdentifier,
volume,
parameters);
ObjectFactory objectFactory = new ObjectFactory();
return objectFactory.createChargingInformation();
}
}
This is the generated interface that I am implementing: VolumeCharging.java
package generated.org.csapi.wsdl.parlayx.bpxg.payment.volume_charging.v3_1._interface;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
/**
* This class was generated by Apache CXF 3.3.2
* 2020-01-27T09:24:18.615Z
* Generated source version: 3.3.2
*
*/
@WebService(targetNamespace = "http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/interface", name = "VolumeCharging")
@XmlSeeAlso({org.csapi.schema.parlayx.payment.volume_charging.v3_1.local.ObjectFactory.class, org.csapi.schema.parlayx.bpxg.payment.volume_charging.v3_1.local.ObjectFactory.class, org.csapi.schema.parlayx.bpxg.common.v3_1.ObjectFactory.class, org.csapi.schema.parlayx.common.v3_1.ObjectFactory.class, org.csapi.schema.parlayx.payment.v3_0.ObjectFactory.class})
public interface VolumeCharging {
@WebMethod
@RequestWrapper(localName = "getAmount", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local", className = "org.csapi.schema.parlayx.bpxg.payment.volume_charging.v3_1.local.GetAmount")
@ResponseWrapper(localName = "getAmountResponse", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local", className = "org.csapi.schema.parlayx.bpxg.payment.volume_charging.v3_1.local.GetAmountResponse")
@WebResult(name = "result", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation getAmount(
@WebParam(name = "endUserIdentifier", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
java.lang.String endUserIdentifier,
@WebParam(name = "volume", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
long volume,
@WebParam(name = "parameters", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property> parameters
) throws org.csapi.wsdl.parlayx.common.v3_0.faults.ServiceException, org.csapi.wsdl.parlayx.common.v3_0.faults.PolicyException;
}
This is the SOAP packet that I am sending in my curl
/SOAPUI request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:mock="http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/service">
<soapenv:Header/>
<soapenv:Body>
<mock:getAmount>
<mock:endUserIdentifier>TestUserId</mock:endUserIdentifier>
<mock:volume>123123</mock:volume>
<mock:parameters>
<name>ParameterName</name>
<value>ParameterValue</value>
</mock:parameters>
</mock:getAmount>
</soapenv:Body>
</soapenv:Envelope>
I just see why it's not hitting my endpoint. If I change the name getAmount
in my request packet to something that is definitely not implemented, I get the message:
No endpoint mapping found for [SaajSoapMessage {http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/service}getTheWeather]
Turning on DEBUG logging for Spring WebServices, I see that my endpoints are being mapped to my endpoint method ok:
2020-01-28 16:36:35.881 DEBUG 72581 --- [ main] yloadRootAnnotationMethodEndpointMapping : Mapped [{http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local}getAmount] onto endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]
2020-01-28 16:36:58.316 DEBUG 72581 --- [nio-8080-exec-1] .WebServiceMessageReceiverHandlerAdapter : Accepting incoming [org.springframework.ws.transport.http.HttpServletConnection@27ef75ac] at [http://localhost:8080/ws]
2020-01-28 16:36:58.481 DEBUG 72581 --- [nio-8080-exec-1] o.s.ws.server.MessageTracing.received : Received request [SaajSoapMessage {http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local}getAmount]
2020-01-28 16:36:58.541 DEBUG 72581 --- [nio-8080-exec-1] yloadRootAnnotationMethodEndpointMapping : Looking up endpoint for [{http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local}getAmount]
2020-01-28 16:36:58.541 DEBUG 72581 --- [nio-8080-exec-1] o.s.w.soap.server.SoapMessageDispatcher : Endpoint mapping [org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping@623a8092] maps request to endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]
2020-01-28 16:36:58.544 DEBUG 72581 --- [nio-8080-exec-1] o.s.w.soap.server.SoapMessageDispatcher : Testing endpoint adapter [org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter@3571b748]
2020-01-28 16:36:58.547 DEBUG 72581 --- [nio-8080-exec-1] s.e.SoapFaultAnnotationExceptionResolver : Resolving exception from endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]: java.lang.IllegalStateException: No adapter for endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]: Is your endpoint annotated with @Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
So it would seem that my endpoint is getting mapped ok, but there is some issue with the endpoint adapter?
Needed to wrap my payloads in JAXBElement
type for Spring to get the message handler adapters to support the endpoint.
@PayloadRoot(namespace = NAMESPACE, localPart = "getAmount")
@ResponsePayload
public JAXBElement<GetAmountResponse> getAmount(@RequestPayload JAXBElement<GetAmount> request) {
...
}