spring-ws icon indicating copy to clipboard operation
spring-ws copied to clipboard

HTTP Interface support

Open ciscoo opened this issue 8 months ago • 4 comments

It would be nice if Spring HTTP interface were supported by Spring-WS in some way.

From Apache CXF, it provides a proxy which can be used to create a declarative client. For example:

@Configuration(proxyBeanMethods = false)
public class CxfConfiguration {

    @Bean
    public CalculatorSoap calculatorSoap() {
        JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
        factoryBean.setServiceClass(CalculatorSoap.class);
        factoryBean.setAddress("http://www.dneonline.com/calculator.asmx");
        return (CalculatorSoap) factoryBean.create();
    }

}

Then using it is straight forward:

@Bean
public CommandLineRunner doRequests(CalculatorSoap calculatorSoap) {
    return ignored -> {
        int result = calculatorSoap.add(1, 1);
        logger.info("Result from CXF client proxy: {}", result);
    };
}

With Spring-WS, there is quite a bit of ceremony just to do the same:

@Configuration(proxyBeanMethods = false)
public class SpringWsConfiguration {

    @Bean
    public SaajSoapMessageFactory saajSoapMessageFactory() {
        return new SaajSoapMessageFactory();
    }

    @Bean
    public HttpComponents5MessageSender httpComponents5MessageSender() {
        return new HttpComponents5MessageSender();
    }

    @Bean
    public Jaxb2Marshaller calculatorJaxb2Marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setPackagesToScan(CalculatorSoap.class.getPackageName());
        return marshaller;
    }

    @Bean
    public WebServiceTemplate calculatorTemplate(SaajSoapMessageFactory messageFactory, HttpComponents5MessageSender sender, Jaxb2Marshaller calculatorJaxb2Marshaller) {
        WebServiceTemplate template = new WebServiceTemplate(messageFactory);
        template.setMessageSender(sender);
        template.setDefaultUri("http://www.dneonline.com/calculator.asmx");
        template.setMarshaller(calculatorJaxb2Marshaller);
        template.setUnmarshaller(calculatorJaxb2Marshaller);
        return template;
    }

}
@Bean
public CommandLineRunner doRequests(CalculatorSoap calculatorSoap, WebServiceTemplate calculatorTemplate) {
    return ignored -> {
        Add addRequest = new Add();
        addRequest.setIntA(1);
        addRequest.setIntB(1);
        AddResponse response = (AddResponse) calculatorTemplate.marshalSendAndReceive(addRequest, new SoapActionCallback("http://tempuri.org/Add"));

        logger.info("Response from Spring-WS: {}", response.getAddResult());
    };
}

Granted, CXF performs a lot of bootstrapping behind the scenes that is not visible without enabling debug logs.

But I think it would be nice of Spring-WS provided some sort of integration or bootstrapping of HTTP interfaces that accomplishes something similar. For example:

@PackageScan("org.tempuri")
public interface CalculatorClient {
    
    @SoapAction("http://tempuri.org/Add")
    int add(int a, int b);

}

This is overly simplified, but hope it gets the idea across.

Attached sample project showcasing the two client examples above

demo.zip

ciscoo avatar Mar 26 '25 02:03 ciscoo

Thanks for the suggestion. Adding support for Spring Framework's HTTP Interface is a neat idea and that would "hide" the WebServiceTemplate behind the scenes.

However, that's not going to trim down the configuration required to set it up. It looks like you're asking two different things in this issue as your last snippet isn't going to fix the configuration bits. Can you please clarify so that we can triage this one accordingly?

snicoll avatar Mar 26 '25 07:03 snicoll

You're right, there are two things here. For this issue, I'll focus just on the interface part.

The other part is https://github.com/spring-projects/spring-ws/issues/1511

So for this issue, it would be nice if the provided annotations, such as @SoapAction would be supported by Spring-WS through some sort of HttpServiceArgumentResolver provided by Spring-WS.

And of course, introduce any additional annotations that would be needed.

ciscoo avatar Mar 26 '25 16:03 ciscoo

The other part is https://github.com/spring-projects/spring-ws/issues/1511

I don't see how a HttpExchangeAdapter is going to trim down the necessary configuration. We can create another issue but the one that you've created doesn't seem to address it. Having an HttpExchangeAdapter looks like something we'd do to implement this issue.

So for this issue, it would be nice if the provided annotations, such as @SoapAction would be supported by Spring-WS through some sort of HttpServiceArgumentResolver provided by Spring-WS.

I don't understand the example in your original description. We're contract first so how would a and b translates to a Add request object? Also, Spring-WS has many other transports.

snicoll avatar Mar 26 '25 16:03 snicoll

We're contract first so how would a and b translates to a Add request object?

I think by supporting the various annotations provided by Jakarta, contract first can be achieved. Focusing on just the add operation:

import jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebResult;
import jakarta.jws.WebService;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import jakarta.xml.ws.RequestWrapper;
import jakarta.xml.ws.ResponseWrapper;

@WebService(targetNamespace = "http://tempuri.org/", name = "CalculatorSoap")
@XmlSeeAlso({ObjectFactory.class})
public interface CalculatorSoap {

    @WebMethod(operationName = "Add", action = "http://tempuri.org/Add")
    @RequestWrapper(localName = "Add", targetNamespace = "http://tempuri.org/", className = "org.tempuri.Add")
    @ResponseWrapper(localName = "AddResponse", targetNamespace = "http://tempuri.org/", className = "org.tempuri.AddResponse")
    @WebResult(name = "AddResult", targetNamespace = "http://tempuri.org/")
    public int add(

        @WebParam(name = "intA", targetNamespace = "http://tempuri.org/")
        int intA,
        @WebParam(name = "intB", targetNamespace = "http://tempuri.org/")
        int intB
    );
}

The class itself is generated by wsdl2java which does not have any specifics to Apache CXF, so this achieves the contract first approach for Spring-WS I think.

On the Marshaller, specifically Jaxb2Marshaller, one can call setSchema/setSchemas to point to the .xsd further driving validation or contract first.

So, if Spring-WS can provide some support for that, then this may be possible:

@Bean
public CalculatorSoap calculatorSoap(RestClient.Builder builder) {
    WebServiceTemplateAdapter webServiceTemplateAdapter = WebServiceTemplateAdapter.create(webServiceTemplate);
    HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builderFor(webServiceTemplateAdapter).build();
    return proxyFactory.createClient(CalculatorSoap.class);
}

ciscoo avatar Mar 26 '25 18:03 ciscoo

I think by supporting the various annotations provided by Jakarta, contract first can be achieved

That's from JAX-WS. Spring-WS doesn't support it and we have no intention to do that. It's a bit similar to the stance of Spring MVC with regards to JAX-RS.

Back to your original description, I am afraid I still think it's a mixed bag of things. I suppose Spring Boot could be improved to configure additional bean so that you don't have to. Right now we provide a builder for the WebServiceTemplate side of things.

Regarding the HTTP interface support itself, I guess there are things we could take into consideration but I'd have to get a better insight on the use cases.

snicoll avatar Apr 09 '25 13:04 snicoll

At least for mine, use case is consuming legacy SOAP/Web services from a modern (REST) API through a declarative manner.

Currently, we use Apache CXF to orchestrate everything. Since our application's are Spring based, we'd like to use Spring approach/libraries rather than another library outside the Spring ecosystem.

WebServiceTemplate is an option yes, but wanted to see if the HTTP interface functionality can be used with Spring-WS to provide a more declarative approach to consume Web services over the I guess 'lower' level WebServiceTemplate.

ciscoo avatar Apr 09 '25 13:04 ciscoo

wanted to see if the HTTP interface functionality can be used with Spring-WS to provide a more declarative approach to consume Web services

Http Interface won't solve the fact that Spring-WS is message in/message out. Adding support for it would lead to something like

interface MyService {

    @SoapExchange
    OutputA getA(InputA inputA);

    @SoapExchange
    OutputB getB(InputB inputB);

    // ...

}

Looking at the code you'd have to write with WebServiceTemplate it won't bring you much.

What you're asking really is to add support for composing the input using method signature and we're not going to do that.

snicoll avatar Apr 17 '25 13:04 snicoll