I'm using springboot to create easy to configure mock for webservices, I've embbeded ´spring-boot-starter-web-services´ artifact in my project. I followed guide from spring-io on how to configure endpoints. But I would like to instantiate a bean which will handle every soap request incoming with a defined namespace and handle the response creation. I've searched a lot into reference of spring-webservice, tried interceptors and listeners but without success, I always got the 404 'No endpoint mapping found for [SaajSoapMessage {myNamespace}MyRequest]'.
I also used javassist to generate Endpoint annotated class with all operations mappings but dispatcher doesn't load this Endpoint.
Thanks for your help / suggestions,
Edit:
As suggested, I add my Poc repository to show my wip: https://github.com/Servhome/sb2-ws-sample
Here is the starting log I obtain when I run the app :
2019-09-23 11:16:06.504 DEBUG 6184 --- [ restartedMain] o.s.w.w.w.p.SuffixBasedPortTypesProvider : Creating port type [{http://sample.com/int/Sample/v1}SamplePortType]
2019-09-23 11:16:06.511 DEBUG 6184 --- [ restartedMain] o.s.w.w.w.p.SuffixBasedPortTypesProvider : Adding operation [searchByName] to port type [{http://sample.com/int/Sample/v1}SamplePortType]
2019-09-23 11:16:06.511 DEBUG 6184 --- [ restartedMain] o.s.w.w.w.p.SuffixBasedPortTypesProvider : Adding operation [searchById] to port type [{http://sample.com/int/Sample/v1}SamplePortType]
2019-09-23 11:16:06.513 DEBUG 6184 --- [ restartedMain] o.s.w.w.wsdl11.provider.Soap11Provider : Creating binding [{http://sample.com/int/Sample/v1}SamplePortTypeSoap11]
2019-09-23 11:16:06.519 DEBUG 6184 --- [ restartedMain] o.s.w.w.wsdl11.provider.Soap11Provider : Creating service [{http://sample.com/int/Sample/v1}SamplePortTypeService]
2019-09-23 11:16:06.520 DEBUG 6184 --- [ restartedMain] o.s.w.w.wsdl11.provider.Soap11Provider : Adding port [SamplePortTypeSoap11] to service [{http://sample.com/int/Sample/v1}SamplePortTypeService]
2019-09-23 11:16:06.529 DEBUG 6184 --- [ restartedMain] yloadRootAnnotationMethodEndpointMapping : Looking for endpoints in application context: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6dea6e9c, started on Mon Sep 23 11:16:02 CEST 2019
2019-09-23 11:16:06.562 DEBUG 6184 --- [ restartedMain] oapActionAnnotationMethodEndpointMapping : Looking for endpoints in application context: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6dea6e9c, started on Mon Sep 23 11:16:02 CEST 2019
2019-09-23 11:16:06.593 DEBUG 6184 --- [ restartedMain] o.s.w.s.e.a.DefaultMethodEndpointAdapter : No MethodArgumentResolvers set, using defaults: [org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor@7c332390, org.springframework.ws.server.endpoint.adapter.method.MessageContextMethodArgumentResolver@4c642359, org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor@31c15cce, org.springframework.ws.server.endpoint.adapter.method.XPathParamMethodArgumentResolver@3c00b80a, org.springframework.ws.soap.server.endpoint.adapter.method.SoapMethodArgumentResolver@594a23b9, org.springframework.ws.soap.server.endpoint.adapter.method.SoapHeaderElementMethodArgumentResolver@3459ad22, org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor@f45b04b, org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor@363c8941, org.springframework.ws.server.endpoint.adapter.method.StaxPayloadMethodArgumentResolver@3ee333d4]
2019-09-23 11:16:06.599 DEBUG 6184 --- [ restartedMain] o.s.w.s.e.a.DefaultMethodEndpointAdapter : No MethodReturnValueHandlers set, using defaults: [org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor@47aaa997, org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor@65cf9ec1, org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor@376b65b8, org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor@10b19d12]
Which indicates that the WSDL definition works properly, but no endpoint can be bound to these operation definitions. I want to be able to handle every request from namespace http://sample.com/int/Sample/v1 by a single Bean 'SampleEndpoint' for instance. And furthermore, be able to make it configurable as I made configurable the WSDL definitions through the CustomWsInitializer class.
I managed to get to a solution that fills my need of a global soap endpoint for a configurable list operations of a given wsdl.
If you are interested in such solution, please see https://github.com/Servhome/sb2-ws-sample I will explain the solution for several steps.
Goal #1 : Create wsdl definition and auto publish these endpoints by configuration:
application-local.properties (github link)
soap.endpoints.path=/services
soap.endpoints=Sample
soap.endpoints.Sample.wsdl.location=classpath:/xsd/Sample.xsd
soap.endpoints.Sample.portType.name=SamplePortType
soap.endpoints.Sample.target.namespace=http://sample.com/int/Sample/v1
Here it's the wsdl definition configuration: wsdl location, the port type name and the namespace (see xsd's targetnamespace).
I registered a context Initializer in the app main, CustomWsInitializer (github link):
private void registerEndpointService(GenericApplicationContext genericApplicationContext, Environment env, String endpointName, String locationUri) {
Resource resource = genericApplicationContext.getResource(env.getProperty(SOAP_ENDPOINTS + endpointName + ".wsdl.location"));
SimpleXsdSchema schema = new SimpleXsdSchema(resource);
genericApplicationContext.registerBean(endpointName + "Schema", SimpleXsdSchema.class, () -> schema);
String portTypeName = env.getProperty(SOAP_ENDPOINTS + endpointName + ".portType.name");
String targetNamespace = env.getProperty(SOAP_ENDPOINTS + endpointName + ".target.namespace");
genericApplicationContext.registerBean(endpointName + "Service",
DefaultWsdl11Definition.class,
() -> {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName(portTypeName);
wsdl11Definition.setLocationUri(locationUri);
wsdl11Definition.setTargetNamespace(targetNamespace);
wsdl11Definition.setSchema(schema);
return wsdl11Definition;
});
}
This utility method first registers a xsd schema instance and then the configured DefaultWsdl11Definition initialized based on previous properties.
If you run the app at this point, you will see confirmation logs that this WsdlDefinition is correctly loaded :
2019-09-23 11:16:06.504 DEBUG 6184 --- [ restartedMain] o.s.w.w.w.p.SuffixBasedPortTypesProvider : Creating port type [{http://sample.com/int/Sample/v1}SamplePortType]
2019-09-23 11:16:06.511 DEBUG 6184 --- [ restartedMain] o.s.w.w.w.p.SuffixBasedPortTypesProvider : Adding operation [searchByName] to port type [{http://sample.com/int/Sample/v1}SamplePortType]
2019-09-23 11:16:06.511 DEBUG 6184 --- [ restartedMain] o.s.w.w.w.p.SuffixBasedPortTypesProvider : Adding operation [searchById] to port type [{http://sample.com/int/Sample/v1}SamplePortType]
2019-09-23 11:16:06.513 DEBUG 6184 --- [ restartedMain] o.s.w.w.wsdl11.provider.Soap11Provider : Creating binding [{http://sample.com/int/Sample/v1}SamplePortTypeSoap11]
2019-09-23 11:16:06.519 DEBUG 6184 --- [ restartedMain] o.s.w.w.wsdl11.provider.Soap11Provider : Creating service [{http://sample.com/int/Sample/v1}SamplePortTypeService]
2019-09-23 11:16:06.520 DEBUG 6184 --- [ restartedMain] o.s.w.w.wsdl11.provider.Soap11Provider : Adding port [SamplePortTypeSoap11] to service [{http://sample.com/int/Sample/v1}SamplePortTypeService]
2019-09-23 11:16:06.529 DEBUG 6184 --- [ restartedMain] yloadRootAnnotationMethodEndpointMapping : Looking for endpoints in application context: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6dea6e9c, started on Mon Sep 23 11:16:02 CEST 2019
2019-09-23 11:16:06.562 DEBUG 6184 --- [ restartedMain] oapActionAnnotationMethodEndpointMapping : Looking for endpoints in application context: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6dea6e9c, started on Mon Sep 23 11:16:02 CEST 2019
2019-09-23 11:16:06.593 DEBUG 6184 --- [ restartedMain] o.s.w.s.e.a.DefaultMethodEndpointAdapter : No MethodArgumentResolvers set, using defaults: [org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor@7c332390, org.springframework.ws.server.endpoint.adapter.method.MessageContextMethodArgumentResolver@4c642359, org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor@31c15cce, org.springframework.ws.server.endpoint.adapter.method.XPathParamMethodArgumentResolver@3c00b80a, org.springframework.ws.soap.server.endpoint.adapter.method.SoapMethodArgumentResolver@594a23b9, org.springframework.ws.soap.server.endpoint.adapter.method.SoapHeaderElementMethodArgumentResolver@3459ad22, org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor@f45b04b, org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor@363c8941, org.springframework.ws.server.endpoint.adapter.method.StaxPayloadMethodArgumentResolver@3ee333d4]
2019-09-23 11:16:06.599 DEBUG 6184 --- [ restartedMain] o.s.w.s.e.a.DefaultMethodEndpointAdapter : No MethodReturnValueHandlers set, using defaults: [org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor@47aaa997, org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor@65cf9ec1, org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor@376b65b8, org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor@10b19d12]
Last log lines indicates that the api is searching for endpoints, which I come with the second goal (original goal on which I stuck a little while).
Goal #2 : Create dynamic single endpoint to configurable webservice's operation mappings:
application-local.properties (github link)
soap.endpoints.Sample.operations.size=1
soap.endpoints.Sample.operations.0.localPart=searchByNameRequest
soap.endpoints.Sample.operations.0.requestType=com.sample._int.sample.v1.SearchByNameRequestType
soap.endpoints.Sample.operations.0.responseType=com.sample._int.sample.v1.GeneralResponseType
These configuration lines indicate which operations must be mapped to the global Endpoint that I want Dispatcher to forward requests to.
Here comes the tricky parts, I added javassist & velocity artifacts to my project. For two reasons: being able to generate automatically an annotated class with the mappings. For that purpose, I created a method template to be loaded (github link):
public javax.xml.transform.dom.DOMSource $localPart(javax.xml.transform.dom.DOMSource request) throws Exception {
org.slf4j.LoggerFactory.getLogger("custom.EndpointMapping").debug("Entered endpoint $localPart : " + request.toString());
return com.sample.controller.GlobalSoapEndpoint.handle(request, "$namespaceUri", "$localPart", "$requestType", "$responseType");
}
Which is loaded by the MockedEndpointGenerator class, that utility class generates a compiled class annotated:
MockedEndpointGenerator.java (github link):
public static Class<?> generateMockEndpoint(MockEndpointDefinition def) {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass(def.getServiceName() + "Endpoint");
ClassFile classFile = cc.getClassFile();
ConstPool constpool = classFile.getConstPool();
classFile.addAttribute(addSingleAnnotation(constpool, Endpoint.class.getName()));
for (MockEndpointDefinition.MockOperation operation : def.getOperations()) {
try {
CtMethod mthd = CtNewMethod.make(templateMethod(operation, def.getNamespace()), cc);
ConstPool mthdConstPool = mthd.getMethodInfo().getConstPool();
// add method annotations
AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
Annotation[] annotations = new Annotation[]{
addAnnotation(mthdConstPool, ResponsePayload.class.getName()),
addAnnotation(mthdConstPool, PayloadRoot.class.getName(),
new String[][]{
new String[]{"namespace", def.getNamespace()},
new String[]{"localPart", operation.getLocalPart()}
}
)
};
annotationsAttribute.setAnnotations(annotations);
mthd.getMethodInfo().addAttribute(annotationsAttribute);
// add method's parameter annotation
ParameterAnnotationsAttribute parameterAttributeInfo = new ParameterAnnotationsAttribute(mthdConstPool, ParameterAnnotationsAttribute.visibleTag);
ConstPool parameterConstPool = parameterAttributeInfo.getConstPool();
Annotation annotation = addAnnotation(parameterConstPool, RequestPayload.class.getName());
Annotation[][] annotations2 = new Annotation[][]{
new Annotation[] {annotation}
};
parameterAttributeInfo.setAnnotations(annotations2);
mthd.getMethodInfo().addAttribute(parameterAttributeInfo);
cc.addMethod(mthd);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
try {
return cc.toClass();
} catch (Exception e) {
throw new IllegalStateException("Custom Endpoint class creation failed", e);
}
}
This Class is then instantiated and registered into Spring's context. Then, you see confirmation of the api in starting logs :
2019-09-24 11:42:34.614 DEBUG 16220 --- [ restartedMain] o.s.w.w.wsdl11.provider.Soap11Provider : Adding port [SamplePortTypeSoap11] to service [{http://sample.com/int/Sample/v1}SamplePortTypeService]
2019-09-24 11:42:34.621 DEBUG 16220 --- [ restartedMain] yloadRootAnnotationMethodEndpointMapping : Looking for endpoints in application context: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5289cd7, started on Tue Sep 24 11:42:28 CEST 2019
2019-09-24 11:42:34.624 DEBUG 16220 --- [ restartedMain] yloadRootAnnotationMethodEndpointMapping : Mapped [{http://sample.com/int/Sample/v1}searchByNameRequest] onto endpoint [public javax.xml.transform.dom.DOMSource SampleEndpoint.searchByNameRequest(javax.xml.transform.dom.DOMSource) throws java.lang.Exception]
2019-09-24 11:42:34.649 DEBUG 16220 --- [ restartedMain] oapActionAnnotationMethodEndpointMapping : Looking for endpoints in application context: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5289cd7, started on Tue Sep 24 11:42:28 CEST 2019
2019-09-24 11:42:34.690 DEBUG 16220 --- [ restartedMain] o.s.w.s.e.a.DefaultMethodEndpointAdapter : No MethodArgumentResolvers set, using defaults: [org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor@1d88a335, org.springframework.ws.server.endpoint.adapter.method.MessageContextMethodArgumentResolver@5c309f36, org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor@349ea8a8, org.springframework.ws.server.endpoint.adapter.method.XPathParamMethodArgumentResolver@72d801b0, org.springframework.ws.soap.server.endpoint.adapter.method.SoapMethodArgumentResolver@2a1185d5, org.springframework.ws.soap.server.endpoint.adapter.method.SoapHeaderElementMethodArgumentResolver@7200768e, org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor@331a4b8e, org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor@1fd09dc, org.springframework.ws.server.endpoint.adapter.method.StaxPayloadMethodArgumentResolver@27e40b1b]
2019-09-24 11:42:34.693 DEBUG 16220 --- [ restartedMain] o.s.w.s.e.a.DefaultMethodEndpointAdapter : No MethodReturnValueHandlers set, using defaults: [org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor@5621ad6f, org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor@3706ca1e, org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor@5edc86cb, org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor@59e7f2d8]
Next step : With the single controller, I can now interrogate a cache manager that holds mocked responses by operation/scenario.
Improvement : making configurable the class to be called by operation (but this purpose is already completed by creating annotated classes with @Endpoint).