Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating SOAP web service guide #2

Closed
wants to merge 12 commits into from
153 changes: 137 additions & 16 deletions README.adoc
@@ -1,24 +1,36 @@
---
tags: []
projects: []
tags: [SOAP]
projects: [spring-ws]
---
:spring_version: current
:spring_boot_version: 1.0.2.RELEASE
:spring_ws_version: 2.0
:Component: http://docs.spring.io/spring/docs/{spring_version}/javadoc-api/org/springframework/stereotype/Component.html
:Controller: http://docs.spring.io/spring/docs/{spring_version}/javadoc-api/org/springframework/stereotype/Controller.html
:DispatcherServlet: http://docs.spring.io/spring/docs/{spring_version}/javadoc-api/org/springframework/web/servlet/DispatcherServlet.html
:SpringApplication: http://docs.spring.io/spring-boot/docs/{spring_boot_version}/api/org/springframework/boot/SpringApplication.html
:ResponseBody: http://docs.spring.io/spring/docs/{spring_version}/javadoc-api/org/springframework/web/bind/annotation/ResponseBody.html
:EnableAutoConfiguration: http://docs.spring.io/spring-boot/docs/{spring_boot_version}/api/org/springframework/boot/autoconfigure/EnableAutoConfiguration.html
:Endpoint: http://docs.spring.io/spring-ws/sites/{spring_ws_version}/apidocs/org/springframework/ws/server/endpoint/annotation/Endpoint.html
:PayloadRoot: http://docs.spring.io/spring-ws/sites/{spring_ws_version}/apidocs/org/springframework/ws/server/endpoint/annotation/PayloadRoot.html
:RequestPayload: http://docs.spring.io/spring-ws/sites/{spring_ws_version}/apidocs/org/springframework/ws/server/endpoint/annotation/RequestPayload.html
:ResponsePayload: http://docs.spring.io/spring-ws/sites/2.0/apidocs/org/springframework/ws/server/endpoint/annotation/ResponsePayload.html
:MessageDispatcherServlet: http://docs.spring.io/spring-ws/sites/2.0/apidocs/org/springframework/ws/transport/http/MessageDispatcherServlet.html
:DefaultMethodEndpointAdapter: http://docs.spring.io/spring-ws/sites/2.0/apidocs/org/springframework/ws/server/endpoint/adapter/DefaultMethodEndpointAdapter.html
:ApplicationContext: http://docs.spring.io/spring/docs/{spring_version}/javadoc-api/org/springframework/web/context/ApplicationContext.html
:DefaultWsdl11Definition: http://docs.spring.io/spring-ws/sites/2.0/apidocs/org/springframework/ws/wsdl/wsdl11/DefaultWsdl11Definition.html
:XsdSchema: http://docs.spring.io/spring-ws/sites/2.0/apidocs/org/springframework/xml/xsd/XsdSchema.html
:toc:
:icons: font
:source-highlighter: prettify
:project_id: draft-gs-template
This guide walks you through the process of creating a Spring application.
:project_id: gs-soap-service
This guide walks you through the process of creating a SOAP-based web service server with Spring.

== What you'll build

You'll build a Spring application.
You will build a server that exposes European countries data using WSDL-based SOAP web service.

In order to simplify the example you will use hardcoded data for couple of countries only: United Kingdom, Spain and Poland.


== What you'll need
Expand All @@ -39,7 +51,7 @@ include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/

`build.gradle`
// AsciiDoc source formatting doesn't support groovy, so using java instead
[source,java]
[source,java,indent=0]
----
include::initial/build.gradle[]
----
Expand All @@ -48,33 +60,119 @@ include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/


[[initial]]
== Create a resource controller
== Add Spring-WS dependency

Project you create will be based on Spring WS. You need to add dependency to Spring WS in your build file. For Maven:

[source,xml,indent=0]
----
include::complete/pom.xml[tags=springws]
----

If you use gradle, make sure to have dependency to `spring-ws-core` added:
[source,java,indent=0]
----
include::complete/build.gradle[tags=dependencies]
----

== Create XSD

Web service domain is defined in XSD file that is later on wrapped with WSDL. Create XSD file describing our domain, which is a service exposing one method returning basic information about requested country: name, population, capital and currency.

`src/main/resources/countries.xsd`
[source,xml,indent=0]
----
include::complete/src/main/resources/countries.xsd[]
----

== Generate domain classes based on a XSD

XSD is just the beginning. The next step is to generate Java classes from it. The right approach is go generate classes during build time using Maven or Gradle plugin.

Plugin configuration for Maven:

[source,xml,indent=0]
----
include::complete/pom.xml[tags=xsd]
----

Generated classes are placed in `target/generated-sources/jaxb/` directory.

To do the same with gradle, first you need to configure JAXB in your build file:

[source,java,indent=0]
----
include::complete/build.gradle[tags=jaxb]
----

Next step is to add task `genJaxb` used by gradle to generate Java classes:

[source,java,indent=0]
----
include::complete/build.gradle[tags=xsd]
----

As gradle does not have a JAXB plugin (yet), it involves an ant task, which makes it a bit more complex than in maven.

In both cases, the JAXB domain object generation process has been wired into the build tool’s lifecycle so there are no extra steps to run.

== Create country repository

In order to provide data to web service, create country repository. In this guide you create dummy country repository implementation with hardcoded data.

[source,java,indent=0]
----
include::complete/src/main/java/hello/CountryRepository.java[]
----

== Create country service endpoint

To create service endpoint, you have to create POJO class and annotate it with Spring WS annotations.

[source,java,indent=0]
----
include::complete/src/main/java/hello/CountryEndpoint.java[]
----

When Spring WS receives SOAP message it searches all defined endpoints for methods matching namespace and localPart. An endpoint is created typically by annotating class with the {Endpoint}[`@Endpoint`] annotation.

In order to route incoming XML message to correct method use {PayloadRoot}[`@PayloadRoot`] annotation that indicates that all messages with namespace `http://spring.io/guides/gs-soap-service` and containing local name `getCountryRequest` will be routed to this method.

{RequestPayload}[`@RequestPayload`] indicates that message will be mapped to `request` parameter.

Analogously, {ResponsePayload}[`@ResponsePayload`] annotation makes Spring WS map returned value to response payload.

== Configure web service components

There are two efficient ways of configuring Spring WS beans: using XML namespaces or using Java config. In this tutorial you will create beans using plain Java.

Create a new controller for your Spring application:
Create a new class with Spring WS related beans configuration:

`src/main/java/hello/GreetingController.java`
[source,java]
[source,java,indent=0]
----
include::complete/src/main/java/hello/GreetingController.java[]
include::complete/src/main/java/hello/WebServiceConfig.java[]
----

NOTE: The above example does not specify `GET` vs. `PUT`, `POST`, and so forth, because `@RequestMapping` maps all HTTP operations by default. Use `@RequestMapping(method=GET)` to narrow this mapping.
* Spring WS uses different servlet type for handling SOAP messages: {MessageDispatcherServlet}[`MessageDispatcherServlet`]. It is important to inject and set {ApplicationContext}[`ApplicationContext`] to {MessageDispatcherServlet}[`MessageDispatcherServlet`]. Without that, Spring WS will not detect Spring Beans automatically.
* {DefaultMethodEndpointAdapter}[`DefaultMethodEndpointAdapter`] configures annotation driven Spring WS programming model - makes possible to use for example {Endpoint}[`@Endpoint`] annotation
* define bean {DefaultWsdl11Definition}[`DefaultWsdl11Definition`] for exposing WSDL using {XsdSchema}[`XsdSchema`]

It's important to notice that you need to specify bean names for {MessageDispatcherServlet}[`MessageDispatcherServlet`] and {DefaultWsdl11Definition}[`DefaultWsdl11Definition`]. Bean names determine the URL under which web service and generated WSDL file is available. In this case, WSDL will be available under `http://<host>:<port>/ws/countries.wsdl`

== Make the application executable

Although it is possible to package this service as a traditional link:/understanding/WAR[WAR] file for deployment to an external application server, the simpler approach demonstrated below creates a standalone application. You package everything in a single, executable JAR file, driven by a good old Java `main()` method. Along the way, you use Spring's support for embedding the link:/understanding/Tomcat[Tomcat] servlet container as the HTTP runtime, instead of deploying to an external instance.


`src/main/java/hello/Application.java`
[source,java]
[source,java,indent=0]
----
include::complete/src/main/java/hello/Application.java[]
----

The `main()` method defers to the {SpringApplication}[`SpringApplication`] helper class, providing `Application.class` as an argument to its `run()` method. This tells Spring to read the annotation metadata from `Application` and to manage it as a component in the link:/understanding/application-context[Spring application context].

The `@ComponentScan` annotation tells Spring to search recursively through the `hello` package and its children for classes marked directly or indirectly with Spring's {Component}[`@Component`] annotation. This directive ensures that Spring finds and registers the `GreetingController`, because it is marked with `@Controller`, which in turn is a kind of `@Component` annotation.
The `@ComponentScan` annotation tells Spring to search recursively through the `hello` package and its children for classes marked directly or indirectly with Spring's {Component}[`@Component`] annotation. This directive ensures that Spring finds and registers the `CountryRepository` and `CountriesEndpoint`, because they are marked marked with `@Component` and `@Endpoint`, which in turn is a kind of `@Component` annotation.

The {EnableAutoConfiguration}[`@EnableAutoConfiguration`] annotation switches on reasonable default behaviors based on the content of your classpath. For example, because the application depends on the embeddable version of Tomcat (tomcat-embed-core.jar), a Tomcat server is set up and configured with reasonable defaults on your behalf. And because the application also depends on Spring MVC (spring-webmvc.jar), a Spring MVC {DispatcherServlet}[`DispatcherServlet`] is configured and registered for you — no `web.xml` necessary! Auto-configuration is a powerful, flexible mechanism. See the {EnableAutoConfiguration}[API documentation] for further details.

Expand All @@ -90,12 +188,35 @@ Logging output is displayed. The service should be up and running within a few s

== Test the application

Now that the application is running, you can test it.
Now that the application is running, you can test it. Create a file `request.xml` containing example SOAP request:

[source,xml,indent=0]
----
include::test/request.xml[]
----

Execute from command line `curl --header "content-type: text/xml" -d @request.xml "http://localhost:8080/ws/"`. As a result you should see the response:

[source,xml]
----
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns3:getCountryResponse xmlns:ns3="http://spring.io/guides/gs-soap-service">
<country>
<name>Spain</name>
<population>46704314</population>
<capital>Madrid</capital>
<currency>EUR</currency>
</country>
</ns3:getCountryResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
----

== Summary

Congratulations! You've just developed a Spring application!
Congratulations! You've developed Spring WS based SOAP service! All without single line of XML configuration!



60 changes: 55 additions & 5 deletions complete/build.gradle
Expand Up @@ -13,19 +13,69 @@ apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
baseName = 'draft-gs-template'
version = '0.1.0'
}

repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-milestone" }
}

// tag::xsd[]
task genJaxb {
ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
ext.classesDir = "${buildDir}/classes/jaxb"
ext.schema = "src/main/resources/countries.xsd"

outputs.dir classesDir

doLast() {
project.ant {
taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
classpath: configurations.jaxb.asPath
mkdir(dir: sourcesDir)
mkdir(dir: classesDir)

xjc(destdir: sourcesDir, schema: schema) {
arg(value: "-wsdl")
produces(dir: sourcesDir, includes: "**/*.java")
}

javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true,
debugLevel: "lines,vars,source",
classpath: configurations.jaxb.asPath) {
src(path: sourcesDir)
include(name: "**/*.java")
include(name: "*.java")
}

copy(todir: classesDir) {
fileset(dir: sourcesDir, erroronmissingdir: false) {
exclude(name: "**/*.java")
}
}
}
}
}
// end::xsd[]

// tag::jaxb[]
configurations {
jaxb
}

jar {
baseName = 'gs-soap-service'
version = '0.1.0'
from genJaxb.classesDir
}

// tag::dependencies[]
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.ws:spring-ws-core:2.1.4.RELEASE")
jaxb("com.sun.xml.bind:jaxb-xjc:2.2.4-1")
compile(files(genJaxb.classesDir).builtBy(genJaxb))
}
// end::dependencies[]
// end::jaxb[]

task wrapper(type: Wrapper) {
gradleVersion = '1.11'
Expand Down
28 changes: 27 additions & 1 deletion complete/pom.xml
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>

<groupId>org.springframework</groupId>
<artifactId>draft-gs-template</artifactId>
<artifactId>gs-soap-service</artifactId>
<version>0.1.0</version>

<parent>
Expand All @@ -18,6 +18,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- tag::springws[] -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<!-- end::springws[] -->
</dependencies>

<properties>
Expand All @@ -36,6 +43,25 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<!-- tag::xsd[] -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory>${project.basedir}/src/main/resources/</schemaDirectory>
</configuration>
</plugin>
<!-- end::xsd[] -->
</plugins>
</build>

Expand Down
30 changes: 30 additions & 0 deletions complete/src/main/java/hello/CountryEndpoint.java
@@ -0,0 +1,30 @@
package hello;

import io.spring.guides.gs_soap_service.GetCountryRequest;
import io.spring.guides.gs_soap_service.GetCountryResponse;
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.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://spring.io/guides/gs-soap-service";

private CountryRepository countryRepository;

@Autowired
public CountryEndpoint(CountryRepository countryRepository) {
this.countryRepository = countryRepository;
}

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
@ResponsePayload
public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
GetCountryResponse response = new GetCountryResponse();
response.setCountry(countryRepository.findCountry(request.getName()));

return response;
}
}