3

Is it possible to easily convert the REST WS example (http://spring.io/guides/gs/rest-service/) to a SOAP WS? What needs to be done to convert that example?

João Dias
  • 16,277
  • 6
  • 33
  • 45
Jerry
  • 1,127
  • 2
  • 17
  • 35
  • Did you see this one: http://stackoverflow.com/questions/21115205/spring-boot-with-spring-ws-soap-endpoint-not-accessable? – Dave Syer Jan 24 '14 at 08:52

1 Answers1

0

Yes, it is easy possible to convert this (simple) example to SOAP.

Migrate

To get from here to (something similar like) this (guide|repository), we need to:

  1. adjust build.gradle to:

    plugins {
      id 'org.springframework.boot' version '2.6.0' // update to latest
      id 'io.spring.dependency-management' version '1.0.11.RELEASE'
      id 'java'
    }
    
    group = 'com.stackoverflow' // re-brand ;)
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '1.8'
    
    repositories {
      mavenCentral()
    }
    
    // complete chunk from https://github.com/spring-guides/gs-producing-web-service/tree/main/complete/build.gradle
    sourceSets {
      main {
        java {
          srcDir 'src/main/java'
          srcDir 'build/generated-sources/jaxb'
        }
      }
    }
    
    task genJaxb {
      ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
      ext.schema = "src/main/resources/greeting.xsd" // replace countries.xsd from soap stackoverflow with our xsd
    
      outputs.dir sourcesDir
    
      doLast() {
        project.ant {
          taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
              classpath: configurations.jaxb.asPath
          mkdir(dir: sourcesDir)
    
          xjc(destdir: sourcesDir, schema: schema) {
            arg(value: "-wsdl")
            produces(dir: sourcesDir, includes: "**/*.java")
          }
        }
      }
    }
    
    compileJava.dependsOn genJaxb
    
    configurations {
      jaxb
    }
    
    dependencies {
      implementation 'org.springframework.boot:spring-boot-starter-web'
      testImplementation('org.springframework.boot:spring-boot-starter-test')
     // addtional dependencies
      implementation 'org.springframework.boot:spring-boot-starter-web-services'
      implementation 'wsdl4j:wsdl4j'
      jaxb('org.glassfish.jaxb:jaxb-xjc')
    }
    // end of chunk
    test {
      useJUnitPlatform()
    }
    

    , which is: original build.gradle + instructions for jaxb + additional dependencies.

  2. settings.gradle we change (correctly but not obligatory) to:

    rootProject.name = 'soap-service'
    
  3. We introduce src/main/resources/greeting.xsd with following content:

    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://stackoverflow.com/greeting-soap/"
                targetNamespace="http://stackoverflow.com/greeting-soap/" elementFormDefault="qualified">
    
      <xs:element name="getGreetingRequest">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="name" type="xs:string" minOccurs="0" nillable="true" default="from XSD" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    
      <xs:element name="getGreetingResponse">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="greeting" type="tns:greeting"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    
      <xs:complexType name="greeting">
        <xs:sequence>
          <xs:element name="id" type="xs:long"/>
          <xs:element name="content" type="xs:string"/>
        </xs:sequence>
      </xs:complexType>
    </xs:schema>
    

    , trying to mimic the REST endpoint and model as SOAP.

  4. (Best) we delete all (main & test) sources in:

    • src/main/java/com/example/restservice
    • src/test/java/com/example/restservice

    , to replace them.

  5. This will be our SpringBootApplication + Configuration:

    package com.stackoverflow.soapservice;       
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletRegistrationBean;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.ws.config.annotation.EnableWs;
    import org.springframework.ws.transport.http.MessageDispatcherServlet;
    import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
    import org.springframework.xml.xsd.SimpleXsdSchema;
    import org.springframework.xml.xsd.XsdSchema;
    @EnableWs
    @SpringBootApplication
    public class SOAPServiceApplication {
    
      public static final String NAMESPACE_URI = "http://stackoverflow.com/greeting-soap/";
    
      public static void main(String[] args) {
        SpringApplication.run(SOAPServiceApplication.class, args);
      }
    
      @Bean
      public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setTransformWsdlLocations(true);
        return new ServletRegistrationBean<>(servlet, "/ws/*");
      }
    
      @Bean(name = "greetings")
      public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema greetingSchema) {
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("GreetingPort");
        wsdl11Definition.setLocationUri("/ws");
        wsdl11Definition.setTargetNamespace(NAMESPACE_URI);
        wsdl11Definition.setSchema(greetingSchema);
        return wsdl11Definition;
      }
    
      @Bean
      public XsdSchema greetingsSchema() {
        return new SimpleXsdSchema(new ClassPathResource("greeting.xsd"));
      }
    }
    
  6. The endpoint class:

     package com.stackoverflow.soapservice;
    
     import com.stackoverflow.greeting_soap.GetGreetingRequest;
     import com.stackoverflow.greeting_soap.GetGreetingResponse;
     import com.stackoverflow.greeting_soap.Greeting;
     import static com.stackoverflow.soapservice.SOAPServiceApplication.NAMESPACE_URI;
     import java.util.concurrent.atomic.AtomicLong;
     import org.springframework.ws.server.endpoint.annotation.Endpoint;
     import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
     import org.springframework.ws.server.endpoint.annotation.RequestPayload;
     import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
    
     @Endpoint
     public class GreetingEndpoint {
    
       private static final String HELLO_TPL = "Hello, %s!";
       private static final AtomicLong COUNTER = new AtomicLong();
    
       @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getGreetingRequest")
       @ResponsePayload
       public GetGreetingResponse sayHello(@RequestPayload GetGreetingRequest request) {
         GetGreetingResponse response = new GetGreetingResponse();
    
         Greeting greeting = new Greeting();
         greeting.setContent(
           String.format(
            HELLO_TPL, request.getName() == null ? "World" : request.getName()
           )
         );
         greeting.setId(COUNTER.incrementAndGet());
    
         response.setGreeting(greeting);
         return response;
       }
     }
    
  7. And a test:

    package com.stackoverflow.soapservice;
    import com.stackoverflow.greeting_soap.GetGreetingRequest;
    import com.stackoverflow.greeting_soap.GetGreetingResponse;
    import java.util.HashMap;
    import javax.xml.bind.JAXBElement;
    import javax.xml.namespace.QName;
    import static org.assertj.core.api.Assertions.assertThat;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.boot.test.context.TestConfiguration;
    import org.springframework.boot.web.server.LocalServerPort;
    import org.springframework.context.annotation.Bean;
    import org.springframework.oxm.jaxb.Jaxb2Marshaller;
    import org.springframework.ws.client.core.WebServiceTemplate;
    
    @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
    class GreetingEndpointTest {
    
     private static final QName NS_NAME_QNAME = QName.valueOf(
       String.format("{%s}%s", SOAPServiceApplication.NAMESPACE_URI, "name")
     );
    
     @TestConfiguration
     static class TestConfig {
    
      @Bean
      public Jaxb2Marshaller marshaller() {
       final Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
       marshaller.setPackagesToScan("com.stackoverflow.greeting_soap");
       marshaller.setMarshallerProperties(new HashMap<String, Object>() {
        {
         put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
        }
       });
       return marshaller;
      }
    
      @Bean
      public WebServiceTemplate wsTemplate(Jaxb2Marshaller marshaller) {
       return new WebServiceTemplate(marshaller);
      }
     }
    
     @LocalServerPort
     private int port = 0;
    
     @Autowired
     private WebServiceTemplate ws;
    
     @Test
     public void testSendAndReceive() {
      // Given:
      final String NAME = "Test";
      GetGreetingRequest request = new GetGreetingRequest();
      JAXBElement<String> elem = new JAXBElement<>(NS_NAME_QNAME, String.class, NAME);
      request.setName(elem);
    
      // When:
      GetGreetingResponse result = (GetGreetingResponse) ws.marshalSendAndReceive("http://localhost:"
        + port + "/ws", request);
    
      // Then:
      assertThat(result != null);
      assertThat(result.getGreeting() != null);
      assertThat(result.getGreeting().getContent() != null);
      assertThat(result.getGreeting().getContent().startsWith("Hello")
        && result.getGreeting().getContent().contains(NAME));
     }
    
     // ... see at github
    }
    

Build

With JDK 1.8:

export JAVA_HOME=/path/to/jdk/8

For higher JDK versions up to 11, we'd have to adjust dependencies. How to shift this sample up-to-date (jdk-17-LTS)? ... "Null Problemo!"

Clean, test and bootRun:

./gradlew clean test bootRun

When first time imported into our IDE, a "priming build", respectively ./gradlew genJaxb will be useful (/generate the jaxb classes).

Use

  • get the wsdl:

    curl http://localhost:8080/ws/greetings.wsdl
    

    gets us (unformatted):

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
      xmlns:sch="http://stackoverflow.com/greeting-soap/" 
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
      xmlns:tns="http://stackoverflow.com/greeting-soap/" 
      targetNamespace="http://stackoverflow.com/greeting-soap/">
      <wsdl:types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" 
          targetNamespace="http://stackoverflow.com/greeting-soap/">
          <xs:element name="getGreetingRequest">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="name" type="xs:string" minOccurs="0" nillable="true" default="from XSD" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
          <xs:element name="getGreetingResponse">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="greeting" type="tns:greeting"/>
              </xs:sequence>
            </xs:complexType>
          </xs:element>
          <xs:complexType name="greeting">
            <xs:sequence>
              <xs:element name="id" type="xs:long"/>
              <xs:element name="content" type="xs:string"/>
            </xs:sequence>
          </xs:complexType>
        </xs:schema>
      </wsdl:types>
      <wsdl:message name="getGreetingResponse">
        <wsdl:part element="tns:getGreetingResponse" name="getGreetingResponse"></wsdl:part>
      </wsdl:message>
      <wsdl:message name="getGreetingRequest">
        <wsdl:part element="tns:getGreetingRequest" name="getGreetingRequest"></wsdl:part>
      </wsdl:message>
      <wsdl:portType name="GreetingPort">
        <wsdl:operation name="getGreeting">
          <wsdl:input message="tns:getGreetingRequest" name="getGreetingRequest"></wsdl:input>
          <wsdl:output message="tns:getGreetingResponse" name="getGreetingResponse"></wsdl:output>
        </wsdl:operation>
      </wsdl:portType>
      <wsdl:binding name="GreetingPortSoap11" type="tns:GreetingPort">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="getGreeting">
          <soap:operation soapAction=""/>
          <wsdl:input name="getGreetingRequest">
            <soap:body use="literal"/>
          </wsdl:input>
          <wsdl:output name="getGreetingResponse">
            <soap:body use="literal"/>
          </wsdl:output>
        </wsdl:operation>
      </wsdl:binding>
      <wsdl:service name="GreetingPortService">
        <wsdl:port binding="tns:GreetingPortSoap11" name="GreetingPortSoap11">
          <soap:address location="http://localhost:8080/ws"/>
        </wsdl:port>
      </wsdl:service>
    </wsdl:definitions>
    
  • send a request:

    curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws > output.xml
    

    with this ./request.xml:

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
      xmlns:gs="http://stackoverflow.com/greeting-soap/">
        <soapenv:Header/>
        <soapenv:Body>
          <gs:getGreetingRequest>
              <gs:name>John</gs:name>
          </gs:getGreetingRequest>
        </soapenv:Body>
    </soapenv:Envelope>
    

    we get the following ./output.xml (unformatted):

    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
      <SOAP-ENV:Header/>
      <SOAP-ENV:Body>
        <ns2:getGreetingResponse xmlns:ns2="http://stackoverflow.com/greeting-soap/">
          <ns2:greeting>
            <ns2:id>1</ns2:id>
            <ns2:content>Hello, John!</ns2:content>
          </ns2:greeting>
        </ns2:getGreetingResponse>
      </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
    

Everything combined in a github repository:

xerx593
  • 12,237
  • 5
  • 33
  • 64