diff --git a/2025-09/spring-32-http-client/pom.xml b/2025-09/spring-32-http-client/pom.xml
new file mode 100644
index 00000000..51c37b83
--- /dev/null
+++ b/2025-09/spring-32-http-client/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-32-http-client
+ 1.0
+
+ pom
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.2
+
+
+
+
+ rest-template
+ soap-client
+ soap-server
+
+
+
+ 17
+ 17
+ UTF-8
+ UTF-8
+ 3.1.2
+ 10.9.1
+ https://raw.githubusercontent.com/OtusTeam/Spring/master/checkstyle.xml
+
+
+
diff --git a/2025-09/spring-32-http-client/rest-template/pom.xml b/2025-09/spring-32-http-client/rest-template/pom.xml
new file mode 100644
index 00000000..311b446e
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+ rest-template
+ 1.0-SNAPSHOT
+
+
+ ru.otus
+ spring-32-http-client
+ 1.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+
+
+ org.springframework.retry
+ spring-retry
+
+
+ org.aspectj
+ aspectjweaver
+
+
+
+ org.springframework
+ spring-webflux
+
+
+ io.projectreactor.netty
+ reactor-netty
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ 2025.0.0
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/Main.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..c51fb9cd
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,21 @@
+package ru.otus.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.retry.annotation.EnableRetry;
+import ru.otus.spring.config.ClientProperties;
+
+@EnableCaching
+@EnableRetry
+@EnableFeignClients
+@EnableConfigurationProperties(ClientProperties.class)
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class, args);
+ }
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/CacheConfig.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/CacheConfig.java
new file mode 100644
index 00000000..cd39f893
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/CacheConfig.java
@@ -0,0 +1,15 @@
+package ru.otus.spring.config;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class CacheConfig {
+
+ @Bean
+ public CacheManager cacheManager() {
+ return new ConcurrentMapCacheManager("countries");
+ }
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/ClientConfiguration.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/ClientConfiguration.java
new file mode 100644
index 00000000..d7b6a412
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/ClientConfiguration.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.config;
+
+import feign.RequestInterceptor;
+import org.springframework.context.annotation.Bean;
+
+public class ClientConfiguration {
+
+ @Bean
+ public RequestInterceptor requestInterceptor(ClientProperties properties) {
+ return requestTemplate -> requestTemplate.query("access_key", properties.getKey());
+ }
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/ClientProperties.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/ClientProperties.java
new file mode 100644
index 00000000..a683ff88
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/config/ClientProperties.java
@@ -0,0 +1,13 @@
+package ru.otus.spring.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "client")
+public class ClientProperties {
+ private String url;
+ private String key;
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/dto/Country.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/dto/Country.java
new file mode 100644
index 00000000..5d0f66e4
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/dto/Country.java
@@ -0,0 +1,19 @@
+package ru.otus.spring.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@ToString
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Country {
+ private String name;
+ // private String capital;
+// private String region;
+ private String alpha3Code;
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/CountryService.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/CountryService.java
new file mode 100644
index 00000000..dbaa7878
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/CountryService.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.service;
+
+import ru.otus.spring.dto.Country;
+
+import java.util.List;
+
+public interface CountryService {
+
+ Country findByCode(String id);
+
+ List getAll();
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/FeignCountryService.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/FeignCountryService.java
new file mode 100644
index 00000000..2a87c1e7
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/FeignCountryService.java
@@ -0,0 +1,25 @@
+package ru.otus.spring.service;
+
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Retryable;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import ru.otus.spring.config.ClientConfiguration;
+import ru.otus.spring.dto.Country;
+
+import java.util.List;
+
+@FeignClient(value = "countrylayer", configuration = ClientConfiguration.class)
+@Cacheable("countries")
+@Retryable(retryFor = Exception.class, maxAttempts = 2, backoff = @Backoff(delay = 2000))
+public interface FeignCountryService {
+ @RequestMapping(method = RequestMethod.GET, value = "/alpha/{code}", produces = "application/json")
+ Country findByCode(@PathVariable("code") String code);
+
+ @RequestMapping(method = RequestMethod.GET, value = "/all", produces = "application/json")
+ List getAll();
+
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/MainServiceImpl.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/MainServiceImpl.java
new file mode 100644
index 00000000..9ae99afb
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/MainServiceImpl.java
@@ -0,0 +1,29 @@
+package ru.otus.spring.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Service;
+import ru.otus.spring.dto.Country;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class MainServiceImpl implements CommandLineRunner {
+ // private final TemplateCountryService countryService;
+// private final WebCountryService countryService;
+// private final RestCountryService countryService;
+ private final FeignCountryService countryService;
+
+ @Override
+ public void run(String... args) {
+// List countries = countryService.getAll();
+// log.info("Countries: {}", countries);
+ Country country = countryService.findByCode("col");
+ log.info("Find {}", country);
+// country = countryService.findByCode("col");
+// log.info("Find {}", country);
+// country = countryService.findByCode("rus");
+// log.info("Find {}", country);
+ }
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/RestCountryService.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/RestCountryService.java
new file mode 100644
index 00000000..c4792623
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/RestCountryService.java
@@ -0,0 +1,56 @@
+package ru.otus.spring.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClient;
+import ru.otus.spring.config.ClientProperties;
+import ru.otus.spring.dto.Country;
+
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class RestCountryService implements CountryService {
+ final ClientProperties properties;
+
+ final RestClient restClient;
+
+ public RestCountryService(ClientProperties properties) {
+ this.properties = properties;
+ this.restClient = RestClient.builder()
+ .requestFactory(new HttpComponentsClientHttpRequestFactory())
+// .messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
+ .baseUrl(properties.getUrl())
+ .defaultUriVariables(Map.of("key", properties.getKey()))
+// .defaultHeader("Header", "")
+// .requestInterceptor(myCustomInterceptor)
+// .requestInitializer(myCustomInitializer)
+ .build();
+ }
+
+ @Override
+ public Country findByCode(String id) {
+ log.info("Rest client Request findByCode");
+ return restClient.get()
+ .uri(uriBuilder -> uriBuilder.path("/alpha/{id}")
+ .queryParam("access_key", properties.getKey())
+ .build(Map.of("id", id)))
+ .accept(MediaType.APPLICATION_JSON)
+ .retrieve()
+ .body(Country.class);
+ }
+
+ @Override
+ public List getAll() {
+ log.info("Web client Request findByCode");
+ return restClient.get()
+ .uri("/all?access_key={key}")
+ .retrieve()
+ .body(new ParameterizedTypeReference<>() {
+ });
+ }
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/TemplateCountryService.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/TemplateCountryService.java
new file mode 100644
index 00000000..34b0a482
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/TemplateCountryService.java
@@ -0,0 +1,48 @@
+package ru.otus.spring.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+import ru.otus.spring.config.ClientProperties;
+import ru.otus.spring.dto.Country;
+
+import java.net.URI;
+import java.util.List;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class TemplateCountryService implements CountryService {
+ final ClientProperties properties;
+
+ private final RestOperations rest = new RestTemplate();
+
+ @Override
+ public Country findByCode(String id) {
+ log.info("RestTemplate Request findByCode");
+ return rest.getForObject("http://api.countrylayer.com/v2/alpha/" + id + "?access_key=" + properties.getKey(),
+ Country.class);
+ }
+
+ @Override
+ public List getAll() {
+ log.info("RestTemplate Request getAll");
+ URI uri = UriComponentsBuilder.fromHttpUrl(properties.getUrl())
+ .path("/all")
+ .queryParam("access_key", properties.getKey())
+ .build().toUri();
+ RequestEntity> requestEntity = new RequestEntity<>(HttpMethod.GET,
+ uri);
+// List countries = rest.getForObject(uri, List.class);
+ ResponseEntity> exchange = rest.exchange(requestEntity, new ParameterizedTypeReference<>() {
+ });
+ return exchange.getBody();
+ }
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/WebCountryService.java b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/WebCountryService.java
new file mode 100644
index 00000000..891a74fd
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/java/ru/otus/spring/service/WebCountryService.java
@@ -0,0 +1,66 @@
+package ru.otus.spring.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.config.ClientProperties;
+import ru.otus.spring.dto.Country;
+
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class WebCountryService implements CountryService {
+ final ClientProperties properties;
+
+ final WebClient webClient;
+
+ public WebCountryService(ClientProperties properties) {
+ this.properties = properties;
+ this.webClient = WebClient.builder()
+ .baseUrl(properties.getUrl())
+ .defaultUriVariables(Map.of("key", properties.getKey()))
+ .filter(errorHandler())
+ .build();
+ }
+
+ @Override
+ public Country findByCode(String id) {
+ log.info("Web client Request findByCode");
+ return webClient.get()
+ .uri("/alpha/{id}?access_key={key}", Map.of("id", id))
+ .retrieve()
+ .bodyToMono(Country.class)
+ .block();
+ }
+
+ @Override
+ public List getAll() {
+ log.info("Web client Request findByCode");
+ return webClient.get()
+ .uri("/all?access_key={key}")
+ .retrieve()
+ .bodyToMono(new ParameterizedTypeReference>() {
+ })
+ .block();
+ }
+
+ private static ExchangeFilterFunction errorHandler() {
+ return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
+ if (clientResponse.statusCode().is5xxServerError()) {
+ return clientResponse.bodyToMono(String.class)
+ .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody)));
+ } else if (clientResponse.statusCode().is4xxClientError()) {
+ return clientResponse.bodyToMono(String.class)
+ .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody)));
+ } else {
+ return Mono.just(clientResponse);
+ }
+ });
+ }
+
+}
diff --git a/2025-09/spring-32-http-client/rest-template/src/main/resources/application.yml b/2025-09/spring-32-http-client/rest-template/src/main/resources/application.yml
new file mode 100644
index 00000000..4ac51230
--- /dev/null
+++ b/2025-09/spring-32-http-client/rest-template/src/main/resources/application.yml
@@ -0,0 +1,19 @@
+spring.main.web-application-type: none
+
+spring:
+ cloud:
+ openfeign:
+ client:
+ config:
+ countrylayer:
+ url: http://api.countrylayer.com/v2
+
+client:
+ url: http://api.countrylayer.com/v2
+ key: [ !!!Your key!!! ]
+
+logging:
+ level:
+ org:
+ springframework:
+ web: debug
diff --git a/2025-09/spring-32-http-client/soap-client/pom.xml b/2025-09/spring-32-http-client/soap-client/pom.xml
new file mode 100644
index 00000000..dc7feaa7
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-client/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ soap-client
+ 1.0
+
+
+ ru.otus
+ spring-32-http-client
+ 1.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web-services
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ com.sun.xml.ws
+ jaxws-maven-plugin
+ 3.0.0
+
+
+
+ wsimport
+
+
+
+
+ hello.wsdl
+
+ src/main/resources/countries.wsdl
+
+
+ true
+
+
+
+
+
+
diff --git a/2025-09/spring-32-http-client/soap-client/src/main/java/hello/ClientApp.java b/2025-09/spring-32-http-client/soap-client/src/main/java/hello/ClientApp.java
new file mode 100644
index 00000000..185dff2d
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-client/src/main/java/hello/ClientApp.java
@@ -0,0 +1,39 @@
+package hello;
+
+import hello.wsdl.Country;
+import hello.wsdl.GetCountryResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+@Slf4j
+@SpringBootApplication
+public class ClientApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ClientApp.class, args);
+ }
+
+ @Bean
+ CommandLineRunner lookup(CountryClient quoteClient) {
+ return args -> {
+ String country = "Spain";
+
+ if (args.length > 0) {
+ country = args[0];
+ }
+ GetCountryResponse response = quoteClient.getCountry(country);
+ if (response.getCountry() != null) {
+ Country countryDetails = response.getCountry();
+ log.warn("GetCountry {} : Country[name={}, population={}, capital={}, currency={}]",
+ country, countryDetails.getName(), countryDetails.getPopulation(),
+ countryDetails.getCapital(), countryDetails.getCurrency());
+ } else {
+ log.warn("GetCountry {} : no country found in response", country);
+ }
+ };
+ }
+
+}
diff --git a/2025-09/spring-32-http-client/soap-client/src/main/java/hello/CountryClient.java b/2025-09/spring-32-http-client/soap-client/src/main/java/hello/CountryClient.java
new file mode 100644
index 00000000..eec56348
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-client/src/main/java/hello/CountryClient.java
@@ -0,0 +1,24 @@
+package hello;
+
+import hello.wsdl.GetCountryRequest;
+import hello.wsdl.GetCountryResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
+
+@Slf4j
+public class CountryClient extends WebServiceGatewaySupport {
+
+ public GetCountryResponse getCountry(String country) {
+
+ GetCountryRequest request = new GetCountryRequest();
+ request.setName(country);
+
+ log.info("Requesting location for {}", country);
+
+ GetCountryResponse response = (GetCountryResponse) getWebServiceTemplate()
+ .marshalSendAndReceive("http://localhost:8080/ws", request);
+
+ return response;
+ }
+
+}
diff --git a/2025-09/spring-32-http-client/soap-client/src/main/java/hello/CountryConfiguration.java b/2025-09/spring-32-http-client/soap-client/src/main/java/hello/CountryConfiguration.java
new file mode 100644
index 00000000..edd3a4a4
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-client/src/main/java/hello/CountryConfiguration.java
@@ -0,0 +1,28 @@
+package hello;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.oxm.jaxb.Jaxb2Marshaller;
+
+@Configuration
+public class CountryConfiguration {
+
+ @Bean
+ public Jaxb2Marshaller marshaller() {
+ Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
+ // this package must match the package in the specified in
+ // pom.xml
+ marshaller.setContextPath("hello.wsdl");
+ return marshaller;
+ }
+
+ @Bean
+ public CountryClient countryClient(Jaxb2Marshaller marshaller) {
+ CountryClient client = new CountryClient();
+ client.setDefaultUri("http://localhost:8080/ws");
+ client.setMarshaller(marshaller);
+ client.setUnmarshaller(marshaller);
+ return client;
+ }
+
+}
diff --git a/2025-09/spring-32-http-client/soap-client/src/main/resources/application.yml b/2025-09/spring-32-http-client/soap-client/src/main/resources/application.yml
new file mode 100644
index 00000000..1aa84da6
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-client/src/main/resources/application.yml
@@ -0,0 +1,6 @@
+logging:
+ level:
+ hello: INFO
+ org:
+ springframework:
+ ws: DEBUG
diff --git a/2025-09/spring-32-http-client/soap-client/src/main/resources/countries.wsdl b/2025-09/spring-32-http-client/soap-client/src/main/resources/countries.wsdl
new file mode 100644
index 00000000..061e18f5
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-client/src/main/resources/countries.wsdl
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2025-09/spring-32-http-client/soap-server/pom.xml b/2025-09/spring-32-http-client/soap-server/pom.xml
new file mode 100644
index 00000000..7b02d5b8
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ soap-server
+ 1.0
+
+
+ ru.otus
+ spring-32-http-client
+ 1.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web-services
+
+
+ wsdl4j
+ wsdl4j
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.codehaus.mojo
+ jaxb2-maven-plugin
+ 3.1.0
+
+
+ xjc
+
+ xjc
+
+
+
+
+
+ ${project.basedir}/src/main/resources/countries.xsd
+
+
+ ru.otus.hello.web_service
+
+
+
+
+
+
diff --git a/2025-09/spring-32-http-client/soap-server/src/main/java/hello/CountryEndpoint.java b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/CountryEndpoint.java
new file mode 100644
index 00000000..b0c6f9f7
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/CountryEndpoint.java
@@ -0,0 +1,30 @@
+package hello;
+
+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 ru.otus.hello.web_service.GetCountryRequest;
+import ru.otus.hello.web_service.GetCountryResponse;
+
+@Endpoint
+public class CountryEndpoint {
+ private static final String NAMESPACE_URI = "http://otus.ru/hello/web-service";
+
+ private final 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;
+ }
+}
diff --git a/2025-09/spring-32-http-client/soap-server/src/main/java/hello/CountryRepository.java b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/CountryRepository.java
new file mode 100644
index 00000000..be6d81dc
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/CountryRepository.java
@@ -0,0 +1,47 @@
+package hello;
+
+import jakarta.annotation.PostConstruct;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import ru.otus.hello.web_service.Country;
+import ru.otus.hello.web_service.Currency;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class CountryRepository {
+ private static final Map countries = new HashMap<>();
+
+ @PostConstruct
+ public void initData() {
+ Country spain = new Country();
+ spain.setName("Spain");
+ spain.setCapital("Madrid");
+ spain.setCurrency(Currency.EUR);
+ spain.setPopulation(46704314);
+
+ countries.put(spain.getName(), spain);
+
+ Country poland = new Country();
+ poland.setName("Poland");
+ poland.setCapital("Warsaw");
+ poland.setCurrency(Currency.PLN);
+ poland.setPopulation(38186860);
+
+ countries.put(poland.getName(), poland);
+
+ Country uk = new Country();
+ uk.setName("United Kingdom");
+ uk.setCapital("London");
+ uk.setCurrency(Currency.GBP);
+ uk.setPopulation(63705000);
+
+ countries.put(uk.getName(), uk);
+ }
+
+ public Country findCountry(String name) {
+ Assert.notNull(name, "The country's name must not be null");
+ return countries.get(name);
+ }
+}
diff --git a/2025-09/spring-32-http-client/soap-server/src/main/java/hello/ServerApp.java b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/ServerApp.java
new file mode 100644
index 00000000..489836fb
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/ServerApp.java
@@ -0,0 +1,12 @@
+package hello;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ServerApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ServerApp.class, args);
+ }
+}
diff --git a/2025-09/spring-32-http-client/soap-server/src/main/java/hello/WebServiceConfig.java b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/WebServiceConfig.java
new file mode 100644
index 00000000..79455f80
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/src/main/java/hello/WebServiceConfig.java
@@ -0,0 +1,40 @@
+package hello;
+
+import org.springframework.boot.web.servlet.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.WsConfigurer;
+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 WebServiceConfig implements WsConfigurer {
+ @Bean
+ public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
+ MessageDispatcherServlet servlet = new MessageDispatcherServlet();
+ servlet.setApplicationContext(applicationContext);
+ servlet.setTransformWsdlLocations(true);
+ return new ServletRegistrationBean<>(servlet, "/ws/*");
+ }
+
+ @Bean(name = "countries")
+ public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
+ DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
+ wsdl11Definition.setPortTypeName("CountriesPort");
+ wsdl11Definition.setLocationUri("/ws");
+ wsdl11Definition.setTargetNamespace("http://otus.ru/hello/web-service");
+ wsdl11Definition.setSchema(countriesSchema);
+ return wsdl11Definition;
+ }
+
+ @Bean
+ public XsdSchema countriesSchema() {
+ return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
+ }
+}
diff --git a/2025-09/spring-32-http-client/soap-server/src/main/resources/application.yml b/2025-09/spring-32-http-client/soap-server/src/main/resources/application.yml
new file mode 100644
index 00000000..4b4d02dc
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/src/main/resources/application.yml
@@ -0,0 +1,3 @@
+logging:
+ level:
+ root: debug
\ No newline at end of file
diff --git a/2025-09/spring-32-http-client/soap-server/src/main/resources/countries.xsd b/2025-09/spring-32-http-client/soap-server/src/main/resources/countries.xsd
new file mode 100644
index 00000000..806a4136
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/src/main/resources/countries.xsd
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2025-09/spring-32-http-client/soap-server/src/test/java/hello/ApplicationIntegrationTests.java b/2025-09/spring-32-http-client/soap-server/src/test/java/hello/ApplicationIntegrationTests.java
new file mode 100644
index 00000000..0bc377ea
--- /dev/null
+++ b/2025-09/spring-32-http-client/soap-server/src/test/java/hello/ApplicationIntegrationTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package hello;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.oxm.jaxb.Jaxb2Marshaller;
+import org.springframework.util.ClassUtils;
+import org.springframework.ws.client.core.WebServiceTemplate;
+import ru.otus.hello.web_service.GetCountryRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+public class ApplicationIntegrationTests {
+
+ private final Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
+
+ @LocalServerPort
+ private int port = 0;
+
+ @BeforeEach
+ public void init() throws Exception {
+ marshaller.setPackagesToScan(ClassUtils.getPackageName(GetCountryRequest.class));
+ marshaller.afterPropertiesSet();
+ }
+
+ @Test
+ public void testSendAndReceive() {
+ WebServiceTemplate ws = new WebServiceTemplate(marshaller);
+ GetCountryRequest request = new GetCountryRequest();
+ request.setName("Spain");
+
+ assertThat(ws.marshalSendAndReceive("http://localhost:" + port + "/ws", request)).isNotNull();
+ }
+}
\ No newline at end of file