hw03-spring-boot-template added

This commit is contained in:
stvort
2023-10-17 02:04:56 +04:00
parent cba25ab76d
commit 895b67826a
35 changed files with 674 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
target/
dependency-reduced-pom.xml
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
+93
View File
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>3.1.4</version>
<relativePath/>
</parent>
<groupId>ru.otus.hw</groupId>
<artifactId>hw03-spring-boot</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<opencsv.version>5.8</opencsv.version>
<lombok.version>1.18.28</lombok.version>
<checkstyle-plugin.version>3.2.2</checkstyle-plugin.version>
<checkstyle.version>10.11.0</checkstyle.version>
<checkstyle.config.url>
https://raw.githubusercontent.com/OtusTeam/Spring/master/checkstyle.xml
</checkstyle.config.url>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>${opencsv.version}</version>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>${lombok.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${checkstyle-plugin.version}</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle.version}</version>
</dependency>
</dependencies>
<configuration>
<configLocation>${checkstyle.config.url}</configLocation>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,15 @@
package ru.otus.hw;
import org.springframework.context.ApplicationContext;
import ru.otus.hw.service.TestRunnerService;
public class Application {
public static void main(String[] args) {
//Создать контекст Spring Boot приложения
ApplicationContext context = null;
var testRunnerService = context.getBean(TestRunnerService.class);
testRunnerService.run();
}
}
@@ -0,0 +1,32 @@
package ru.otus.hw.config;
import lombok.Data;
import java.util.Locale;
import java.util.Map;
@Data
// Использовать @ConfigurationProperties.
// Сейчас класс соответствует файлу настроек. Чтобы они сюда отобразились нужно только правильно разместить аннотации
public class AppConfig implements TestConfig, TestFileNameProvider, LocaleConfig {
private int rightAnswersCountToPass;
private Locale locale;
private Map<String, String> fileNameByLocaleTag;
public void setLocale(String locale) {
this.locale = Locale.forLanguageTag(locale);
}
@Override
public int getRightAnswersCountToPass() {
return rightAnswersCountToPass;
}
@Override
public String getTestFileName() {
return fileNameByLocaleTag.get(locale.toLanguageTag());
}
}
@@ -0,0 +1,7 @@
package ru.otus.hw.config;
import java.util.Locale;
public interface LocaleConfig {
Locale getLocale();
}
@@ -0,0 +1,5 @@
package ru.otus.hw.config;
public interface TestConfig {
int getRightAnswersCountToPass();
}
@@ -0,0 +1,5 @@
package ru.otus.hw.config;
public interface TestFileNameProvider {
String getTestFileName();
}
@@ -0,0 +1,24 @@
package ru.otus.hw.dao;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.otus.hw.config.TestFileNameProvider;
import ru.otus.hw.domain.Question;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor
@Component
public class CsvQuestionDao implements QuestionDao {
private final TestFileNameProvider fileNameProvider;
@Override
public List<Question> findAll() {
// Использовать CsvToBean
// https://opencsv.sourceforge.net/#collection_based_bean_fields_one_to_many_mappings
// Использовать QuestionReadException
return new ArrayList<>();
}
}
@@ -0,0 +1,9 @@
package ru.otus.hw.dao;
import ru.otus.hw.domain.Question;
import java.util.List;
public interface QuestionDao {
List<Question> findAll();
}
@@ -0,0 +1,13 @@
package ru.otus.hw.dao.dto;
import com.opencsv.bean.AbstractCsvConverter;
import ru.otus.hw.domain.Answer;
public class AnswerCsvConverter extends AbstractCsvConverter {
@Override
public Object convertToRead(String value) {
var valueArr = value.split("%");
return new Answer(valueArr[0], Boolean.parseBoolean(valueArr[1]));
}
}
@@ -0,0 +1,25 @@
package ru.otus.hw.dao.dto;
import com.opencsv.bean.CsvBindAndSplitByPosition;
import com.opencsv.bean.CsvBindByPosition;
import lombok.Data;
import ru.otus.hw.domain.Answer;
import ru.otus.hw.domain.Question;
import java.util.ArrayList;
import java.util.List;
@Data
public class QuestionDto {
@CsvBindByPosition(position = 0)
private String text;
@CsvBindAndSplitByPosition(position = 1, collectionType = ArrayList.class, elementType = Answer.class,
converter = AnswerCsvConverter.class, splitOn = "\\|")
private List<Answer> answers;
public Question toDomainObject() {
return new Question(text, answers);
}
}
@@ -0,0 +1,4 @@
package ru.otus.hw.domain;
public record Answer(String text, boolean isCorrect) {
}
@@ -0,0 +1,6 @@
package ru.otus.hw.domain;
import java.util.List;
public record Question(String text, List<Answer> answers) {
}
@@ -0,0 +1,7 @@
package ru.otus.hw.domain;
public record Student(String firstName, String lastName) {
public String getFullName() {
return String.format("%s %s", firstName, lastName);
}
}
@@ -0,0 +1,27 @@
package ru.otus.hw.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class TestResult {
private final Student student;
private final List<Question> answeredQuestions;
private int rightAnswersCount;
public TestResult(Student student) {
this.student = student;
this.answeredQuestions = new ArrayList<>();
}
public void applyAnswer(Question question, boolean isRightAnswer) {
answeredQuestions.add(question);
if (isRightAnswer) {
rightAnswersCount++;
}
}
}
@@ -0,0 +1,7 @@
package ru.otus.hw.exceptions;
public class QuestionReadException extends RuntimeException {
public QuestionReadException(String message, Throwable ex) {
super(message, ex);
}
}
@@ -0,0 +1,15 @@
package ru.otus.hw.service;
public interface IOService {
void printLine(String s);
void printFormattedLine(String s, Object ...args);
String readString();
String readStringWithPrompt(String prompt);
int readIntForRange(int min, int max, String errorMessage);
int readIntForRangeWithPrompt(int min, int max, String prompt, String errorMessage);
}
@@ -0,0 +1,13 @@
package ru.otus.hw.service;
public interface LocalizedIOService extends LocalizedMessagesService, IOService {
void printLineLocalized(String code);
void printFormattedLineLocalized(String code, Object ...args);
String readStringWithPromptLocalized(String promptCode);
int readIntForRangeLocalized(int min, int max, String errorMessageCode);
int readIntForRangeWithPromptLocalized(int min, int max, String promptCode, String errorMessageCode);
}
@@ -0,0 +1,76 @@
package ru.otus.hw.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class LocalizedIOServiceImpl implements LocalizedIOService {
private final LocalizedMessagesService localizedMessagesService;
private final IOService ioService;
@Override
public void printLine(String s) {
ioService.printLine(s);
}
@Override
public void printFormattedLine(String s, Object... args) {
ioService.printFormattedLine(s, args);
}
@Override
public String readString() {
return ioService.readString();
}
@Override
public String readStringWithPrompt(String prompt) {
return ioService.readStringWithPrompt(prompt);
}
@Override
public int readIntForRange(int min, int max, String errorMessage) {
return ioService.readIntForRange(min, max, errorMessage);
}
@Override
public int readIntForRangeWithPrompt(int min, int max, String prompt, String errorMessage) {
return ioService.readIntForRangeWithPrompt(min, max, prompt, errorMessage);
}
@Override
public void printLineLocalized(String code) {
ioService.printLine(localizedMessagesService.getMessage(code));
}
@Override
public void printFormattedLineLocalized(String code, Object... args) {
ioService.printLine(localizedMessagesService.getMessage(code, args));
}
@Override
public String readStringWithPromptLocalized(String promptCode) {
return ioService.readStringWithPrompt(localizedMessagesService.getMessage(promptCode));
}
@Override
public int readIntForRangeLocalized(int min, int max, String errorMessageCode) {
return ioService.readIntForRange(min, max, localizedMessagesService.getMessage(errorMessageCode));
}
@Override
public int readIntForRangeWithPromptLocalized(int min, int max, String promptCode, String errorMessageCode) {
return ioService.readIntForRangeWithPrompt(min, max,
localizedMessagesService.getMessage(promptCode),
localizedMessagesService.getMessage(errorMessageCode)
);
}
@Override
public String getMessage(String code, Object... args) {
return localizedMessagesService.getMessage(code, args);
}
}
@@ -0,0 +1,5 @@
package ru.otus.hw.service;
public interface LocalizedMessagesService {
String getMessage(String code, Object ...args);
}
@@ -0,0 +1,19 @@
package ru.otus.hw.service;
import lombok.RequiredArgsConstructor;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;
import ru.otus.hw.config.LocaleConfig;
@RequiredArgsConstructor
@Service
public class LocalizedMessagesServiceImpl implements LocalizedMessagesService {
private final LocaleConfig localeConfig;
// Доделать
@Override
public String getMessage(String code, Object... args) {
return null;
}
}
@@ -0,0 +1,7 @@
package ru.otus.hw.service;
import ru.otus.hw.domain.TestResult;
public interface ResultService {
void showResult(TestResult testResult);
}
@@ -0,0 +1,33 @@
package ru.otus.hw.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.otus.hw.config.TestConfig;
import ru.otus.hw.domain.TestResult;
@Service
@RequiredArgsConstructor
public class ResultServiceImpl implements ResultService {
private final TestConfig testConfig;
private final LocalizedIOService ioService;
@Override
public void showResult(TestResult testResult) {
ioService.printLine("");
ioService.printLineLocalized("ResultService.test.results");
ioService.printFormattedLineLocalized("ResultService.student",
testResult.getStudent().getFullName());
ioService.printFormattedLineLocalized("ResultService.answered.questions.count",
testResult.getAnsweredQuestions().size());
ioService.printFormattedLineLocalized("ResultService.right.answers.count",
testResult.getRightAnswersCount());
if (testResult.getRightAnswersCount() >= testConfig.getRightAnswersCountToPass()) {
ioService.printLineLocalized("ResultService.passed.test");
return;
}
ioService.printLineLocalized("ResultService.fail.test");
}
}
@@ -0,0 +1,68 @@
package ru.otus.hw.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Scanner;
@Service
public class StreamsIOService implements IOService {
private static final int MAX_ATTEMPTS = 10;
private final PrintStream printStream;
private final Scanner scanner;
public StreamsIOService(@Value("#{T(System).out}") PrintStream printStream,
@Value("#{T(System).in}") InputStream inputStream) {
this.printStream = printStream;
this.scanner = new Scanner(inputStream);
}
@Override
public void printLine(String s) {
printStream.println(s);
}
@Override
public void printFormattedLine(String s, Object... args) {
printStream.printf(s + "%n", args);
}
@Override
public String readString() {
return scanner.nextLine();
}
@Override
public String readStringWithPrompt(String prompt) {
printLine(prompt);
return scanner.nextLine();
}
@Override
public int readIntForRange(int min, int max, String errorMessage) {
for (int i = 0; i < MAX_ATTEMPTS; i++) {
try {
var stringValue = scanner.nextLine();
int intValue = Integer.parseInt(stringValue);
if (intValue < min || intValue > max) {
throw new IllegalArgumentException();
}
return intValue;
} catch (IllegalArgumentException e) {
printLine(errorMessage);
}
}
throw new IllegalArgumentException("Error during reading int value");
}
@Override
public int readIntForRangeWithPrompt(int min, int max, String prompt, String errorMessage) {
printLine(prompt);
return readIntForRange(min, max, errorMessage);
}
}
@@ -0,0 +1,8 @@
package ru.otus.hw.service;
import ru.otus.hw.domain.Student;
public interface StudentService {
Student determineCurrentStudent();
}
@@ -0,0 +1,19 @@
package ru.otus.hw.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.otus.hw.domain.Student;
@Service
@RequiredArgsConstructor
public class StudentServiceImpl implements StudentService {
private final LocalizedIOService ioService;
@Override
public Student determineCurrentStudent() {
var firstName = ioService.readStringWithPromptLocalized("StudentService.input.first.name");
var lastName = ioService.readStringWithPromptLocalized("StudentService.input.last.name");
return new Student(firstName, lastName);
}
}
@@ -0,0 +1,5 @@
package ru.otus.hw.service;
public interface TestRunnerService {
void run();
}
@@ -0,0 +1,22 @@
package ru.otus.hw.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class TestRunnerServiceImpl implements TestRunnerService {
private final TestService testService;
private final StudentService studentService;
private final ResultService resultService;
@Override
public void run() {
var student = studentService.determineCurrentStudent();
var testResult = testService.executeTestFor(student);
resultService.showResult(testResult);
}
}
@@ -0,0 +1,8 @@
package ru.otus.hw.service;
import ru.otus.hw.domain.Student;
import ru.otus.hw.domain.TestResult;
public interface TestService {
TestResult executeTestFor(Student student);
}
@@ -0,0 +1,33 @@
package ru.otus.hw.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.otus.hw.dao.QuestionDao;
import ru.otus.hw.domain.Student;
import ru.otus.hw.domain.TestResult;
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {
private final LocalizedIOService ioService;
private final QuestionDao questionDao;
@Override
public TestResult executeTestFor(Student student) {
ioService.printLine("");
ioService.printLineLocalized("TestService.answer.the.questions");
ioService.printLine("");
var questions = questionDao.findAll();
var testResult = new TestResult(student);
for (var question: questions) {
var isAnswerValid = false; // Задать вопрос, получить ответ
testResult.applyAnswer(question, isAnswerValid);
}
return testResult;
}
}
@@ -0,0 +1,13 @@
spring:
messages:
baseName: messages
encoding: UTF-8
# Не дает свалиться в системную локаль если не найден файл для переданной в MessageSource
# Если false то будут браться сообщения из дефолтного файла, если true, то из файла соответствующего системной локали
fallbackToSystemLocale: false
test:
rightAnswersCountToPass: 3
locale: en-US
fileNameByLocaleTag:
ru-RU: questions_ru.csv
en-US: questions.csv
@@ -0,0 +1,9 @@
ResultService.test.results=Test results:
ResultService.student=Student: {0}
ResultService.answered.questions.count=Answered questions count: {0}
ResultService.right.answers.count=Right answers count: {0}
ResultService.passed.test=Congratulations! You passed test!
ResultService.fail.test=Sorry. You fail test.
StudentService.input.first.name=Please input your first name
StudentService.input.last.name=Please input your last name
TestService.answer.the.questions=Please answer the questions below
@@ -0,0 +1,9 @@
ResultService.test.results=\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0442\u0435\u0441\u0442\u0430:
ResultService.student=\u0421\u0442\u0443\u0434\u0435\u043D\u0442: {0}
ResultService.answered.questions.count=\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u043E\u0442\u0432\u0435\u0447\u0435\u043D\u043D\u044B\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432: {0}
ResultService.right.answers.count=\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u043E\u0442\u0432\u0435\u0447\u0435\u043D\u043D\u044B\u0445 \u0432\u0435\u0440\u043D\u043E: {0}
ResultService.passed.test=\u041F\u043E\u0437\u0434\u0440\u0430\u0432\u043B\u044F\u0435\u043C! \u0412\u044B \u043F\u0440\u043E\u0448\u043B\u0438 \u0442\u0435\u0441\u0442!
ResultService.fail.test=\u041F\u0440\u043E\u0441\u0442\u0438\u0442\u0435. \u0412\u044B \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0438 \u0442\u0435\u0441\u0442
StudentService.input.first.name=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u0432\u043E\u0435 \u0438\u043C\u044F
StudentService.input.last.name=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u0432\u043E\u044E \u0444\u0430\u043C\u0438\u043B\u0438\u044E
TestService.answer.the.questions=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043E\u0442\u0432\u0435\u0442\u044C\u0442\u0435 \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u043D\u0438\u0436\u0435
@@ -0,0 +1,4 @@
# Добавить сюда своих вопросов. Эту строку надо пропустить при настройке CsvToBean (withSkipLines)
Is there life on Mars?;Science doesn't know this yet%true|Certainly. The red UFO is from Mars. And green is from Venus%false|Absolutely not%false
How should resources be loaded form jar in Java?;ClassLoader#geResourceAsStream or ClassPathResource#getInputStream%true|ClassLoader#geResource#getFile + FileReader%false|Wingardium Leviosa%false
Which option is a good way to handle the exception?;@SneakyThrow%false|e.printStackTrace()%false|Rethrow with wrapping in business exception (for example, QuestionReadException)%true|Ignoring exception%false
1 # Добавить сюда своих вопросов. Эту строку надо пропустить при настройке CsvToBean (withSkipLines)
2 Is there life on Mars?;Science doesn't know this yet%true|Certainly. The red UFO is from Mars. And green is from Venus%false|Absolutely not%false
3 How should resources be loaded form jar in Java?;ClassLoader#geResourceAsStream or ClassPathResource#getInputStream%true|ClassLoader#geResource#getFile + FileReader%false|Wingardium Leviosa%false
4 Which option is a good way to handle the exception?;@SneakyThrow%false|e.printStackTrace()%false|Rethrow with wrapping in business exception (for example, QuestionReadException)%true|Ignoring exception%false
@@ -0,0 +1,4 @@
# Добавить сюда своих вопросов. Эту строку надо пропустить при настройке CsvToBean (withSkipLines)
Есть ли жизнь на Марсе?;Это науке пока не известно%true|Конечно. Красные (НЛО) это с Марса. А зеленые (НЛО) с Венеры%false|Точно нет%false
Как нужно загружать ресурс из jar в Java?;ClassLoader#geResourceAsStream или ClassPathResource#getInputStream%true|ClassLoader#geResource#getFile + FileReader%false|Wingardium Leviosa%false
Какой предпочтительный способ реакции на исключения?;@SneakyThrow%false|e.printStackTrace()%false|Проброс дальше с предварительным оборачиванием в бизнес-исключение (например, QuestionReadException)%true|Игнорирование исключения%false
1 # Добавить сюда своих вопросов. Эту строку надо пропустить при настройке CsvToBean (withSkipLines)
2 Есть ли жизнь на Марсе?;Это науке пока не известно%true|Конечно. Красные (НЛО) это с Марса. А зеленые (НЛО) с Венеры%false|Точно нет%false
3 Как нужно загружать ресурс из jar в Java?;ClassLoader#geResourceAsStream или ClassPathResource#getInputStream%true|ClassLoader#geResource#getFile + FileReader%false|Wingardium Leviosa%false
4 Какой предпочтительный способ реакции на исключения?;@SneakyThrow%false|e.printStackTrace()%false|Проброс дальше с предварительным оборачиванием в бизнес-исключение (например, QuestionReadException)%true|Игнорирование исключения%false