SOAP webservice with Spring-boot

While playing around with spring-boot starters, I get in contact with simple configuration of webservices. This is just an example with a soap offering spring-boot server and a simple soap client that is baked from provided wsdl.

first the server:

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── eu
    │   │       └── christophburmeister
    │   │           └── playground
    │   │               └── springboot
    │   │                   ├── ApplicationConfig.java
    │   │                   ├── Application.java
    │   │                   ├── endpoints
    │   │                   │   ├── GetCityByCodeEndpoint.java
    │   │                   │   └── GetCountryByCityNameEndpoint.java
    │   │                   ├── persistence
    │   │                   │   └── CityRepository.java
    │   │                   └── services
    │   │                       ├── CitiesService.java
    │   │                       └── ICitiesService.java
    │   └── resources
    │       ├── application.properties
    │       ├── log4j2.xml
    │       └── META-INF
    │           └── schemas
    │               └── cities.xsd
    └── test
        ├── java
        └── resources

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.2.5.RELEASE</version>
	</parent>
	<groupId>eu.christophburmeister.playground</groupId>
	<artifactId>springboot</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<generated.sources.dir>${project.basedir}/target/generated-sources</generated.sources.dir>
	</properties>

	<build>
		<plugins>
			<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>
						<phase>generate-sources</phase>
					</execution>
				</executions>
				<configuration>
					<schemaDirectory>${project.basedir}/src/main/resources/META-INF/schemas</schemaDirectory>
					<outputDirectory>${generated.sources.dir}</outputDirectory>
					<clearOutputDir>false</clearOutputDir>
				</configuration>
			</plugin>

			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>build-helper-maven-plugin</artifactId>
				<version>1.7</version>
				<executions>
					<execution>
						<id>add-source</id>
						<phase>generate-sources</phase>
						<goals>
							<goal>add-source</goal>
						</goals>
						<configuration>
							<sources>
								<source>${generated.sources.dir}</source>
							</sources>
						</configuration>
					</execution>
				</executions>
			</plugin>

			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
			<exclusions>
				<!-- we want to rely on external log4j2 -->
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-ws</artifactId>
		</dependency>
		<dependency>
			<groupId>jaxen</groupId>
			<artifactId>jaxen</artifactId>
		</dependency>
		<dependency>
			<groupId>org.jdom</groupId>
			<artifactId>jdom2</artifactId>
		</dependency>
		<dependency>
			<groupId>wsdl4j</groupId>
			<artifactId>wsdl4j</artifactId>
		</dependency>
		<!-- logging -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>

	</dependencies>

</project>

Basically the server will provide one service with two operations. One service will be mapper for city codes to city and the second from city name to country.

The schema is defined in src/main/resources/META-INF/schemas :

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:playground="http://christophburmeister.eu/playground/schema/cities"
	targetNamespace="http://christophburmeister.eu/playground/schema/cities"
	elementFormDefault="qualified">

	<xs:complexType name="City">
		<xs:sequence>
			<xs:element name="country" type="xs:string" />
			<xs:element name="code" type="xs:int" />
			<xs:element name="name" type="xs:string" />
			<xs:element name="population" type="xs:int" />
			<xs:element name="founded" type="xs:int" />
		</xs:sequence>
	</xs:complexType>

	<xs:element name="getCityByCodeRequest">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="code" type="xs:int" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getCityByCodeResponse">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="city" type="playground:City" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getCountryByCityNameRequest">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="cityName" type="xs:string" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getCountryByCityNameResponse">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="country" type="xs:string" />
			</xs:sequence>
		</xs:complexType>
	</xs:element> 	

</xs:schema>

This schema will be compiled while Maven build to schema classes that we will use.

To keep this example simple, we use a static list as persistence in CityRepository class:

package eu.christophburmeister.playground.springboot.persistence;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

import eu.christophburmeister.playground.schema.cities.City;

@Component
public class CityRepository {
	
	static final Logger logger = LogManager.getLogger(CityRepository.class.getName());
	
	private static final List<City> cities = new ArrayList<City>();

	@PostConstruct
	public void initData() {
		City neuruppin = new City();
		neuruppin.setName("Neuruppin");	
		neuruppin.setCountry("Germany");
		neuruppin.setCode(16816);	
		neuruppin.setPopulation(30000);
		neuruppin.setFounded(1256);
		cities.add(neuruppin);
		
		City paris = new City();
		paris.setName("Paris");	
		paris.setCountry("France");
		paris.setCode(75001);	
		paris.setPopulation(2250000);
		paris.setFounded(1256);
		cities.add(paris);
	}

	public City findCity(int code) {
		logger.info("searching city for: " + code);
		City result = null;
		for (City city : cities) {
			if (code == city.getCode()) {
				result = city;
			}
		}
		return result;
	}

	public String findCountry(String cityName) {
		logger.info("searching country for: " + cityName);
		String result = null;
		for (City city : cities) {
			if (cityName.equals(city.getName())) {
				result = city.getCountry();
			}
		}
		return result;
	}
}

This repo provides two methods that will be exposed via endpoints:

package eu.christophburmeister.playground.springboot.endpoints;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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;

import eu.christophburmeister.playground.schema.cities.City;
import eu.christophburmeister.playground.schema.cities.GetCityByCodeRequest;
import eu.christophburmeister.playground.schema.cities.GetCityByCodeResponse;
import eu.christophburmeister.playground.springboot.persistence.CityRepository;
import eu.christophburmeister.playground.springboot.services.CitiesService;

@Endpoint
public class GetCityByCodeEndpoint {

	static final Logger logger = LogManager.getLogger(GetCityByCodeEndpoint.class.getName());

	CityRepository cityRepository;
	
	@Autowired
	public GetCityByCodeEndpoint(CityRepository cityRepository) {
		this.cityRepository = cityRepository;
	}
	
	@PayloadRoot(namespace = "http://christophburmeister.eu/playground/schema/cities", localPart = "getCityByCodeRequest")
	@ResponsePayload
	public GetCityByCodeResponse getCityByCode(@RequestPayload GetCityByCodeRequest request) {
		logger.info("entering endpoint");
		GetCityByCodeResponse response = new GetCityByCodeResponse();
		City city = cityRepository.findCity(request.getCode());
		response.setCity(city);
		return response;
	}

}
package eu.christophburmeister.playground.springboot.endpoints;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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;

import eu.christophburmeister.playground.schema.cities.GetCountryByCityNameRequest;
import eu.christophburmeister.playground.schema.cities.GetCountryByCityNameResponse;
import eu.christophburmeister.playground.springboot.persistence.CityRepository;

@Endpoint
public class GetCountryByCityNameEndpoint {

	static final Logger logger = LogManager.getLogger(GetCountryByCityNameEndpoint.class.getName());

	CityRepository cityRepository;
	
	@Autowired
	public GetCountryByCityNameEndpoint(CityRepository cityRepository) {
		this.cityRepository = cityRepository;
	}
	
	@PayloadRoot(namespace = "http://christophburmeister.eu/playground/schema/cities", localPart = "getCountryByCityNameRequest")
	@ResponsePayload
	public GetCountryByCityNameResponse getCityByCode(@RequestPayload GetCountryByCityNameRequest request) {
		logger.info("entering endpoint");
		GetCountryByCityNameResponse response = new GetCountryByCityNameResponse();
		String country = cityRepository.findCountry(request.getCityName());
		response.setCountry(country);
		return response;
	}

}

The CitiesService implements following interface:

package eu.christophburmeister.playground.springboot.services;

import eu.christophburmeister.playground.schema.cities.City;

public interface ICitiesService {
	
	City getCityByCode(int code);
	String getCountryByCityName(String cityName);
	
}
package eu.christophburmeister.playground.springboot.services;

import eu.christophburmeister.playground.schema.cities.City;
import eu.christophburmeister.playground.springboot.persistence.CityRepository;

public class CitiesService implements ICitiesService{

	CityRepository cityRepository;
	
	public City getCityByCode(int code) {
		cityRepository = new CityRepository();
		City city = cityRepository.findCity(code);
		return city;
	}

	public String getCountryByCityName(String cityName) {
		cityRepository = new CityRepository();
		String country = cityRepository.findCountry(cityName);
		return country;
	}

}

The ApplicationConfig implements Spring’s WSConfigureAdapter where the schema file with requests, responses and complex datatypes is referenced:

package eu.christophburmeister.playground.springboot;

import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
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
@Configuration
public class ApplicationConfig extends WsConfigurerAdapter {
		
	private static final String SCHEMA_LOCATION = "META-INF/schemas/cities.xsd";
	private static final String PORTTYPE_NAME = "Cities";

	@Bean
	public ServletRegistrationBean dispatcherServlet(ApplicationContext applicationContext) {
		MessageDispatcherServlet servlet = new MessageDispatcherServlet();
		servlet.setApplicationContext(applicationContext);
		servlet.setTransformWsdlLocations(false);
		return new ServletRegistrationBean(servlet, "/services/*");
	}

	@Bean(name = "cities")
	public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema getSchema) {
		DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
		wsdl11Definition.setPortTypeName(PORTTYPE_NAME); 
		wsdl11Definition.setLocationUri("/cities");
		wsdl11Definition.setTargetNamespace("http://christophburmeister.eu/playground/schema/cities/");
		wsdl11Definition.setSchema(getSchema);
		return wsdl11Definition;
	}		

	@Bean
	public XsdSchema getSchema() {
		return new SimpleXsdSchema(new ClassPathResource(SCHEMA_LOCATION));
	}

}

Now you need a startup main class for the spring-boot application:

package eu.christophburmeister.playground.springboot;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
	
	static final Logger logger = LogManager.getLogger(Application.class.getName());

	public static void main(String[] args) {
		logger.info("entering application");
		SpringApplication.run(Application.class, args);
	}
}

For the configuration of spring-boot itself, we gonna use the application.config:

server.port=10000

And for the logging, we rely on external log4j2 (not the Spring internal logging)

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
	<Appenders>
		<Console name="Console" target="SYSTEM_OUT">
			<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
		</Console>
	</Appenders>
	<Loggers>
		<Root level="info">
			<AppenderRef ref="Console" />
		</Root>
		<Logger name="eu.christophburmeister.playground" level="info" additivity="false">
			<AppenderRef ref="Console" />
		</Logger>
	</Loggers>
</Configuration>

start command directly from Maven shell:

mvn spring-boot:run

After that, the server comes up. All you need now is a client requesting the offered SOAP services.

Here you go with a simple SOAP client:

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── eu
    │   │       └── christophburmeister
    │   │           └── playground
    │   │               └── soapclient
    │   │                   ├── Application.java
    │   │                   └── Executor.java
    │   └── resources
    │       ├── beans.xml
    │       ├── log4j2.xml
    │       └── wsdl
    │           └── cities.wsdl
    └── test
        ├── java
        └── resources

The core of that client is the WSDL file or better the generated classes from that file.
When starting the spring-boot server on localhost, the WSDL can be fetched from here: http://localhost:10000/services/cities.wsdl

<?xml version="1.0" encoding="UTF-8" standalone="no"?><wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://christophburmeister.eu/playground/schema/cities" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://christophburmeister.eu/playground/schema/cities/" targetNamespace="http://christophburmeister.eu/playground/schema/cities/">
  <wsdl:types>
    <xs:schema xmlns:playground="http://christophburmeister.eu/playground/schema/cities" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://christophburmeister.eu/playground/schema/cities">

	<xs:complexType name="City">
		<xs:sequence>
			<xs:element name="country" type="xs:string"/>
			<xs:element name="code" type="xs:int"/>
			<xs:element name="name" type="xs:string"/>
			<xs:element name="population" type="xs:int"/>
			<xs:element name="founded" type="xs:int"/>
		</xs:sequence>
	</xs:complexType>

	<xs:element name="getCityByCodeRequest">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="code" type="xs:int"/>
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getCityByCodeResponse">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="city" type="playground:City"/>
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getCountryByCityNameRequest">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="cityName" type="xs:string"/>
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getCountryByCityNameResponse">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="country" type="xs:string"/>
			</xs:sequence>
		</xs:complexType>
	</xs:element> 	

</xs:schema>
  </wsdl:types>
  <wsdl:message name="getCountryByCityNameRequest">
    <wsdl:part element="sch:getCountryByCityNameRequest" name="getCountryByCityNameRequest">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getCountryByCityNameResponse">
    <wsdl:part element="sch:getCountryByCityNameResponse" name="getCountryByCityNameResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getCityByCodeResponse">
    <wsdl:part element="sch:getCityByCodeResponse" name="getCityByCodeResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getCityByCodeRequest">
    <wsdl:part element="sch:getCityByCodeRequest" name="getCityByCodeRequest">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="Cities">
    <wsdl:operation name="getCountryByCityName">
      <wsdl:input message="tns:getCountryByCityNameRequest" name="getCountryByCityNameRequest">
    </wsdl:input>
      <wsdl:output message="tns:getCountryByCityNameResponse" name="getCountryByCityNameResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="getCityByCode">
      <wsdl:input message="tns:getCityByCodeRequest" name="getCityByCodeRequest">
    </wsdl:input>
      <wsdl:output message="tns:getCityByCodeResponse" name="getCityByCodeResponse">
    </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="CitiesSoap11" type="tns:Cities">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getCountryByCityName">
      <soap:operation soapAction=""/>
      <wsdl:input name="getCountryByCityNameRequest">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getCountryByCityNameResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="getCityByCode">
      <soap:operation soapAction=""/>
      <wsdl:input name="getCityByCodeRequest">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getCityByCodeResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="CitiesService">
    <wsdl:port binding="tns:CitiesSoap11" name="CitiesSoap11">
      <soap:address location="/cities"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

A maven plugin (cxf-codegen) is used for class creation from WSDL:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>eu.christophburmeister.playground</groupId>
	<artifactId>soapclient</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<cxf.version>3.1.2</cxf.version>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.cxf</groupId>
				<artifactId>cxf-codegen-plugin</artifactId>
				<version>${cxf.version}</version>
				<executions>
					<execution>
						<id>generate-sources</id>
						<phase>generate-sources</phase>
						<configuration>
							<sourceRoot>${project.build.directory}/generated-sources</sourceRoot>
							<wsdlOptions>
								<wsdlOption>
									<wsdl>${basedir}/src/main/resources/wsdl/cities.wsdl</wsdl>
									<extraargs>
										<extraarg>-client</extraarg>
									</extraargs>
								</wsdlOption>
							</wsdlOptions>
						</configuration>
						<goals>
							<goal>wsdl2java</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>build-helper-maven-plugin</artifactId>
				<version>1.7</version>
				<executions>
					<execution>
						<id>add-source</id>
						<phase>generate-sources</phase>
						<goals>
							<goal>add-source</goal>
						</goals>
						<configuration>
							<sources>
								<source>${project.basedir}/target/generated-sources</source>
							</sources>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.2.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>javax.xml.ws</groupId>
			<artifactId>jaxws-api</artifactId>
			<version>2.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
			<version>2.4</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.4</version>
		</dependency>
	</dependencies>
</project>

The rest is no big magic: simple Spring application constructing the Executor class directly from the beans.xml where the endpointAdress can easily be configured and pumped into the app:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean id="executor" class="eu.christophburmeister.playground.soapclient.Executor">
   		<property name="endpointAdress" value="http://localhost:10000/services/cities" />
   </bean>

</beans>

package eu.christophburmeister.playground.soapclient;


import javax.xml.ws.BindingProvider;

import eu.christophburmeister.playground.schema.cities.GetCityByCodeRequest;
import eu.christophburmeister.playground.schema.cities.GetCityByCodeResponse;
import eu.christophburmeister.playground.schema.cities.GetCountryByCityNameRequest;
import eu.christophburmeister.playground.schema.cities.GetCountryByCityNameResponse;
import eu.christophburmeister.playground.schema.cities.Cities;
import eu.christophburmeister.playground.schema.cities.CitiesService;

public final class Executor {

	private String endpointAdress;
	
    public void execute() {
              
        CitiesService ss = new CitiesService();
        Cities port = ss.getCitiesSoap11();  

		BindingProvider bp = (BindingProvider) port;
		bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAdress);
        
		// creating the requests        
        GetCityByCodeRequest getCityByCodeRequest = new GetCityByCodeRequest();
        GetCountryByCityNameRequest getCountryByCityNameRequest = new GetCountryByCityNameRequest();
        
        // enriching the requests
        getCityByCodeRequest.setCode(16816);
        getCountryByCityNameRequest.setCityName("Neuruppin");
        
        // invoking the requests and fetching the responses                    
        GetCityByCodeResponse getCityByCodeResponse = port.getCityByCode(getCityByCodeRequest);
        GetCountryByCityNameResponse getCountryByCityNameResponse = port.getCountryByCityName(getCountryByCityNameRequest);
             
        // 
        System.out.println(getCityByCodeResponse.getCity().getName());
        System.out.println(getCountryByCityNameResponse.getCountry());
        


    }

	public String getEndpointAdress() {
		return endpointAdress;
	}

	public void setEndpointAdress(String endpointAdress) {
		this.endpointAdress = endpointAdress;
	}
}

remember: this client is only for stupid integration testing endpoints of our springboot server, so nothing for production!

The rest is plain java and configuration:

package eu.christophburmeister.playground.soapclient;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

		Executor executor = (Executor) context.getBean("executor");

		executor.execute();
	}

}

This class can simply be started via Maven commandline.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
	<Appenders>
		<Console name="Console" target="SYSTEM_OUT">
			<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
		</Console>
	</Appenders>
	<Loggers>
		<Root level="debug">
			<AppenderRef ref="Console" />
		</Root>
		<Logger name="eu.christophburmeister.playground" level="debug" additivity="false">
			<AppenderRef ref="Console" />
		</Logger>
	</Loggers>
</Configuration>

That’s it. From my point of view, spring-boot has quite good potential to be one of the next big things, because it simply hides most of the great but sometimes confusing features of the spring eco system. And it embodies one of the most important pattern in nowerdays existing software frameworks: Convention over configuration 🙂