diff --git a/2023-07/spring-05-springBoot/.gitignore b/2023-07/spring-05-springBoot/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2023-07/spring-05-springBoot/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2023-07/spring-05-springBoot/greeting-app/.gitignore b/2023-07/spring-05-springBoot/greeting-app/.gitignore
new file mode 100644
index 00000000..549e00a2
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/.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/2023-07/spring-05-springBoot/greeting-app/pom.xml b/2023-07/spring-05-springBoot/greeting-app/pom.xml
new file mode 100644
index 00000000..9befc9f4
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.2
+
+
+
+ ru.otus
+ greeting-app
+ 0.0.1-SNAPSHOT
+ greeting-app
+ Spring Boot project demo
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ UTF-8
+ 0.0.1-SNAPSHOT
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ ru.otus
+ io-starter
+ ${io.starter.version}
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/GreetingAppApplication.java b/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/GreetingAppApplication.java
new file mode 100644
index 00000000..06d4a71a
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/GreetingAppApplication.java
@@ -0,0 +1,17 @@
+package ru.otus.greetingapp;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import ru.otus.greetingapp.config.OtusIOConfig;
+
+
+@EnableConfigurationProperties(OtusIOConfig.class)
+@SpringBootApplication
+public class GreetingAppApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GreetingAppApplication.class, args);
+ }
+
+}
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/config/OtusIOConfig.java b/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/config/OtusIOConfig.java
new file mode 100644
index 00000000..bd91577e
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/config/OtusIOConfig.java
@@ -0,0 +1,31 @@
+package ru.otus.greetingapp.config;
+
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.bind.ConstructorBinding;
+import ru.otus.io.config.LocaleProvider;
+
+import java.util.Locale;
+
+@ToString
+@ConfigurationProperties(prefix = "otus.custom")
+public class OtusIOConfig implements LocaleProvider {
+
+ private final boolean ioEnabled;
+ private final Locale locale;
+
+ @ConstructorBinding
+ public OtusIOConfig(boolean ioEnabled, String locale) {
+ this.ioEnabled = ioEnabled;
+ this.locale = Locale.forLanguageTag(locale);
+ }
+
+ @Override
+ public Locale getCurrent() {
+ return locale;
+ }
+
+ public boolean isIoEnabled() {
+ return ioEnabled;
+ }
+}
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/runner/AppRunner.java b/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/runner/AppRunner.java
new file mode 100644
index 00000000..6547f1a8
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/java/ru/otus/greetingapp/runner/AppRunner.java
@@ -0,0 +1,47 @@
+package ru.otus.greetingapp.runner;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+import ru.otus.greetingapp.config.OtusIOConfig;
+import ru.otus.io.services.IOService;
+import ru.otus.io.services.LocalizationService;
+
+import static java.util.Objects.isNull;
+
+@Component
+public class AppRunner implements ApplicationRunner {
+
+ private final OtusIOConfig ioConfig;
+ private final IOService ioService;
+ private final LocalizationService localizationService;
+
+ public AppRunner(@Autowired(required = false) OtusIOConfig ioConfig,
+ @Autowired(required = false) IOService ioService,
+ @Autowired(required = false) LocalizationService localizationService) {
+ this.ioConfig = ioConfig;
+ this.ioService = ioService;
+ this.localizationService = localizationService;
+ }
+
+
+ @Override
+ public void run(ApplicationArguments args) {
+ if (isNull(ioService)) {
+ System.err.println("Отсутствует сервис ввода-вывода. Включите настройку \"otus.custom.io.enabled\"");
+ return;
+ }
+ ioService.outputString("---------------------------------------------");
+
+ ioService.outputAsString(ioConfig);
+
+ ioService.outputString("---------------------------------------------");
+
+ var greetingTarget = localizationService.getMessage("greeting.target");
+ var greeting = localizationService.getMessage("greeting", greetingTarget);
+ ioService.outputString(greeting);
+
+ ioService.outputString("---------------------------------------------");
+ }
+}
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/resources/application.yml b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/application.yml
new file mode 100644
index 00000000..47bbd92b
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/application.yml
@@ -0,0 +1,9 @@
+#spring:
+ #messages:
+ #basename: messages
+ #fallback-to-system-locale: false
+
+#otus:
+ #custom:
+ #io-enabled: true
+ #locale: ru-RU
\ No newline at end of file
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/resources/banner.txt b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/banner.txt
new file mode 100644
index 00000000..da1aa028
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/banner.txt
@@ -0,0 +1,9 @@
+ o o o o o
+ <|> <|> <|> <|> <|>
+ / > / \ / \ / \ < \
+ \o__ __o o__ __o \o/ \o/ o__ __o o o o__ __o \o__ __o \o/ o__ __o/
+ | v\ /v |> | | /v v\ <|> <|> /v v\ | |> | /v |
+ / \ <\ /> // / \ / \ /> <\ < > < > /> <\ / \ < > / \ /> / \
+ \o/ o/ \o o/ \o/ \o/ \ / \o o/\o o/ \ / \o/ \o/ \ \o/
+ | <| v\ /v __o | | o o v\ /v v\ /v o o | | o |
+ / \ / \ <\/> __/> / \ / \ <\__ __/> <\/> <\/> <\__ __/> / \ / \ <\__ / \
\ No newline at end of file
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages.properties b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages.properties
new file mode 100644
index 00000000..2552c87d
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages.properties
@@ -0,0 +1,2 @@
+greeting=Hello {0}!!!
+greeting.target=World
\ No newline at end of file
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages_en_US.properties b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages_en_US.properties
new file mode 100644
index 00000000..2552c87d
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages_en_US.properties
@@ -0,0 +1,2 @@
+greeting=Hello {0}!!!
+greeting.target=World
\ No newline at end of file
diff --git a/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages_ru_RU.properties b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages_ru_RU.properties
new file mode 100644
index 00000000..b8cd2136
--- /dev/null
+++ b/2023-07/spring-05-springBoot/greeting-app/src/main/resources/messages_ru_RU.properties
@@ -0,0 +1,2 @@
+greeting=\u041F\u0440\u0438\u0432\u0435\u0442 {0}!!!
+greeting.target=\u041C\u0438\u0440
\ No newline at end of file
diff --git a/2023-07/spring-05-springBoot/io-starter/.gitignore b/2023-07/spring-05-springBoot/io-starter/.gitignore
new file mode 100644
index 00000000..549e00a2
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/.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/2023-07/spring-05-springBoot/io-starter/pom.xml b/2023-07/spring-05-springBoot/io-starter/pom.xml
new file mode 100644
index 00000000..fc83feb1
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/pom.xml
@@ -0,0 +1,65 @@
+
+
+ 4.0.0
+
+ ru.otus
+ io-starter
+ 0.0.1-SNAPSHOT
+ io-starter
+ Spring Boot starter demo
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ UTF-8
+ 3.1.2
+ 2.0
+ 1.18.28
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ ${spring.boot.version}
+ compile
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ ${spring.boot.version}
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ ${spring.boot.version}
+ compile
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.3.0
+
+
+ false
+
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/IOAutoconfiguration.java b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/IOAutoconfiguration.java
new file mode 100644
index 00000000..77ff63c4
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/IOAutoconfiguration.java
@@ -0,0 +1,46 @@
+package ru.otus.io;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import ru.otus.io.config.DefaultLocaleProvider;
+import ru.otus.io.config.LocaleProvider;
+import ru.otus.io.services.IOService;
+import ru.otus.io.services.LocalizationService;
+import ru.otus.io.services.LocalizationServiceImpl;
+import ru.otus.io.services.StreamsIOService;
+
+@ConditionalOnProperty(value = "otus.custom.io-enabled", havingValue = "true", matchIfMissing = false)
+@Configuration
+public class IOAutoconfiguration {
+
+ @Autowired
+ private ApplicationContext ctx;
+
+ @ConditionalOnMissingBean(IOService.class)
+ @Bean
+ public IOService ioService() {
+ return new StreamsIOService(System.out, System.in);
+ }
+
+ @ConditionalOnMissingBean(LocaleProvider.class)
+ @Bean
+ public LocaleProvider localeProvider(@Value("${otus.custom.locale}") String localeTag) {
+ return new DefaultLocaleProvider(localeTag);
+ }
+
+ @ConditionalOnMissingBean(LocalizationService.class)
+ @ConditionalOnBean(MessageSource.class)
+ @Bean
+ public LocalizationService localizationService(LocaleProvider localeProvider, MessageSource messageSource) {
+ return new LocalizationServiceImpl(localeProvider, messageSource);
+ }
+
+}
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/config/DefaultLocaleProvider.java b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/config/DefaultLocaleProvider.java
new file mode 100644
index 00000000..f5a93df0
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/config/DefaultLocaleProvider.java
@@ -0,0 +1,17 @@
+package ru.otus.io.config;
+
+import java.util.Locale;
+
+public class DefaultLocaleProvider implements LocaleProvider {
+
+ private final Locale locale;
+
+ public DefaultLocaleProvider(String localeTag) {
+ this.locale = Locale.forLanguageTag(localeTag);
+ }
+
+ @Override
+ public Locale getCurrent() {
+ return locale;
+ }
+}
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/config/LocaleProvider.java b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/config/LocaleProvider.java
new file mode 100644
index 00000000..2ef1b06a
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/config/LocaleProvider.java
@@ -0,0 +1,7 @@
+package ru.otus.io.config;
+
+import java.util.Locale;
+
+public interface LocaleProvider {
+ Locale getCurrent();
+}
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/IOService.java b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/IOService.java
new file mode 100644
index 00000000..5561be32
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/IOService.java
@@ -0,0 +1,8 @@
+package ru.otus.io.services;
+
+public interface IOService {
+
+ void outputString(String s);
+ void outputAsString(Object o);
+ String readString();
+}
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/LocalizationService.java b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/LocalizationService.java
new file mode 100644
index 00000000..b912ea2d
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/LocalizationService.java
@@ -0,0 +1,5 @@
+package ru.otus.io.services;
+
+public interface LocalizationService {
+ String getMessage(String key, Object ...args);
+}
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/LocalizationServiceImpl.java b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/LocalizationServiceImpl.java
new file mode 100644
index 00000000..80a82751
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/LocalizationServiceImpl.java
@@ -0,0 +1,20 @@
+package ru.otus.io.services;
+
+import org.springframework.context.MessageSource;
+import ru.otus.io.config.LocaleProvider;
+
+public class LocalizationServiceImpl implements LocalizationService {
+
+ private final LocaleProvider localeProvider;
+ private final MessageSource messageSource;
+
+ public LocalizationServiceImpl(LocaleProvider localeProvider, MessageSource messageSource) {
+ this.localeProvider = localeProvider;
+ this.messageSource = messageSource;
+ }
+
+ @Override
+ public String getMessage(String key, Object... args) {
+ return messageSource.getMessage(key, args, localeProvider.getCurrent());
+ }
+}
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/StreamsIOService.java b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/StreamsIOService.java
new file mode 100644
index 00000000..3d22a649
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/java/ru/otus/io/services/StreamsIOService.java
@@ -0,0 +1,31 @@
+package ru.otus.io.services;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Scanner;
+
+public class StreamsIOService implements IOService {
+
+ private final PrintStream ps;
+ private final Scanner sc;
+
+ public StreamsIOService(PrintStream ps, InputStream is) {
+ this.ps = ps;
+ this.sc = new Scanner(is);
+ }
+
+ @Override
+ public void outputString(String s) {
+ ps.println(s);
+ }
+
+ @Override
+ public void outputAsString(Object o) {
+ ps.println(o);
+ }
+
+ @Override
+ public String readString() {
+ return sc.nextLine();
+ }
+}
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/2023-07/spring-05-springBoot/io-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000..f98b216a
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+ru.otus.io.IOAutoconfiguration
\ No newline at end of file
diff --git a/2023-07/spring-05-springBoot/io-starter/src/main/resources/application.yaml b/2023-07/spring-05-springBoot/io-starter/src/main/resources/application.yaml
new file mode 100644
index 00000000..e2c091b0
--- /dev/null
+++ b/2023-07/spring-05-springBoot/io-starter/src/main/resources/application.yaml
@@ -0,0 +1,4 @@
+otus:
+ custom:
+ io-enabled: false
+ locale: en-US
\ No newline at end of file
diff --git a/2023-07/spring-05-springBoot/pom.xml b/2023-07/spring-05-springBoot/pom.xml
new file mode 100644
index 00000000..c7742646
--- /dev/null
+++ b/2023-07/spring-05-springBoot/pom.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-boot-intro
+ 1.0
+
+ pom
+
+
+ greeting-app
+ io-starter
+
+