diff --git a/2022-11/spring-30/.gitignore b/2022-11/spring-30/.gitignore
new file mode 100644
index 00000000..549e00a2
--- /dev/null
+++ b/2022-11/spring-30/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/2022-11/spring-30/pom.xml b/2022-11/spring-30/pom.xml
new file mode 100644
index 00000000..56dbc7bd
--- /dev/null
+++ b/2022-11/spring-30/pom.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-30
+ 1.0
+
+ pom
+
+
+ spring-30-exercise
+ spring-30-solution
+
+
diff --git a/2022-11/spring-30/spring-30-exercise/pom.xml b/2022-11/spring-30/spring-30-exercise/pom.xml
new file mode 100644
index 00000000..0ea46312
--- /dev/null
+++ b/2022-11/spring-30/spring-30-exercise/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-30-exercise
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.9
+
+
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-integration
+
+
+ org.springframework
+ spring-messaging
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2022-11/spring-30/spring-30-exercise/src/main/java/ru/otus/spring/integration/App.java b/2022-11/spring-30/spring-30-exercise/src/main/java/ru/otus/spring/integration/App.java
new file mode 100644
index 00000000..f39acb46
--- /dev/null
+++ b/2022-11/spring-30/spring-30-exercise/src/main/java/ru/otus/spring/integration/App.java
@@ -0,0 +1,70 @@
+package ru.otus.spring.integration;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.annotation.IntegrationComponentScan;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.dsl.MessageChannels;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.messaging.PollableChannel;
+import org.springframework.messaging.SubscribableChannel;
+
+import lombok.extern.slf4j.Slf4j;
+
+
+@SpringBootApplication
+@IntegrationComponentScan
+@Slf4j
+public class App {
+
+ public static void main(String[] args) throws InterruptedException {
+ ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
+
+ PollableChannel queueChannel = ctx.getBean("queueChannel", PollableChannel.class);
+ SubscribableChannel directChannel = ctx.getBean("directChannel", SubscribableChannel.class);
+
+
+ log.warn("INIT");
+ for (int i = 0; i < 10; i++) {
+ queueChannel.send(MessageBuilder.withPayload("Start " + i).build());
+ }
+ log.warn("INIT FINISH");
+ Thread.sleep(5000);
+
+ log.warn("START");
+ directChannel.subscribe((msg) -> log.warn("Receive msg: {}", msg));
+ new Thread(() -> {
+ while (true) {
+ directChannel.send(queueChannel.receive());
+ }
+ }).start();
+ log.warn("START FINISH");
+ Thread.sleep(5000);
+
+ log.warn("");
+ queueChannel.send(MessageBuilder.withPayload("Hello").build());
+ Thread.sleep(5000);
+
+ log.warn("");
+ queueChannel.send(MessageBuilder.withPayload("Hello2").build());
+ Thread.sleep(5000);
+
+ log.warn("");
+ queueChannel.send(MessageBuilder.withPayload("Hello3").build());
+
+ }
+
+ @Bean
+ public PollableChannel queueChannel() {
+ return new QueueChannel(10);
+ }
+
+ @Bean
+ public SubscribableChannel directChannel() {
+ return MessageChannels.direct("channel2").get();
+ }
+
+}
+
diff --git a/2022-11/spring-30/spring-30-exercise/src/main/resources/application.yml b/2022-11/spring-30/spring-30-exercise/src/main/resources/application.yml
new file mode 100644
index 00000000..1a9aeb48
--- /dev/null
+++ b/2022-11/spring-30/spring-30-exercise/src/main/resources/application.yml
@@ -0,0 +1,5 @@
+logging:
+ level:
+ root: info
+
+ org.springframework.integration: debug
\ No newline at end of file
diff --git a/2022-11/spring-30/spring-30-exercise/src/test/java/ru/otus/spring/integration/MessagesTest.java b/2022-11/spring-30/spring-30-exercise/src/test/java/ru/otus/spring/integration/MessagesTest.java
new file mode 100644
index 00000000..ffff36c7
--- /dev/null
+++ b/2022-11/spring-30/spring-30-exercise/src/test/java/ru/otus/spring/integration/MessagesTest.java
@@ -0,0 +1,98 @@
+package ru.otus.spring.integration;
+
+
+import org.junit.jupiter.api.Test;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.support.ErrorMessage;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.messaging.support.MessageBuilder;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+@SuppressWarnings("all")
+public class MessagesTest {
+
+ @Test
+ public void testCreateSimpleGenericMessage() {
+ // TODO: Создайте сообщение с payload-ом "Hello" с помощью конструктора
+ Message message = null;
+
+ assertNotNull(message);
+ assertEquals(GenericMessage.class, message.getClass());
+ assertNotNull(message.getPayload());
+ assertEquals("Hello", message.getPayload());
+ }
+
+ @Test
+ public void testCreateGenericMessage() {
+ // TODO: Создайте сообщение с пользователем с помощью конструктора
+ Message message = null;
+
+ assertNotNull(message);
+ assertEquals(GenericMessage.class, message.getClass());
+ assertNotNull(message.getPayload());
+ assertEquals(new User("John", 23), message.getPayload());
+ }
+
+ @Test
+ public void testGenericMessageWithHeaders() {
+ // TODO: Создайте сообщение с payload-ом "Hello" и header-ом "to":"World"
+ Map headers = null;
+ Message message = null;
+
+ assertNotNull(message);
+ assertEquals("Hello", message.getPayload());
+ assertEquals("World", message.getHeaders().get("to", String.class));
+ }
+
+ @Test
+ public void testGenericMessageWithMessageHeaders() {
+ // TODO: Создайте сообщение с payload-ом "Hello" и header-ом "to":"World"
+ MessageHeaders headers = null;
+ Message message = null;
+
+ assertNotNull(message);
+ assertEquals("Hello", message.getPayload());
+ assertEquals("World", message.getHeaders().get("to", String.class));
+ }
+
+ @Test
+ public void testErrorMessage() {
+ // TODO: Создайте сообщение об ошибки с объектом NullPointerException внутри
+ Message errorMessage = null;
+
+ assertNotNull(errorMessage);
+ assertEquals(ErrorMessage.class, errorMessage.getClass());
+ assertEquals(NullPointerException.class, errorMessage.getPayload().getClass());
+ }
+
+ @Test
+ public void testMessageBuilder() {
+ // TODO: Создайте сообщение с payload-ом "Hello" и header-ом "to":"World" с помощью MessageBuilder
+ Message message = null;
+
+ assertNotNull(message);
+ assertEquals("Hello", message.getPayload());
+ assertEquals("World", message.getHeaders().get("to", String.class));
+ }
+
+ @Test
+ public void testBuildFromMessage() {
+ Message original = MessageBuilder
+ .withPayload(new User("Kate", 30))
+ .setHeader("processor", "userService")
+ .build();
+
+ // TODO: Создайте новое сообщение с теми же payload и header-ами c помощью MessageBuilder
+ Message newMessage = null;
+
+ assertNotNull(newMessage);
+ assertEquals(original.getPayload(), newMessage.getPayload());
+ assertEquals(original.getHeaders().get("processor"), newMessage.getHeaders().get("processor"));
+ }
+}
diff --git a/2022-11/spring-30/spring-30-exercise/src/test/java/ru/otus/spring/integration/User.java b/2022-11/spring-30/spring-30-exercise/src/test/java/ru/otus/spring/integration/User.java
new file mode 100644
index 00000000..5dd81ef8
--- /dev/null
+++ b/2022-11/spring-30/spring-30-exercise/src/test/java/ru/otus/spring/integration/User.java
@@ -0,0 +1,36 @@
+package ru.otus.spring.integration;
+
+import java.util.Objects;
+
+public class User {
+
+ private final String name;
+ private final int age;
+
+ public User(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof User)) return false;
+ User user = (User) o;
+ return age == user.age &&
+ Objects.equals(name, user.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, age);
+ }
+}
diff --git a/2022-11/spring-30/spring-30-solution/pom.xml b/2022-11/spring-30/spring-30-solution/pom.xml
new file mode 100644
index 00000000..d280368c
--- /dev/null
+++ b/2022-11/spring-30/spring-30-solution/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-30-solution
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.9
+
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-integration
+
+
+ org.springframework
+ spring-messaging
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2022-11/spring-30/spring-30-solution/src/main/java/ru/otus/spring/integration/App.java b/2022-11/spring-30/spring-30-solution/src/main/java/ru/otus/spring/integration/App.java
new file mode 100644
index 00000000..49630427
--- /dev/null
+++ b/2022-11/spring-30/spring-30-solution/src/main/java/ru/otus/spring/integration/App.java
@@ -0,0 +1,70 @@
+package ru.otus.spring.integration;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.annotation.IntegrationComponentScan;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.dsl.MessageChannels;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.messaging.PollableChannel;
+import org.springframework.messaging.SubscribableChannel;
+
+import lombok.extern.slf4j.Slf4j;
+
+
+@SpringBootApplication
+@IntegrationComponentScan
+@Slf4j
+public class App {
+
+ public static void main(String[] args) throws InterruptedException {
+ ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
+
+ PollableChannel queueChannel = ctx.getBean("queueChannel", PollableChannel.class);
+ SubscribableChannel directChannel = ctx.getBean("directChannel", SubscribableChannel.class);
+
+
+ log.warn("INIT");
+ for (int i = 0; i < 10; i++) {
+ queueChannel.send(MessageBuilder.withPayload("Start " + i).build());
+ }
+ log.warn("INIT FINISH");
+ Thread.sleep(5000);
+
+ log.warn("START");
+ directChannel.subscribe((msg) -> log.warn("Receive msg: {}", msg));
+ new Thread(() -> {
+ while (true) {
+ directChannel.send(queueChannel.receive());
+ }
+ }).start();
+ log.warn("START FINISH");
+ Thread.sleep(5000);
+
+ log.warn("");
+ queueChannel.send(MessageBuilder.withPayload("Hello").build());
+ Thread.sleep(5000);
+
+ log.warn("");
+ queueChannel.send(MessageBuilder.withPayload("Hello2").build());
+ Thread.sleep(5000);
+
+ log.warn("");
+ queueChannel.send(MessageBuilder.withPayload("Hello3").build());
+
+ }
+
+ @Bean
+ public PollableChannel queueChannel() {
+ return new QueueChannel(10);
+ }
+
+ @Bean
+ public SubscribableChannel directChannel() {
+ return MessageChannels.direct("channel2").get();
+ }
+
+}
+
diff --git a/2022-11/spring-30/spring-30-solution/src/main/resources/application.yml b/2022-11/spring-30/spring-30-solution/src/main/resources/application.yml
new file mode 100644
index 00000000..1a9aeb48
--- /dev/null
+++ b/2022-11/spring-30/spring-30-solution/src/main/resources/application.yml
@@ -0,0 +1,5 @@
+logging:
+ level:
+ root: info
+
+ org.springframework.integration: debug
\ No newline at end of file
diff --git a/2022-11/spring-30/spring-30-solution/src/test/java/ru/otus/spring/integration/MessagesTest.java b/2022-11/spring-30/spring-30-solution/src/test/java/ru/otus/spring/integration/MessagesTest.java
new file mode 100644
index 00000000..496c003f
--- /dev/null
+++ b/2022-11/spring-30/spring-30-solution/src/test/java/ru/otus/spring/integration/MessagesTest.java
@@ -0,0 +1,101 @@
+package ru.otus.spring.integration;
+
+
+import org.junit.jupiter.api.Test;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.support.ErrorMessage;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.messaging.support.MessageBuilder;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+@SuppressWarnings("all")
+public class MessagesTest {
+
+ @Test
+ public void testCreateSimpleGenericMessage() {
+ // TODO: Создайте сообщение с payload-ом "Hello" с помощью конструктора
+ Message message = new GenericMessage("Hello");
+
+ assertNotNull(message);
+ assertEquals(GenericMessage.class, message.getClass());
+ assertNotNull(message.getPayload());
+ assertEquals("Hello", message.getPayload());
+ }
+
+ @Test
+ public void testCreateGenericMessage() {
+ // TODO: Создайте сообщение с пользователем с помощью конструктора
+ Message message = new GenericMessage(new User("John", 23));
+
+ assertNotNull(message);
+ assertEquals(GenericMessage.class, message.getClass());
+ assertNotNull(message.getPayload());
+ assertEquals(new User("John", 23), message.getPayload());
+ }
+
+ @Test
+ public void testGenericMessageWithHeaders() {
+ // TODO: Создайте сообщение с payload-ом "Hello" и header-ом "to":"World"
+ Map headers = Collections.singletonMap("to", "World");
+ Message message = new GenericMessage<>("Hello", headers);
+
+ assertNotNull(message);
+ assertEquals("Hello", message.getPayload());
+ assertEquals("World", message.getHeaders().get("to", String.class));
+ }
+
+ @Test
+ public void testGenericMessageWithMessageHeaders() {
+ // TODO: Создайте сообщение с payload-ом "Hello" и header-ом "to":"World"
+ MessageHeaders headers = new MessageHeaders(Collections.singletonMap("to", "World"));
+ Message message = new GenericMessage<>("Hello", headers);
+
+ assertNotNull(message);
+ assertEquals("Hello", message.getPayload());
+ assertEquals("World", message.getHeaders().get("to", String.class));
+ }
+
+ @Test
+ public void testErrorMessage() {
+ // TODO: Создайте сообщение об ошибки с объектом NullPointerException внутри
+ Message errorMessage = new ErrorMessage(new NullPointerException());
+
+ assertNotNull(errorMessage);
+ assertEquals(ErrorMessage.class, errorMessage.getClass());
+ assertEquals(NullPointerException.class, errorMessage.getPayload().getClass());
+ }
+
+ @Test
+ public void testMessageBuilder() {
+ // TODO: Создайте сообщение с payload-ом "Hello" и header-ом "to":"World" с помощью MessageBuilder
+ Message message = MessageBuilder.withPayload("Hello")
+ .setHeader("to", "World")
+ .build();
+
+ assertNotNull(message);
+ assertEquals("Hello", message.getPayload());
+ assertEquals("World", message.getHeaders().get("to", String.class));
+ }
+
+ @Test
+ public void testBuildFromMessage() {
+ Message original = MessageBuilder
+ .withPayload(new User("Kate", 30))
+ .setHeader("processor", "userService")
+ .build();
+
+ // TODO: Создайте новое сообщение с теми же payload и header-ами c помощью MessageBuilder
+ Message newMessage = MessageBuilder.fromMessage(original).build();
+
+ assertNotNull(newMessage);
+ assertEquals(original.getPayload(), newMessage.getPayload());
+ assertEquals(original.getHeaders().get("processor"), newMessage.getHeaders().get("processor"));
+ }
+}
diff --git a/2022-11/spring-30/spring-30-solution/src/test/java/ru/otus/spring/integration/User.java b/2022-11/spring-30/spring-30-solution/src/test/java/ru/otus/spring/integration/User.java
new file mode 100644
index 00000000..5dd81ef8
--- /dev/null
+++ b/2022-11/spring-30/spring-30-solution/src/test/java/ru/otus/spring/integration/User.java
@@ -0,0 +1,36 @@
+package ru.otus.spring.integration;
+
+import java.util.Objects;
+
+public class User {
+
+ private final String name;
+ private final int age;
+
+ public User(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof User)) return false;
+ User user = (User) o;
+ return age == user.age &&
+ Objects.equals(name, user.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, age);
+ }
+}