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).