diff --git a/README.md b/README.md
index 228b708b7094..fff4d41c9f20 100644
--- a/README.md
+++ b/README.md
@@ -19,17 +19,21 @@ Java Enterprise Online Project
Вводное занятие (обязательно смотреть все видео)
===============
-##  1. Осваиваем Java Enterprise. Трудоустройство. Ответы на вопросы.
+##  1. Осваиваем Java Enterprise. Трудоустройство. Ответы на вопросы.
- Слайды презентации
-- Java Tools and Technologies Landscape Report 2016
-- [Java in 2017 Survey](http://www.baeldung.com/java-in-2017)
-- Из юниоров в разработчики: получаем первую работу
+- [Как стать профессиональным Java разработчиком](https://www.youtube.com/watch?v=ft0Nj8Cm9kk)
+- [Популярность Java-технологий в 2019 году](https://topjava.ru/blog/sostoyanie-java-v-2019-godu)
+- [Java Technology Report 2021](https://www.jrebel.com/blog/2021-java-technology-report)
+- [The State of Developer Ecosystem 2020](https://www.jetbrains.com/lp/devecosystem-2020/java/)
+- [JVM Ecosystem Report 2021](https://snyk.io/jvm-ecosystem-report-2021/)
+- [Быть программистом: от детства к зрелости](https://www.youtube.com/watch?v=D5Hej0TyLaU)
+- [Литература](https://javaops.ru/view/books)
#### Spring Pet-Clinic
- Spring PetClinic Sample Application
- Presentation
-##  2. Системы управления версиями. Git.
+##  2. Системы управления версиями. Git.
- **Wiki по ведению проекта в Git**
- Система управления версиями. VCS/DVSC.
- Ресурсы:
@@ -41,9 +45,16 @@ Java Enterprise Online Project
- Git Overview
- [Основы Git за 20 минут](https://www.youtube.com/watch?v=TMeZGvtQnT8)
- [Git - для новичков](https://www.youtube.com/watch?list=PLY4rE9dstrJyTdVJpv7FibSaXB4BHPInb&v=PEKN8NtBDQ0)
+ - [Руководство по написанию комментариев в коммитах](https://techrocks.ru/2019/12/02/writing-good-commit-messages)
+
+##  3. Работа с проектом (выполнять инструкции)
+- **ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы**
+- **Плагин уже Git Intergation не требуется и вкладку `Version control` в IDEA переименовали в `Git`**
+
+Для переключения режима отображения изменений из вкладки Commit в Git: Local Changes нужно переключить `Settings/Preferences | Version Control | Commit | Use non-modal commit interface` или в контекстном меню вкладки `Commit`:
+
+ 
-##  3. Работа с проектом (выполнять инструкции)
-**ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы**
### Патч [prepare_to_HW0.patch](https://drive.google.com/file/d/1LNPpu9OkuCpfpD8ZJHO-o0vwu49p2i5M) (скачать и положить в каталог вашего проекта)
> Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео:
@@ -51,7 +62,7 @@ Java Enterprise Online Project
> - в `UserMeals/UserMealWithExcess` поля изменились на `private`
> - обновил данные `UserMealsUtil.meals` и переименовал некоторые пременные, поля и методы
> - добавил `UserMealWithExcess.toString()` и метод для выполнения _Optional домашнего задания_
-
+> - метод фильтрации в `TimeUtil` переименовали в `isBetweenHalfOpen` (также изменилась логика сравнения - `startTime` включается в интервал)
## Инструкция по шагам (из видео):
- Установить ПО (git, JDK8, IntelliJ IDEA, Maven)
@@ -69,8 +80,11 @@ Java Enterprise Online Project
- Выполнить задание и залить на GitHub (commit + push)
- Переключиться в основную ветку проекта master.
+##  4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/1BpTzjNFjS0TSekCyt_xvt6YoLvuw5KTZ/view?usp=sharing)
+- [Типы промежутков](https://ru.wikipedia.org/wiki/Промежуток_(математика))
+
##  Домашнее задание HW0
-```
+
Реализовать метод `UserMealsUtil.filteredByCycles` через циклы (`forEach`):
- должны возвращаться только записи между `startTime` и `endTime`
- поле `UserMealWithExcess.excess` должно показывать,
@@ -78,13 +92,14 @@ Java Enterprise Online Project
Т.е `UserMealWithExcess` - это запись одной еды, но поле `excess` будет одинаково для всех записей за этот день.
-- Проверьте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды)
-- Оцените Time complexity алгоритма. Если она больше O(N), например O(N*N) или N*log(N), сделайте O(N).
-```
+> - Проверьте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды)
+> - Оцените Time complexity алгоритма. Если она больше O(N), например O(N*N) или N*log(N), сделайте O(N).
+> **Внимание: внимательно прочитайте про O(N). O - это любой коэффициент, 2*N это тоже O(N).**
+
- Java 8 Date and Time API
-- Алгоритмы и структуры данных для начинающих: сложность алгоритмов
+- Алгоритмы и структуры данных для начинающих: сложность алгоритмов
- [Головач: сложность алгоритмов в теме коллекций](https://www.youtube.com/watch?v=Ek9ijOiplNE&feature=youtu.be&t=778)
-- Time complexity
+- Time complexity
- Временная сложность алгоритма
- Вычислительная сложность
@@ -98,27 +113,31 @@ Java Enterprise Online Project
- Java 8: Lambda выражения
- Java 8: Потоки
- Pуководство по Java 8 Stream
-- Java 8 Stream API в картинках и примерах
+- [Полное руководство по Java 8 Stream API в картинках и примерах](https://annimon.com/article/2778)
- [7 способов использовать groupingBy в Stream API](https://habrahabr.ru/post/348536)
- Лямбда-выражения в Java 8
- A Guide to Java 8
- Шпаргалка Java Stream API
- Алексея Владыкин: Элементы функционального программирования в Java
- Yakov Fain о новом в Java 8
-- stream.map vs forEach
+- stream.map vs forEach`
- - без циклов по другим коллекциям
- - решение должно быть рабочим в общем случае (не только при запуске main)
-- через Stream API за 1 проход по исходному списку `meals.streem()`
- - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (его копиям).
- Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах)
- - возможно дополнительные проходы по частям списка
+ - без циклов по другим коллекциям/массивам (к ним также относим методы коллекций `addAll()/removeAll()`)
+- через Stream API за 1 проход по исходному списку `meals.stream()`
+ - нельзя использовать внешние коллекции, не являющиеся частью коллектора
+ - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента.
+
+Ресурсы:
+- [Java 8 Stream API, часть шестая: собственный коллектор](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor)
+- [Руководство по Java 8 Stream API: Collector](https://annimon.com/article/2778#collector)
### Замечания по использованию Stream API:
- Когда встречаешь что-то непривычное, приходится перестраивать мозги. Например, переход с процедурного на ООП программирование дается непросто. Те, кто не знает шаблонов (и не хотят учить) также их встречают плохо. Хорошая новость в том, что если это принять и начать использовать, то начинаешь получать от этого удовольствие. И тут главное не впасть в другую крайность:
@@ -133,7 +152,7 @@ Java Enterprise Online Project
##  Замечания к HW0
- 1: Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно при правках в рабочем проекте обязательно нужно проконсультироваться/проревьюироваться у авторов кода (находится по истории VCS)
-- 2: Наследовать `UserMealWithExcess` от `UserMeal` я не буду, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке.
+- 2: Наследовать `UserMealWithExcess` от `UserMeal` нельзя, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. Это относится и к зависимости.
- 3: Правильная реализация должна быть простой и красивой, можно сделать 2-мя способами: через стримы и через циклы. Сложность должна быть O(N), т.е. без вложенных стримов и циклов.
- 4: При реализации через циклы посмотрите в `Map` на методы `getOrDefault` или `merge`
- 5: **При реализации через `Stream` заменяйте `forEach` оператором `stream.map(..)`**
@@ -144,17 +163,14 @@ Java Enterprise Online Project
- 10: `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование.
- 11: Результаты, возвращаемые `UserMealsUtil.filteredByStreams` мы будем использовать [в нашем приложении](http://topjava.herokuapp.com/) для фильтрации по времени и отображения еды правильным цветом.
- 12: Обращайте внимание на комментарии к вашим коммитам в git. Они должны быть короткие и информативные (лучше на english)
-- 13: Не полагайтесь в решении на то, что список будет подаваться отсортированным. Такого условия нет.
+- 13: Не полагайтесь в решении на то, что список еды будет подаваться отсортированным. Такого условия нет.
-----
## [Пример 7-го занятия онлайн стажировки, несколько видео открыто](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md)
-### Полезные ресурсы
-> ВНИМАНИЕ:
-> - **ДЗ первого урока будет связано с [созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах](http://danielniko.com/2012/04/17/simple-crud-using-jsp-servlet-and-mysql/)**. Введение будет, но предварительное знакомство не помешает.
-> - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия!
-
-Все остальное - опционально.
+> - ДЗ первого урока будет связано с созданием небольшого [CRUD](https://ru.wikipedia.org/wiki/CRUD) приложения (в памяти, без базы данных) на JSP и сервлетах
+> - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия
+### Полезные ресурсы
#### HTML, JavaScript, CSS
- [Basic HTML and HTML5](https://learn.freecodecamp.org/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements/)
- [Справочник по WEB](https://developer.mozilla.org/ru/)
@@ -172,17 +188,18 @@ Java Enterprise Online Project
#### Java (базовые вещи)
- Интуит. Программирование на Java
- 1й урок MasterJava: Многопоточность
-- Основы Java garbage collection
+- [Основы Java garbage collection](http://web.archive.org/web/20180831013112/https://ggenikus.github.io/blog/2014/05/04/gc)
- Размер Java объектов
- Введение в Java Reflection API
- Структуры данных в картинках
- Обзор java.util.concurrent.*
-- Синхронизация потоков
+- Синхронизация потоков
- String literal pool
- Маленькие хитрости Java
- A Guide to Java 8
### Туториалы, разное
+- [Открытый курс: Spring Boot + HATEOAS](https://javaops.ru/view/bootjava)
- [Что нужно знать о бэкенде новичку в веб-разработке](https://tproger.ru/translations/backend-web-development)
- [Туториалы: Spring Framework, Hibernate, Java Core, JDBC](http://proselyte.net/tutorials/)
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index f4e7412eb816..68ebc2ef69d7 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1,8 +1,62 @@
# TopJava Release Notes
+
+### Topjava 23
+- migrate to JDK 16
+- в новой spring-data-jpa `getOne` заменили на `getById`
+- в UserUtil#prepareToSave убрал проверку пароля на `hasText`. На UI поле проверяется на `@NotBlank`
+- `ProfileRestController#register` делаю по правилам REST (POST без "/register")
+- css стили `data-...` сделал [low-case через дефисы](https://stackoverflow.com/questions/36176474/548473)
+- `TestMatcher` переименовал в `MatcherFactory`
+- Для Swagger UI пометил `AuthorizedUser` аннотацией `@ApiIgnore`
+
+### Topjava 22
+ - очистка пароля `AuthorizedUser#userTo`
+ - заменил `@SafeHtml`, который удалили из `hibernate.validator` на [Jsoup.clean](https://stackoverflow.com/a/68888601/548473)
+ - перенес запрет на обновление admin/user в `UserService`
+ - проверку email на уникальность для update с `id=null` в теле запроса сделал на основе анализа `HttpServletRequest.getRequestURI()`
+ - проверку класса в `classpath` в `Profiles#getActiveDbProfile` делаю на `org.springframework.util.ClassUtils#isPresent`
+ - удалил `type="text/javascript"`
+
+### Topjava 21
+- **добавили документирование REST API: Swagger**
+- мигрировали на JDK 15 и используем текстовые блоки
+- Вынес `produces = MediaType.APPLICATION_JSON_VALUE` на уровень контроллеров
+- Правильно используем [глабальные переменные в js](https://stackoverflow.com/a/5064235/548473)
+- Зарефакторил `inputField.tag`
+- Тестовые переменные переименовал из UPPERCASE в camelCase
+- Из тестов сервисов убрал `throws Exception` (в IDEA больше не генерятся по умолчанию)
+- **Мигрировали на Spring Boot 2.4.1**
+
+### Topjava 20
+- мигрировали на JDK 14
+- в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`)
+- в `topjava.common.js` в `makeEditable()` вместо объекта контекст передаю 3 параметра
+- в UI контроллерах убрал префикс `ajax`
+- из тестов сервисов убрал `repository`. При проверке через `assertThrows` он не требуется
+- в `TestMatcher` сценарии сравнения сделал параметризируемыми (паттерн стратегия)
+- в API добавили `/users/{id}/with-meals` (см. [двунаправленные отношения](https://www.codeflow.site/ru/article/jackson-bidirectional-relationships-and-infinite-recursion))
+- добавил `UserTestData.USER_WITH_MEALS_MATCHER` (проверки пользователя сразу с едой) и константу id `NOT_FOUND`
+
+### Topjava 19
+- Изменилась логика для интервалов времени (исключаем `endTime`)
+- Заменил собственный `MessageUtil` велосипед на спринговый `MessageSourceAccessor`
+- В ролях убрал префиксы `ROLE_` ([Role and GrantedAuthority](https://stackoverflow.com/a/19542316/548473))
+- Добавился удобный метод `int AbstractBaseEntity.id()`
+- Фикс `Location` в `ProfileRestController.register`
+- Фикс валидации `UniqueMailValidator` для REST update без `user.id`
+- Заменил `jdbc.initLocation` на полный путь - IDEA не ругается
+- В конфигурации `cargo-maven2-plugin` сделал [индивидуальный контекст приложения](https://stackoverflow.com/a/60797999/548473)
+- Тесты
+ - Обновил даты еды на 2020г.
+ - Зарефакторил тесты сервисов на удаление - `NotFoundException` может бросаться при `delete()`
+ - В тестах контроллеров вернулся к реализации без обертки над `MockMvcRequestBuilders`
+ - Для `InMemory` тестов подключаю только `inmemory.xml` (добавил туда необходимую конфигурацию из `spring-app.xml`)
+
+
### Topjava 18
- В `ErrorType` добавил `HttpStatus status`
-- В PostgreSQL обнаружилась бага: граничное значение `0:00` из за ошибок округления попадает в предыдущий интервал.
+- В PostgreSQL обнаружилась бага: граничное значение `0:00` из-за ошибок округления попадает в предыдущий интервал.
Мораль: всегда в тестах проверяйте граничные значения. Добавил этот случай в тестовые данные.
- Изменил `MealRepository.getBetween` (принимаю `@Nullable LocalDate`). Изменились реализации.
- Выделил метод `UserService.prepareAndSave`
diff --git a/cv.md b/cv.md
index a24d881fdeba..910b9ad88ab6 100644
--- a/cv.md
+++ b/cv.md
@@ -3,7 +3,7 @@

- Научиться программировать сложнее, чем кажется
-- [Собеседование. Разработка ПО. Вопросы.](https://drive.google.com/open?id=0B9Ye2auQ_NsFQVc2WUdCR0xvLWM)
+- [Собеседование. Разработка ПО. Вопросы.](https://drive.google.com/file/d/0B9Ye2auQ_NsFQVc2WUdCR0xvLWM/view?usp=sharing&resourcekey=0-HaWoRxoyboMSKjg5P2I1cQ)
- [Набор ссылок для тренировки и прохождения интервью](https://github.com/andreis/interview)
### Составление резюме:
@@ -20,6 +20,7 @@
### Наши истории (делимся опытом и успехом)
### Тесты/задачи онлайн:
+- [Interviewing: the most profitable skill you can learn (pramp.com)](https://www.pramp.com/)
- [Java Programming Test](https://tests4geeks.com/java)
- game: test Java skills
- Codility lesson tests
@@ -28,7 +29,6 @@
- Sphere online judge
- Codility programmers lessons
- Hackerrank practice coding
-- [Interviewing: the most profitable skill you can learn (pramp.com)](https://www.pramp.com/)
- [start.interviewing.io](https://start.interviewing.io/)
## [Тестовое собеседование, самые спрашиваемые темы](http://javaops.ru/interview/test.html)
@@ -37,6 +37,8 @@
- Михаил Портнов. Собеседование на работу: как продать себя грамотно
- Михаил Портнов. Какие вопросы мы задаем на собеседовании?
- Михаил Портнов. Собеседование на работу: жизненный путь
+- [Лёша Корепанов. Признаки плохих компаний для программиста](https://www.youtube.com/watch?v=Sj-WSWr-n7U)
+- [Лёша Корепанов. Как отвечать на вопросы, которые ты не знаешь. Техническое интервью для программиста](https://www.youtube.com/watch?v=Beoh3tfgPEk)
- Канал: Резюме, поиск работы, интервью
- Яков Файн: Как стать профессиональным Java разработчиком
- Ответы на вопросы на собеседовании Junior Java Developer
@@ -48,7 +50,7 @@
- Тест на знание SQL
- Вопросы на собеседовании Java Junior Developer
- Java вопросы с собеседований на Android
-- Сборка вопросов от JavaRush
+- Сборка вопросов от JavaRush
> про clone и finalize объязательно прочтите Джошуа Блох: Java. Эффективное программирование (второе издание)
- Cracking the Coding Interview
@@ -88,9 +90,13 @@
- Яндекс агрегатор
- HH
- LinkedIn
+- ХабрКарьера
+- [headz.io](https://app.headz.io/candidates/new)
- djinni.co (более актуально для Украины)
-## Как выжить на испытательном сроке
+[Как изучать Java. Подборка от JavaRush](https://javarush.ru/groups/posts/3538-v-zakladki-kak-izuchatjh-java-boljhshaja-podborka-po-planu-obuchenija-instrumentam-i-poiskam-mo)
+
Как выжить на испытательном сроке
+
- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В
процессе формулирования очень часто приходит ее решение.
- Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки
@@ -106,6 +112,13 @@
- Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи.
- [**5 вещей, которые разработчик должен сделать прежде чем попросить о помощи**](https://techrocks.ru/2018/07/16/5-things-a-developer-should-do-before-asking-for-help/)
- [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer)
+- [ТОП-13 ошибок начинающего программиста](https://proglib.io/p/beginners-fails/)
+- [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/)
+- [Путеводитель по синдрому самозванца](https://vc.ru/hr/167443-eshche-odin-putevoditel-po-sindromu-samozvanca-korni-prichiny-simptomy-i-posledstviya-chast-1)
- [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job)
-
+- Видео [Junior и испытательный срок на первой работе](https://www.youtube.com/watch?v=GsGlsCbok-c)
+- Типичные ошибки начинающих программистов от JavaRush:
+ - [Часть 1](https://javarush.ru/groups/posts/3044-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-1)
+ - [Часть 2](https://javarush.ru/groups/posts/3055-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-2)
+- [От джуна к миддлу: практические советы](https://tproger.ru/articles/ot-dzhuna-k-middlu-prakticheskie-sovety)
## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246)
diff --git a/description.md b/description.md
index d2448ca99e57..e3626957efc7 100644
--- a/description.md
+++ b/description.md
@@ -1,77 +1,80 @@
#### Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API и сохранением в базах данных Postgresql и HSQLDB.
-- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения.
-- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий.
-- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit тестов.
-- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой
+- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения.
+- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий.
+- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit тестов.
+- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например, мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой
Hibernate, делаем конверторы для типов LocalDateTime (Java 8 time API).
- Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object.
- Т.е на выходе получается не учебный проект, а хорошо маштабируемый шаблон для большого проекта на всех пройденных технологиях.
-- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST
- контроллеров, насторойка EntityManagerFactory,
- выбор реализации пула коннектов. Особое внимание уделяется работе с базой: через Spring JDBC, Spring ORM и
- Spring Data Jpa.
-- Используются самые востребованные на сегодняшний момент фреймворки: Maven, Spring Security 4
- вместе с Spring Security Test, наиболее удобный для работы с базой проект Spring Data Jpa, библиотека логирования logback, реализующая SLF4J, повсеместно используемый Bootstrap и jQuery.
+- Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object. То есть на выходе получается не учебный проект, а хорошо масштабируемый шаблон для большого проекта на всех пройденных технологиях.
+- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST контроллеров, настройка EntityManagerFactory, выбор реализации пула коннектов. Особое внимание уделяется работе с базой: через Spring JDBC, Spring ORM и Spring Data Jpa.
+- Используются самые востребованные на сегодняшний момент фреймворки: Maven, Spring Security 4 вместе с Spring Security Test, наиболее удобный для работы с базой проекта Spring Data Jpa, библиотека логирования logback, реализующая SLF4J, повсеместно используемый Bootstrap и jQuery.
#### Демо разрабатываемого приложения
## План проекта (ссылки на некоторые темы открыты для просмотра)
### Архитектура проекта. Персистентность.
-- Системы управления версиями
-- Java 8: Lambda, Stream API
-- Обзор используемых в проекте технологий и инструментов.
-- Инструмент сборки Maven.
-- WAR. Веб-контейнер Tomcat. Сервлеты.
-- Логирование.
-- Обзор стандартных библиотек. Apache Commons, Guava
-- Слои приложения. Создание каркаса приложения.
-- Обзор Spring Framework. Spring Context.
-- Тестирование через JUnit.
-- Spring Test
-- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM.
-- Настройка Database в IDEA.
-- Скрипты инициализации базы. Spring Jdbc Template.
-- Spring: инициализация и популирование DB
-- ORM. Hibernate. JPA.
+- Системы управления версиями
+- Java 8: Lambda, Stream API
+- Обзор используемых в проекте технологий и инструментов.
+- Инструмент сборки Maven
+- WAR. Веб-контейнер Tomcat. Сервлеты.
+- Логирование.
+- Обзор стандартных библиотек. Apache Commons, Guava
+- Слои приложения. Создание каркаса приложения.
+- Обзор Spring Framework. Spring Context.
+- Тестирование через JUnit.
+- Spring Test
+- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM.
+- Настройка Database в IDEA.
+- Скрипты инициализации базы. Spring Jdbc Template.
+- Spring: инициализация и популирование DB
+- ORM. Hibernate. JPA.
- [Тестирование JPA сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw)
-- Поддержка HSQLDB
-- Транзакции
-- Профили Maven и Spring
-- Пул коннектов
-- Spring Data JPA
-- Кэш Hibernate
+- Поддержка HSQLDB
+- Транзакции
+- Профили Maven и Spring
+- Пул коннектов
+- Spring Data JPA
+- Кэш Hibernate
### Разработка WEB
-- Spring кэш
-- Spring Web
-- JSP, JSTL, i18n
-- Tomcat maven plugin. JNDI
-- Spring Web MVC
-- Spring Internationalization
-- Тестирование Spring MVC
-- REST контроллеры
-- Тестирование REST контроллеров. Jackson.
-- jackson-datatype-hibernate. Тестирование через матчеры.
-- Тестирование через SoapUi. UTF-8
-- WebJars.
-- Bootstrap. jQuery datatables.
-- AJAX. jQuery. Notifications.
-- Spring Security
-- Spring Binding/Validation
-- Работа с datatables через Ajax.
-- Spring Security Test
-- [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/open?id=0B9Ye2auQ_NsFRTFsTjVHR2dXczA)
-- Encoding password
-- CSRF (добавление в проект защиты от межсайтовой подделки запроса)
-- form-login. Spring Security Taglib
-- Handler interceptor
-- Spring Exception Handling
-- Смена локали
-- Фильтрация JSON через @JsonView
-- Защита от XSS (Cross Site Scripting)
-- Деплой в Heroku
-- Локализация datatables, ошибок валидации
-- Обработка ошибок 404 (NotFound)
-- Доступ к AuthorizedUser
-- Собеседование. Разработка ПО
+- Spring кэш
+- Spring Web
+- JSP, JSTL, i18n
+- Tomcat maven plugin. JNDI
+- Spring Web MVC
+- Spring Internationalization
+- Тестирование Spring MVC
+- REST контроллеры
+- Тестирование REST контроллеров. Jackson.
+- jackson-datatype-hibernate. Тестирование через матчеры.
+- Тестирование через SoapUi. UTF-8
+- WebJars.
+- Bootstrap. jQuery datatables.
+- AJAX. jQuery. Notifications.
+- Spring Security
+- Spring Binding/Validation
+- Работа с datatables через Ajax.
+- Spring Security Test
+- [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/file/d/0B9Ye2auQ_NsFRTFsTjVHR2dXczA/view?usp=sharing&resourcekey=0-Ou4A_gRor5HaRho4Fciqdw)
+- Encoding password
+- CSRF (добавление в проект защиты от межсайтовой подделки запроса)
+- form-login. Spring Security Taglib
+- Handler interceptor
+- Spring Exception Handling
+- Смена локали
+- Фильтрация JSON с помощью @JsonView
+- Защита от XSS (Cross Site Scripting)
+- Деплой в Heroku
+- Локализация datatables, ошибок валидации
+- Обработка ошибок 404 (NotFound)
+- Доступ к AuthorizedUser
+- Собеседование. Разработка ПО
+
+### Миграция на Spring Boot
+- Основы Spring Boot. Spring Boot maven plugin
+- Lombok, база H2, ApplicationRunner
+- Spring Data REST + HATEOAS
+- Swagger/ OpenAPI 3.0
+- Тестирование и кэширование в Spring Boot
+- Миграция приложения TopJava на Spring Boot
diff --git a/doc/lesson07.md b/doc/lesson07.md
index c4f4992a61df..1a3b1fe72692 100644
--- a/doc/lesson07.md
+++ b/doc/lesson07.md
@@ -2,7 +2,7 @@
## [Почему мы?](http://javaops.ru/#why)
## REST, REST контроллеры, тестирование Spring MVC контроллеров
-# Для просмотра открыты видео [4](#--4-миграция-на-junit-5), [5](#-5-принципы-rest-rest-контроллеры), [6](#-6-тестирование-rest-контроллеров-jackson), [7](#-7-кастомизация-jackson-object-mapper), [8](#-8-тестирование-rest-контроллеров-через-jsonassert)
+# Для просмотра открыты видео [4](#--4-миграция-на-junit-5), [5](#-5-принципы-rest-rest-контроллеры), [6](#-6-тестирование-rest-контроллеров-jackson), [7](#-7-кастомизация-jackson-object-mapper), [8](#user-content--8-тестирование-rest-контроллеров-через-jsonassert-и-матчеры)
- Не стоит стремиться прочитать все ссылки урока, их можно использовать как справочник. Гораздо важнее пройти основной материал урока и сделать Домашнее Задание
- Обязательно посмотри правила работы с патчами на проекте
- Делать Apply Patch лучше по одному, непосредственно перед видео на эту тему, а при просмотре видео сразу отслеживать все изменения кода проекта по изменению в патче (`Version Control->Local Changes-> Ctrl+D`)
@@ -13,59 +13,171 @@
###  1. HW6
#### Apply 7_01_HW6_fix_tests.patch
-> - Добавил `AbstractServiceTest.isJpaBased()` и `Assume.assumeTrue(isJpaBased())` в `AbstractMealServiceTest.testValidation()`.
-> - Как вариант можно было вместо наследования от `AbstractJpaUserServiceTest` сделать `@Autowired(required = false) JpaUtil` и чистить кэш по условию `isJpaBased()`.
-> - В новой версии Spring классы `spring-mvc` требуют `WebApplicationContext`, поэтому поправил `inmemory.xml`
#### Apply 7_02_HW6_meals.patch
-При переходе на AJAX `JspMealController` удалим за ненадобностью, возвращение всей еды `meals()` останется в `RootController`.
+
+> сделал фильтрацию еды через `get`: операция идемпотентная, можно делать в браузере обновление по F5
+
+### Внимание: чиним пути в следующем патче
#### Apply 7_03_HW6_fix_relative_url_utf8.patch
-- Relative paths in JSP
-- Spring redirect: prefix
+
+-
+ Relative paths in JSP
+-
+ Spring redirect: prefix
###  2. HW6 Optional
+
#### Apply 7_04_HW6_optional_add_role.patch
-`JdbcUserServiceTest` отвалились. Будем чинить в `7_06_HW6_optional_jdbc.patch`
+
+#### `JdbcUserServiceTest` отвалились. Будем чинить в `7_06_HW6_jdbc_transaction_roles.patch`
#### Apply 7_05_fix_hint_graph.patch
-- В `JpaUserRepositoryImpl.getByEmail` DISTINCT попадает в запрос, хотя он там не нужен. Это просто указание Hibernate не дублировать данные.
-Для оптимизации можно указать Hibernate делать запрос без distinct: [15.16.2. Using DISTINCT with entity queries](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct)
- - Бага [HINT_PASS_DISTINCT_THROUGH does not work if 'hibernate.use_sql_comments=true'](https://hibernate.atlassian.net/browse/HHH-13280). При `hibernate.use_sql_comments=false` все работает- в SELECT нет DISTINCT.
-- Тест `DataJpaUserServiceTest.testGetWithMeals()` не работает для admin (у админа 2 роли, и еда при JOIN дублируется). `DISTINCT` при нескольких JOIN не помогает.
-Оставил в графе только `meals`. Корректно поставить тип `LOAD`, чтобы остальные ассоциации доставались по стратегии модели. Однако [с типом по умолчанию `FETCH` роли также достаются](https://stackoverflow.com/a/46013654/548473) (похоже, что бага).
+- В `JpaUserRepositoryImpl.getByEmail` DISTINCT попадает в запрос, хотя он там не нужен. Это просто указание Hibernate
+ не дублировать данные. Для оптимизации можно указать Hibernate делать запрос без
+ distinct: [15.16.2. Using DISTINCT with entity queries](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct)
+- Бага [HINT_PASS_DISTINCT_THROUGH does not work if 'hibernate.use_sql_comments=true'](https://hibernate.atlassian.net/browse/HHH-13280). При `hibernate.use_sql_comments=false` все работает - в SELECT нет DISTINCT.
+- Тест `DataJpaUserServiceTest.getWithMeals()` не работает для admin (у админа 2 роли, и еда при JOIN дублируется). ...
-#### Apply 7_06_HW6_optional_jdbc.patch
-> - реализовал в `JdbcUserRepositoryImpl.getAll()` доставание ролей через лямбду
-> - в `insertRoles` поменял метод `batchUpdate` и сделал проверку на empty
-> - в `setRoles` достаю роли через `queryForList`
-
-Еще интересные JDBC реализации:
- - в `getAll()/ get()/ getByEmail()` делать запросы с `LEFT JOIN` и сделать реализацию `ResultSetExtractor`
- - подключить зависимость `spring-data-jdbc-core`. Там есть готовый `OneToManyResultSetExtractor`. Можно посмотреть, как он реализован.
- - реализация, зависимая от БД (для postgres): доставать агрегированные роли и делать им `split(",")`:
-```
-SELECT u.*, string_agg(r.role, ',') AS roles
-FROM users u
- JOIN user_roles r ON u.id=r.user_id
-GROUP BY u.id
-```
+#### Apply 7_06_HW6_jdbc_transaction_roles.patch
+
+Еще интересные JDBC реализации: ...
+
+### Валидация для `JdbcUserRepository` через Bean Validation API
+
+#### Apply 7_07_HW6_optional_jdbc_validation.patch
+
+- [Валидация данных при помощи Bean Validation API](https://alexkosarev.name/2018/07/30/bean-validation-api/).
+
+На данный момент у нас реализована валидация сущностей только для jpa- и dataJpa-репозиториев. При работе
+через JDBC-репозиторий может произойти попытка записи в БД некорректных данных, что приведет к `SQLException` из-за нарушения
+ограничений, наложенных на столбцы базы данных. Для того, чтобы перехватить невалидные данные еще до
+обращения в базу, воспользуемся API *javax.validation* (ее реализация `hibernate-validator` используется для проверки данных в Hibernate и будет использоваться в Spring Validation, которую подключим позже).
+В `ValidationUtil` создадим один потокобезопасный валидатор, который можно переиспользовать (см. *javadoc*).
+С его помощью в методах сохранения и обновления сущности в jdbc-репозиториях мы можем производить валидацию этой сущности: `ValidationUtil.validate(object);`
+Чтобы проверка не падала, `@NotNull Meal.user` пришлось пока закомментировать. Починим в 10-м занятии через `@JsonView`.
+
+### Отключение кэша в тестах:
+
+Вместо наших приседаний с `JpaUtil` и проверкой профилей мы можем ...
+
+#### Apply 7_08_HW06_optional2_disable_tests_cache.patch
+
+- [Example of PropertyOverrideConfigurer](https://www.concretepage.com/spring/example_propertyoverrideconfigurer_spring)
+- [Spring util schema](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#xsd-schemas-util)
## Занятие 7:
-###  3. Тестирование Spring MVC
-#### Apply 7_07_controller_test.patch
+
+###  3. Тестирование Spring MVC
+
+
+ Краткое содержание
+
+#### Тестирование Spring MVC
+
+Для более удобного сравнения объектов в тестах мы будем использовать библиотеку *Harmcrest* с Matcher'ами, которая
+позволяет делать сложные проверки. С *Junit* по умолчанию подтягивается *Harmcrest core*, но нам потребуется расширенная версия:
+в `pom.xml` из зависимости Junit исключим дочернюю `hamcrest-core` и добавим `hamcrest-all`.
+
+Для тестирования web создадим вспомогательный класс `AbstractControllerTest`, от которого будут наследоваться все
+тесты контроллеров. Его особенностью будет наличие `MockMvc` - эмуляции Spring MVC для тестирования web-компонентов.
+Инициализируем ее в методе, отмеченном `@PostConstruct`:
+
+ ```
+mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(CHARACTER_ENCODING_FILTER).build();
+ ```
+
+Для того, чтобы в тестах контроллеров не популировать базу перед каждым тестом, пометим этот базовый тестовый класс аннотацией `@Transactional`.
+Теперь каждый тестовый метод будет выполняться в транзакции, которая будет откатываться после окончания метода и возвращать базу данных в исходное
+состояние. Однако теперь в работе тестов могут возникнуть нюансы, связанные с пропагацией транзакций: все
+транзакции репозиториев станут вложенными во внешнюю транзакцию теста. При этом, например, кэш первого уровня станет работать не
+так, как ожидается. Т.е при таком подходе нужно быть готовыми к ошибкам: мы их увидим и поборем в тестах на обработку ошибок на последних занятиях TopJava.
+
+#### UserControllerTest
+
+Создадим тестовый класс для контроллера юзеров, он должен наследоваться от `AbstractControllerTest`.
+В `MockMvc` используется [паттерн проектирования Builder](https://refactoring.guru/ru/design-patterns/builder).
+
+ ```
+ mockMvc.perform(get("/users")) // выполнить HTTP метод GET к "/users"
+ .andDo(print()) // распечатать содержимое ответа
+ .andExpect(status().isOk()) // от контроллера ожидается ответ со статусом HTTP 200(ok)
+ .andExpect(view().name("users")) // контроллер должен вернуть view с именем "users"
+ .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) // ожидается, что клиент должен быть перенаправлен на "/WEB-INF/jsp/users.jsp"
+ .andExpect(model().attribute("users", hasSize(2))) // в модели должен быть атрибут "users" размером = 2
+ .andExpect(model().attribute("users", hasItem( // внутри которого есть элемент ...
+ allOf(
+ hasProperty("id", is(START_SEQ)), // ... с аттрибутом id = START_SEQ
+ hasProperty("name", is(USER.getName())) //... и name = user
+ )
+ )));
+}
+ ```
+
+В параметры метода `andExpect()` передается реализация `ResultMatcher`, в которой мы определяем как должен быть обработан ответ контроллера.
+
+
+
+#### Apply 7_09_controller_test.patch
+
> - в `MockMvc` добавился `CharacterEncodingFilter`
-> - добавил `AllActiveProfileResolver`
-> - реализация Ehcache нетранзакционная, после отката транзакции в тестах по `@Transactional` Hibernate-кеш не восстанавливал роль для USER. Добавил очистку кэша Hibernate в `AbstractControllerTest.setUp` (с учетом того, что `Profiles.REPOSITORY_IMPLEMENTATION` можно переключить на `JDBC`).
+> - добавил [`AllActiveProfileResolver`](//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver) для возвращения массива профилей
+> - сделал вспомогательный метод `AbstractControllerTest.perform()`
+
+- Hamcrest
+- Unit Testing of Spring MVC Controllers
+
+###  4. [Миграция на JUnit 5](https://drive.google.com/open?id=16wi0AJLelso-dPuDj6xaGL7yJPmiO71e)
+
+
+ Краткое содержание
+
+Для миграции на 5-ю версию JUnit в файле `pom.xml` поменяем зависимость `junit` на `junit-jupiter-engine` ([No need `junit-platform-surefire-provider` dependency in `maven-surefire-plugin`](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven)).
+Актуальную версию всегда можно посмотреть [в центральном maven репозитории](https://search.maven.org/search?q=junit-jupiter-engine), берем только релизы (..-Mx означают предварительные milestone версии)
+Изменять конфигурацию плагина `maven-sureface-plugin` в новых версиях JUnit уже не требуется.
+Junit5 не содержит в себе зависимости от *Harmcrest* (которую нам приходилось вручную
+отключать для JUnit4 в предыдущих шагах), поэтому исключение `hamcrest-core` просто удаляем.
+В итоге у нас останутся зависимости JUnit5 и расширенный Harmcrest.
+Теперь мы можем применить все нововведения пятой версии в наших тестах:
+ 1. Для всех тестов теперь мы можем удалить `public`.
+ 2. Аннотацию `@Before` исправим на `@BeforeEach` - теперь метод, который будет выполняться перед
+каждым тестом, помечается именно так.
+ 3. В Junit5 работа с исключениями похожа на Junit4 версии 4.13: вместо ожидаемых исключений в параметрах аннотации `@Test(expected = Exception.class)` используется метод `assertThrows()`,
+в который первым аргументом мы передаем ожидаемое исключение, а вторым аргументом — реализацию функционального интерфейса `Executable` (кода теста,
+в котором ожидается возникновение исключения).
+ 4. Метод `assertThrows()` возвращает исключение, которое было выброшено в переданном ему коде. Теперь мы можем получить это исключение, извлечь из него сообщение с помощью
+ `e.getMessage()` и сравнить с ожидаемым.
+ 5. Для теста на валидацию при проверке предусловия, только при выполнении которого
+будет выполняться следующий участок кода (например, в нашем случае тесты на валидацию выполнялись
+только в jpa профиле), - теперь нужно пользоваться утильным методом `Assumptions` (нам уже не требуется).
+ 6. Проверку Root Cause - причины, из-за которой было выброшено пойманное исключение, мы будем делать позднее, при тестах на ошибки.
+ 7. Из JUnit5 исключена функциональность `@Rule`, вместо них теперь нужно использовать `Extensions`, которые
+могут встраиваться в любую фазу тестов. Чтобы добавить их в тесты, пометим базовый тестовый класс аннотацией `@ExtendWith`.
+
+JUnit предоставляет нам набор коллбэков — интерфейсов, которые будут исполняться в определенный момент тестирования.
+Создадим класс `TimingExtension`, который будет засекать время выполнения тестовых методов.
+Этот класс будет имплементировать маркерные интерфейсы — коллбэки JUnit:
+ - `BeforeTestExecutionCallback` - коллбэк, который будет вызывать методы этого интерфейса перед каждым тестовым методом.
+ - `AfterTestExecutionCallback` - методы этого интерфейса будут вызываться после каждого тестового метода;
+ - `BeforeAllCallback` - методы перед выполнением тестового класса;
+ - `AfterAllCallback` - методы после выполнения тестового класса;
+
+Осталось реализовать соответствующие методы, которые описываются в каждом из этих интерфейсов, они и будут вызываться JUnit в нужный момент:
+ - в методе `beforeAll` (который будет вызван перед запуском тестового класса) создадим спринговый утильный секундомер `StopWatch` для текущего тестового класса;
+ - в методе `beforeTestExecution` (будет вызван перед тестовым методом) - запустим секундомер;
+ - в методе `afterTestExecution` (будет вызван после тестового метода) - остановим секундомер.
+ - в методе `afterAll` (который будет вызван по окончанию работы тестового класса) - выведем результат работы этого секундомера в консоль;
-- Hamcrest
-- Unit Testing of Spring MVC Controllers
+ 8. Аннотации `@ContextConfiguration` и `@ExtendWith(SpringExtension.class)` (замена `@RunWith`) мы можем заменить одной `@SpringJUnitConfiguration` (в старых версиях IDEA ее не понимает)
+
+
+
+#### Apply 7_10_JUnit5.patch
-###  4. [Миграция на JUnit 5](https://www.youtube.com/watch?v=YmLzT-j1hU4)
-#### Apply 7_08_JUnit5.patch
> - [No need `junit-platform-surefire-provider` dependency in `maven-surefire-plugin`](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven)
-> - [Наконец пофиксили баг с `@SpringJUnitConfig`](https://youtrack.jetbrains.com/issue/IDEA-166549). Можно обновить IDEA до 2019.2 (обновите, только если достаточно памяти: минимум 3Gb. `Help menu -> Edit Custom VM Options`)
+> - [Наконец пофиксили баг с `@SpringJUnitConfig`](https://youtrack.jetbrains.com/issue/IDEA-166549)
- [JUnit 5 homepage](https://junit.org/junit5)
- [Overview](https://junit.org/junit5/docs/snapshot/user-guide/#overview)
@@ -79,114 +191,376 @@ GROUP BY u.id
- [Third party Extensions](https://github.com/junit-team/junit5/wiki/Third-party-Extensions)
- [Реализация assertThat](https://stackoverflow.com/questions/43280250)
+###  5. [Принципы REST. REST контроллеры](https://drive.google.com/open?id=1e4ySjV15ZbswqzL29UkRSdGb4lcxXFm1)
+
+
+ Краткое содержание
+
+#### Принципы REST, REST-контроллеры
+
+> [REST](http://spring-projects.ru/understanding/rest/) - архитектурный стиль проектирования распределенных систем (типа клиент-сервер).
+
+Чаще всего в REST сервер и клиент общаются посредством обмена JSON-объектами через HTTP-методы GET/POST/PUT/DELETE/PATCH.
+Особенностью REST является отсутствие состояния (контекста) взаимодействий клиента и сервера.
+
+В нашем приложении есть контроллеры для Admin и для User. Чтобы сделать их REST-контроллерами,
+заменим аннотацию `@Controller` на `@RestController`
+
+> Не поленитесь зайти чз Ctrl+Click в `@RestController`: к аннотации `@Controller` добавлена `@ResponseBody`. Т.е. ответ от нашего приложения будет не имя View, а данные в теле ответа.
+
+В `@RequestMapping`, кроме пути для методов контроллера (`value`) добавляем параметр `produces = MediaType.APPLICATION_JSON_VALUE`.
+Это означает, что в заголовки ответа будет добавлен тип `ContentType="application/json"` - в ответе от контроллера будет приходить JSON-объект.
+
+> Чтобы было удобно использовать путь к этому контроллеру в приложении и в тестах,
+> выделим путь к нему в константу REST_URL, к которой можно будет обращаться из других классов
+
+1. Метод `AdminRestController.getAll` пометим аннотацией `@GetMapping` - маршрутизация к методу по HTTP GET.
+
+2. Метод `AdminRestController.get` пометим аннотацией `@GetMapping("/{id}")`.
+В скобках аннотации указано, что к основному URL контроллера будет добавляться `id` пользователя - переменная, которая передается в запросе непосредственно в URL.
+ Соответствующий параметр метода нужно пометить аннотацией `@PathVariable` (если имя в URL и имя аргумента метода не совпадают, в параметрах аннотации дополнительно нужно будет уточнить
+ имя в URL. Если они совпадают, [этого не требуется](https://habr.com/ru/post/440214/).
+
+3. Метод создания пользователя `create` отметим аннотацией `@PostMapping` - маршрутизация к методу по HTTP POST.
+ В метод мы передаем объект `User` в теле запроса (аннотация `@RequestBody`) и в формате JSON (`consumes = MediaType.APPLICATION_JSON_VALUE`).
+ При создании нового ресурса правила хорошего тона - вернуть в заголовке ответа URL созданного ресурса.
+ Для этого возвращем не `User`, а `ResponseEntity`, который мы можем с помощью билдера `ServletUriComponentsBuilder` дополнить заголовком ответа `Location` и вернуть статус `CREATED(201)`
+ (если пойти в код `ResponseEntity.created` можно докопаться до сути, очень рекомендую смотреть в исходники кода).
+
+4. Метод `delete` помечаем `@DeleteMapping("/{id}")` - HTTP DELETE.
+ Он ничего не возвращает, поэтому помечаем его аннотацией `@ResponseStatus(HttpStatus.NO_CONTENT)`. Статус ответа будет HTTP.204;
+
+5. Над методом обновления ставим `@PutMapping` (HTTP PUT). В аргументах метод принимает `@RequestBody User user` и `@PathVariable int id`.
+
+6. Метод поиска по `email` также помечаем `@GetMapping`, и, чтобы не было конфликта маршрутизации с методом `get()`,
+ указываем в URL добавку "/by". В этот метод `email` передается как параметр запроса, аннотация `@RequestParam`.
+
+> **Все это СТАНДАРТ архитектурного стиля REST. НЕ придумывайте ничего своего в своих выпускных проектах! Это очень большая ошибка - не придерживаться стандартов API.**
+
+7. `ProfileRestController` выполняем аналогичным способом с учетом того, что пользователь имеет доступ только к своим данным.
-###  5. [Принципы REST. REST контроллеры](https://youtu.be/33_2yOck4ak)
-#### Apply 7_09_rest_controller.patch
-
-- Понимание REST
-- JSON (JavaScript Object Notation)
+Если на данном этапе попытаться запустить приложение и обратиться к какому-либо методу контроллера, сервер ответит нам ошибкой со статусом 406,
+так как Spring не знает, как преобразовать объект User в JSON...
+
+
+
+#### Apply 7_11_rest_controller.patch
+
+- Понимание REST
+- JSON (JavaScript Object Notation)
- [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)
-- [10 Best Practices for Better RESTful](http://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/)
+- [10 Best Practices for Better RESTful](https://medium.com/@mwaysolutions/10-best-practices-for-better-restful-api-cbe81b06f291)
- [Best practices for rest nested resources](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources)
-- Request mapping
-- Дополнительно:
- - JAX-RS vs Spring MVC
- - RESTful API для сервера – делаем правильно (Часть 1)
- - RESTful API для сервера – делаем правильно (Часть 2)
- - И. Головач. RestAPI
- - [value/name в аннотациях @PathVariable и @RequestParam](https://habr.com/ru/post/440214/)
-
-###  6. [Тестирование REST контроллеров. Jackson](https://drive.google.com/file/d/1aZm2qoMh4yL_-i3HhRoyZFjRAQx-15lO)
-#### Apply 7_10_rest_test_jackson.patch
-- [Jackson databind github](https://github.com/FasterXML/jackson-databind)
-- [Jackson Annotation Examples](https://www.baeldung.com/jackson-annotations)
-
-###  7. [Кастомизация Jackson Object Mapper](https://drive.google.com/file/d/1CM6y1JhKG_yeLQE_iCDONnI7Agi4pBks)
-
-#### Apply 7_11_jackson_object_mapper.patch
-- Сериализация hibernate lazy-loading с помощью jackson-datatype-hibernate
-- Handle Java 8 dates with Jackson
-- Дополнительно:
- - Jackson JSON Serializer & Deserializer
-
-###  8. [Тестирование REST контроллеров через JSONassert](https://drive.google.com/file/d/1oa3e0_tG57E71g6PW7_tfb3B61Qldctl)
-#### Apply 7_12_json_assert_tests.patch
+-
+ Request mapping
+- [Лучшие практики разработки REST API: правила 1-7,15-17](https://tproger.ru/translations/luchshie-praktiki-razrabotki-rest-api-20-sovetov/)
+- Дополнительно:
+ - [Подборка практик REST](https://gist.github.com/Londeren/838c8a223b92aa4017d3734d663a0ba3)
+ - JAX-RS vs Spring MVC
+ - RESTful API для сервера – делаем правильно (Часть 1)
+ - RESTful API для сервера – делаем правильно (Часть 2)
+ - И. Головач.
+ RestAPI
+ - [value/name в аннотациях @PathVariable и @RequestParam](https://habr.com/ru/post/440214/)
+
+###  6. [Тестирование REST контроллеров. Jackson.](https://drive.google.com/open?id=1aZm2qoMh4yL_-i3HhRoyZFjRAQx-15lO)
+
+
+ Краткое содержание
+
+Для работы с JSON добавляем в `pom.xml` зависимость `jackson-databind`.
+Актуальную версию библиотеки можно посмотреть в [центральном maven-репозитории](https://search.maven.org/artifact/com.fasterxml.jackson.core/jackson-databind).
+Теперь спринг будет автоматически использовать эту библиотеку для сериализации/десериализации объектов в JSON (найдя ее в *classpath*).
+Если сейчас запустить приложение и обратиться к методам REST-контроллера, то оно выбросит `LazyInitializationException`.
+Оно возникает из-за того, что у наших сущностей есть лениво загружаемые поля, отмеченные `FetchType.LAZY` - при загрузке сущности из базы, вместо этого поля подставится Proxy, который и должен вернуть
+реальный экземпляр этого поля при первом же обращении. Jackson при сериализации в JSON использует все поля сущности,
+и при обращении к *Lazy* полям возникает исключение, так как сессия работы с БД в этот момент уже закрыта, и нужный объект
+не может быть инициализирован. Чтобы Jackson игнорировал эти поля, пометим их аннотацией `@JsonIgnore`.
+
+Теперь при запуске приложения REST-контроллер будет работать. Но при получении JSON объектов мы можем увидеть, что Jackson сериализовал объект
+через геттеры (например в ответе есть поле `new` от метода `Persistable.isNew()`).
+Чтобы учитывались только поля объектов, добавим над `AbstractBaseEntity`:
+````java
+@JsonAutoDetect(fieldVisibility = ANY, // jackson видит все поля
+ getterVisibility = NONE, // ... но не видит геттеров
+ isGetterVisibility = NONE, //... не видит геттеров boolean полей
+ setterVisibility = NONE) // ... не видит сеттеров
+````
+Теперь все сущности, унаследованные от базового класса, будут сериализоваться/десериализоваться через поля.
+
+
+
+#### Apply 7_12_rest_test_jackson.patch
+
+- [Jackson databind github](https://github.com/FasterXML/jackson-databind)
+- [Jackson Annotation Examples](https://www.baeldung.com/jackson-annotations)
+
+###  7. [Кастомизация Jackson Object Mapper](https://drive.google.com/open?id=1CM6y1JhKG_yeLQE_iCDONnI7Agi4pBks)
+
+
+ Краткое содержание
+
+Сейчас, чтобы не сериализовать *Lazy* поля, мы должны пройтись по каждой сущности и
+вручную пометить их аннотацией `@JsonIgnore`. Это неудобно, засоряет код и допускает возможные ошибки. К тому же,
+при некоторых условиях, нам иногда нужно загрузить и в ответе передать эти *Lazy* поля.
+Чтобы запретить сериализацию Lazy полей для всего проекта, подключим в `pom.xml` библиотеку `jackson-datatype-hibernate`.
+Также изменим сериализацию/десериализацию полей объектов в JSON: не через аннотацию `@JsonAutoDetect`, а в классе `JacksonObjectMapper`, который
+унаследуем от `ObjectMapper` (стандартный Mapper, который использует Jackson) и сделаем в нем другие настройки.
+В конструкторе:
+- регистрируем `Hibernate5Module` - модуль `jackson-datatype-hibernate`, который не делает сериализацию ленивых полей.
+- модуль для корректной сериализации `LocalDateTime` в поля JSON - `JavaTimeModule` модуль библиотеки `jackson-datatype-jsr310`
+- запрещаем доступ ко всем полям и методам класса и потом разрешаем доступ только к полям
+- не сериализуем null-поля (`setSerializationInclusion(JsonInclude.Include.NON_NULL)`)
+
+Чтобы подключить наш кастомный `JacksonObjectMapper` в проект, в конфигурации `spring-mvc.xml` к
+настройке `` добавим `MappingJackson2HttpMessageConverter`, который будет использовать наш маппер.
+
+
+
+
+#### Apply 7_13_jackson_object_mapper.patch
+
+- Сериализация hibernate lazy-loading с помощью
+ jackson-datatype-hibernate
+- Handle Java 8 dates with Jackson
+- Дополнительно:
+ - Jackson JSON
+ Serializer & Deserializer
+
+###  8. [Тестирование REST контроллеров через JSONassert и Матчеры](https://drive.google.com/open?id=1oa3e0_tG57E71g6PW7_tfb3B61Qldctl)
+
+
+ Краткое содержание
+
+Сейчас в тестах REST-контроллера мы проводим проверку только на статус ответа и тип возвращаемого контента. Добавим проверку содержимого ответа.
+
+#### 7_14_json_assert_tests
+
+Чтобы сравнивать содержимое ответа контроллера в виде JSON и сущность, воспользуемся библиотекой
+`jsonassert`, которую подключим в `pom.xml` со scope *test*.
+
+Эта библиотека при сравнении в тестах в качестве ожидаемого значения ожидает от
+нас объект в виде JSON-строки. Чтобы вручную не преобразовывать объекты в JSON и не
+хардкодить их в виде строк в наши тесты, воспользуемся Jackson.
+Для преобразования объектов в JSON и обратно создадим утильный класс `JsonUtil`, в котором
+с помощью нашего `JacksonObjectMapper` и будет конвертировать объекты.
+И мы сталкиваемся с проблемой: `JsonUtil` - утильный класс и не является
+бином спринга, а для его работы требуется наш кастомный маппер, который находится под управлением
+спринга и расположен в контейнере зависимостей. Поэтому, чтобы была возможность получить
+наш маппер из других классов - сделаем его синглтоном и сделаем в нем статический
+метод, который будет возвращать его экземпляр. Теперь `JsonUtil` сможет его получить.
+И нам нужно указать спрингу, чтобы он не создавал второй экземпляр этого объекта, а клал в свой контекст существующий.
+Для этого в конфигурации `spring-mvc.xml` определим factory-метод, с помощью которого спринг должен
+получить экземпляр (instance) этого класса:
+```xml
+
+```
+а в конфигурации `message-converter` вместо создания бина просто сошлемся на сконфигурированный `objectMapper`.
+
+Метод `ContentResultMatchers.json()` из `spring-test` использует библиотеку `jsonassert` для сравнения 2-х JSON строк: одну из ответа контроллера и вторую -
+JSON-сериализация `admin` без поля `registered` (это поле инициализируется в момент создания и отличается).
+В методе `JsonUtil.writeIgnoreProps` мы преобразуем объект `admin` в мапу, удаляем из нее игнорируемые поля и снова сериализуем в JSON.
+
+Также сделаем тесты для утильного класса `JsonUtil`. В тестах мы записываем
+объект в JSON-строку, затем конвертируем эту строку обратно в объект и сравниваем с исходным. И то же самое делаем со списком объектов.
+
+#### 7_15_tests_refactoring
+
+**`RootControllerTest`**
+
+Сделаем рефакторинг `RootControllerTest`. Ранее мы в тесте получали модель, доставали из нее сущности и с помощью `hamcrest-all`
+производили по одному параметру их сравнение с ожидаемыми значениями.
+Метод `ResultActions.andExpect()` позволяет передавать реализацию интерфейса `Matcher`, в котором можно делать любые сравнения.
+Функциональность сравнения списка юзеров по ВСЕМ полям у нас уже есть - мы просто делегируем сравнение объектов в `UserTestData.MATCHER`.
+При этом нам больше не нужен `harmcrest-all`, нам достаточно только `harmcrest-core`.
+
+**`MatcherFactory`**
+
+Теперь вместо `jsonassert` и сравнения JSON-строк в тестах контроллеров сделаем сравнения JSON-объектов через `MatcherFactory`.
+Преобразуем ответ контроллера из JSON в объект и сравним с эталоном через уже имеющийся у нас матчер.
+Вместо сравнения JSON-строк в метод `andExpect()` мы будем передавать реализации интерфейса `ResultMatcher` из `MATCHER.contentJson(..)`.
+
+`MATCHER.contentJson(..)` принимают ожидаемый объект и возвращают для него `ResultMatcher` с реализацией единственного метода `match(MvcResult result)`,
+в котором делегируем сравнение уже существующим у нас матчерам.
+Мы берем JSON-тело ответа (`MatcherFactory.getContent`), десериализуем его в объект (`JsonUtil.readValue/readValues`) и сравниваем через имеющийся `MATCHER.assertMatch`
+десериализованный из тела контроллера объект и ожидаемое значение.
+
+> Методы из класса `TestUtil` перенес в `MatcherFactory`, лишние удалил.
+
+**`AdminRestControllerTest`**
+
+- `getByEmail()` - сделан по аналогии с тестом `get()`. Дополнительно нужно дополнить строку URL параметрами запроса.
+- `delete()` - выполняем HTTP.DELETE. Проверяем статус ответа 204. Проверяем, что пользователь удален.
+
+> Раньше я получал всех users из базы и проверял, что среди них нет удаленного. При этом тесты становятся чувствительными ко всем users в базе и ломаются при добавлении-удалении новых тестовых данных.
+
+- `update()` - выполняем HTTP.PUT. В тело запроса подаем сериализованный `JsonUtil.writeValue(updated)`. После выполнения проверяем, что объект в базе обновился.
+- `create()` - выполняем HTTP.POST аналогично `update()`. Но сравнить результат мы сразу не можем, т.к. при создании объекта ему присваивается `id`.
+ Поэтому мы извлекаем созданного пользователя из ответа (`MATCHER.readFromJson(action)`), получаем его `id`, и уже с этим `id` эталонный объект мы можем сравнить с объектом в ответе контроллера и со
+ значением в базе.
+- `getAll()` - аналогично get(). Список пользователей из ответа в формате JSON сравниваем с эталонным списком (`MATCHER.contentJson(admin, user)`).
+
+Тесты для `ProfileRestController` выполнены аналогично.
+
+
+
+#### Apply 7_14_json_assert_tests.patch
+
+> - В `JsonUtil.writeIgnoreProps` вместо цикла по мапе сделал `map.keySet().removeAll`
+
- [JSONassert](https://github.com/skyscreamer/JSONassert)
- [Java Code Examples for ObjectMapper](https://www.programcreek.com/java-api-examples/index.php?api=com.fasterxml.jackson.databind.ObjectMapper)
-#### Apply 7_13_tests_refactoring.patch
+#### Apply 7_15_tests_refactoring.patch
+
+> - Методы из класса `TestUtil` перенес в `MatcherFactory`, лишние удалил.
+> - Раньше в тестах я для проверок получал всех users из базы и сравнивал с эталонным списком. При этом тесты становятся чувствительными ко всем users в базе и ломаются при добавлении-удалении новых тестовых данных.
+
+- [Java @SafeVarargs Annotation](https://www.baeldung.com/java-safevarargs)
###  9. Тестирование через SoapUi. UTF-8
-#### Apply 7_14_soapui_utf8_converter.patch
+
+
+ Краткое содержание
+
+SOAP UI - это один из инструментов для тестирования API приложений, которые работают по REST и по SOAP.
+Он позволяет нам по HTTP протоколу дернуть методы нашего API и увидеть ответ контроллеров.
+
+Если в контроллер мы добавим метод, который в теле ответа будет возвращать текст на кириллице, то мы увидим кодировка теряться.
+Для сохранения кодировки используем `StringHttpMessageConverter`, который конфигурируем в `spring-mvc.xml`.
+При этом мы должны явно указать, что конвертор будет работать только с текстом в кодировке *UTF-8*.
+
+
+
+#### Apply 7_16_soapui_utf8_converter.patch
- Инструменты тестирования REST:
- - SoapUi
- - Написание HTTP-запросов с помощью Curl
-(для Windows можно использовать Git Bash). Для работы с UTF-8 в Windows 10 нужны пляски с бубном: ["Язык и региональные стандарты" -> "Сопутствующие параметры" -> "Административные языковые параметры" -> "Изменить язык системы" -> галка "Бета-версия:Использовать Юникод (UTF-8) для поддержки языка во всем мире"](https://drive.google.com/open?id=1J1WquTv9wenJQ9ptMymXPYGnrvFzAV-L), перезагрузка.
- - Postman
- - IDEA: Tools->HTTP Client->...
- - [Insomnia REST client](https://insomnia.rest/)
+ - SoapUi
+ - Написание HTTP-запросов с помощью
+ Curl.
+ Для Windows 7 можно использовать Git Bash, с Windows 10 v1803 можно прямо из консоли. Возможны проблемы с UTF-8:
+ - [CURL doesn't encode UTF-8](https://stackoverflow.com/a/41384903/548473)
+ - [Нстройка кодировки в Windows](https://support.socialkit.ru/ru/knowledge-bases/4/articles/11110-preduprezhdenie-obnaruzhenyi-problemyi-svyazannyie-s-raspoznavaniem-russkih-simvolov)
+ - **[IDEA: Tools->HTTP Client->...](https://www.jetbrains.com/help/idea/rest-client-tool-window.html)**
+ - Postman
+ - [Insomnia REST client](https://insomnia.rest/)
-**Импортировать проект в SoapUi из config\Topjava-soapui-project.xml. Response смотреть в формате JSON.**
+**Импортировать проект в SoapUi из `config\Topjava-soapui-project.xml`. Response смотреть в формате JSON.**
-> Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text
+> Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text
-ResponseBody and UTF-8
+[ResponseBody and UTF-8](http://web.archive.org/web/20190102203042/http://forum.spring.io/forum/spring-projects/web/74209-responsebody-and-utf-8)
##  Ваши вопросы
+
+> Зачем у нас и UIController'ы, и RestController'ы? То есть в общем случае backend-разработчику недостаточно предоставить REST-api и RestController?
+
+В общем случае нужны и те и другие. REST обычно используют для отдельного UI например на React или Angular или для
+интеграции / мобильного приложения. У нас REST контроллеры используются только для тестирования. UI мы используем для
+нашего приложения на JSP шаблонах. Таких сайтов без богатой UI логики тоже немало. Например https://javaops.ru/ :)
+Разница в запросах:
+
+- для UI используются только GET и POST
+- при создании-обновлении в UI мы принимаем данные из формы `application/x-www-form-urlencoded` (посмотрите
+ вкладку `Network`, не в формате JSON)
+- для REST запросы GET, POST, PUT, DELETE, PATCH и возвращают только данные (обычно JSON)
+
+и в способе авторизации:
+
+- для RESТ у нас будет базовая авторизация
+- для UI - через cookies
+
+Также часто бывают смешанные сайты - где есть и отдельное JS приложение и шаблоны.
+
> При выполнении тестов через MockMvc никаких изменений на базе не видно, почему оно не сохраняет?
-`AbstractControllerTest` аннотируется `@Transactional` - это означает, что тесты идут в транзакции, и после каждого теста JUnit делает rollback базы.
+`AbstractControllerTest` аннотируется `@Transactional` - это означает, что тесты идут в транзакции, и после каждого
+теста JUnit делает rollback базы.
-> Что получается в результате выполнения запроса `SELECT DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email`? В чем разница в SQL без `DISTINCT`.
+> Что получается в результате выполнения запроса `SELECT DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email`? В чем разница в SQL без `DISTINCT`.
-Запросы SQL можно посмотреть в логах. Т.е. `DISTINCT` в `JPQL` влияет на то, как Hibernate обрабатывает дублирующиеся записи (с `DISTINCT` их исключает). Результат можно посмотреть в тестах или приложении, поставив брекпойнт.
-По поводу `SQL DISTINCT` не стесняйтесь пользоваться google, например, [оператор SQL DISTINCT](http://2sql.ru/novosti/sql-distinct/)
+Запросы SQL можно посмотреть в логах. Т.е. `DISTINCT` в `JPQL` влияет на то, как Hibernate обрабатывает дублирующиеся
+записи (с `DISTINCT` их исключает). Результат можно посмотреть в тестах или приложении, поставив брекпойнт. По
+поводу `SQL DISTINCT` не стесняйтесь пользоваться google,
+например, [оператор SQL DISTINCT](http://2sql.ru/novosti/sql-distinct/)
-> В чем заключается расширение функциональности hamcrest в нашем тесте, что нам пришлось его отдельно от JUnit прописывать?
+> В чем заключается расширение функциональности hamcrest в нашем тесте, что нам пришлось его отдельно от JUnit прописывать?
hamcrest-all используется в проверках `RootControllerTest`: `org.hamcrest.Matchers.*`
-> Jackson мы просто подключаем в помнике, и Спринг будет с ним работать без любых других настроек?
+> Jackson мы просто подключаем в помнике, и Spring будет с ним работать без любых других настроек?
Да, Spring смотрит в classpath и если видит там Jackson, то подключает интеграцию с ним.
-> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ?
+> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ?
+
+Так делают, только при
+отношении
+агрегация, например, если давать админу право смотреть еду любого юзера, URL мог бы быть похож
+на `http://localhost/topjava/rest/users/{userId}/meals/{mealId}` (не рекомендуется, см ссылку ниже). В случае критериев
+поиска или страничных данных они передаются как параметр. Смотри также:
-Так делают, только при отношении агрегация, например, если давать админу право смотреть еду любого юзера, URL мог бы быть похож на `http://localhost/topjava/rest/users/{userId}/meals/{mealId}`. В случае критериев поиска или страничных данных они передаются как параметр. Смотри также:
- [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)
-- 10 Best Practices for Better RESTful
+- 10 Best Practices
+ for Better RESTful
- [REST resource hierarchy (если кратко: не рекомендуется)](https://stackoverflow.com/questions/15259843/how-to-structure-rest-resource-hierarchy)
> Что означает конструкция в `JsonUtil`: `reader.readValues(json)`;
-См. Generic Methods. Когда компилятор не может вывести тип, можно его уточнить при вызове generic метода. Неважно, static или нет.
+См. Generic Methods. Когда компилятор
+не может вывести тип, можно его уточнить при вызове generic метода. Неважно, static или нет.
##  Домашнее задание HW07
- 1: Добавить тесты контроллеров:
- - 1.1 `RootControllerTest.testMeals` для `meals.jsp`
- - 1.2 `ResourceControllerTest` для `style.css` (проверить `status` и `ContentType`)
+ - 1.1 `RootControllerTest.getMeals` для `meals.jsp`
+ - 1.2 Сделать `ResourceControllerTest` для `style.css` (проверить `status` и `ContentType`)
- 2: Реализовать `MealRestController` и протестировать его через `MealRestControllerTest`
- - 2.1 следите, чтобы url в тестах совпадал с параметрами в методе контроллера. Можно добавить логирование `` для проверки маршрутизации.
- - 2.2 в параметрах `getBetween` принимать `LocalDateTime` (конвертировать через @DATETIMEFORMAT WITH JAVA 8 DATE-TIME API), а передавать в тестах в формате `ISO_LOCAL_DATE_TIME` (например `'2011-12-03T10:15:30'`). Вызывать `super.getBetween()` пока без проверки на `null`, используя `toLocalDate()/toLocalTime()` (см. Optional п.3)
-
-#### Optional
-- 3: Переделать `MealRestController.getBetween` на параметры `LocalDate/LocalTime` c раздельной фильтрацией по времени/дате, работающий при `null` значениях (см. демо и `JspMealController.getBetween`). Заменить `@DateTimeFormat` на свои LocalDate/LocalTime конверторы или форматтеры.
- - Spring Type Conversion
- - Spring Field Formatting
- - Difference between Spring MVC formatters and converters
-- 4: Протестировать `MealRestController` (SoapUi, curl, IDEA Test RESTful Web Service, Postman). Запросы `curl` занести в отдельный `md` файл (либо `README.md`)
-
-**На следующем занятии используется JavaScript/jQuery. Если у вас там пробелы, пройдите его основы**
-
+ - 2.1 следите, чтобы url в тестах совпадал с параметрами в методе контроллера. Можно добавить
+ логирование `` для проверки маршрутизации.
+ - 2.2 в параметрах `getBetween` принимать `LocalDateTime` (конвертировать
+ через @DateTimeFormat with Java
+ 8 Date-Time API), пока без проверки на `null` (используя `toLocalDate()/toLocalTime()`, см. Optional п.3). В
+ тестах передавать в формате `ISO_LOCAL_DATE_TIME` (
+ например `'2011-12-03T10:15:30'`).
+
+### Optional
+
+- 3: Переделать `MealRestController.getBetween` на параметры `LocalDate/LocalTime` c раздельной фильтрацией по
+ времени/дате, работающий при `null` значениях (см. демо и `JspMealController.getBetween`)
+ . Заменить `@DateTimeFormat` на свои LocalDate/LocalTime конверторы или форматтеры.
+ - Spring Type
+ Conversion
+ - Spring Field
+ Formatting
+ -
+ Difference between Spring MVC formatters and converters
+- 4: Протестировать `MealRestController` (SoapUi, curl, IDEA Test RESTful Web Service, Postman). Запросы `curl` занести
+ в отдельный `md` файл (или `README.md`)
+- 5: Добавить в `AdminRestController` и `ProfileRestController` методы получения пользователя вместе с
+ едой (`getWithMeals`, `/with-meals`).
+ - [Jackson – Bidirectional Relationships](https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion)
+
+### Optional 2
+
+- 6: Сделать тесты на методы контроллеров `getWithMeals()` (п.5)
+
+**На следующем занятии используется JavaScript/jQuery. Если у вас там
+пробелы, пройдите его основы**
---------------------
+
##  Типичные ошибки и подсказки по реализации
+
- 1: Ошибка в тесте _Invalid read array from JSON_ обычно расшифровывается немного ниже: читайте внимательно.
-- 2: Jackson и неизменяемые объекты
-- 3: Jackson JSON Tutorial
-- 4: Если у meal, приходящий в контроллер, поля `null`, проверьте `@RequestBody` перед параметром (данные приходят в формате JSON)
-- 5: При проблемах с собственным форматтером убедитесь, что в конфигурации `Jackson и неизменяемые объекты (для
+ сериализации MealTo)
+- 3: Если у meal, приходящий в контроллер, поля `null`, проверьте `@RequestBody` перед параметром (данные приходят в
+ формате JSON)
+- 4: При проблемах с собственным форматтером убедитесь, что в конфигурации ` Совершенство достигнуто не тогда, когда нечего добавить, а тогда, когда нечего отнять
_Антуан де Сент-Экзюпери_
-- 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять**
-- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**
-- 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации**
-- 4: **учитывайте, что пользователей может быть ооочень много, а админов- мало**
-- 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего:
- - 5.1 НЕ надо делать разные профили базы и работы с ней.
- - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай.
- - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования.
- - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО.
-- 6: базу лучше взять без установки (H2 или HSQLDB). Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения**
-- 7: по возможности сделать JUnit тесты
-- 8: уделяйте внимание обработке ошибок
-- 9: далаем REST API в соответствии с концепцией REST
- - [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)
- - 10 Best Practices for Better RESTful API
+### 1: ТЗ (Тех.задание)
+
+- 1.1: Читай ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять
+- 1.2: Учитывай, что пользователей может быть ОООЧЕНЬ много, а админов - МАЛО
+- 1.3: Сначала сделай основные сценарии по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.
+
+### 2. API
+
+- 2.1: API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (клиента, UI)
+- 2.1: Тщательно считайте количество запросов в вашем API для отображения нужной информации
+- 2.3: Из потребностей приложения (клиента) реализуй только очевидные сценарии. Необходимо и достаточно: ВСЕ НЕОБХОДИМОЕ для клиента и НИЧЕГО ЛИШНЕГО. Процесс творческий, приходит с опытом.
+- 2.4: Делаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`). Имена ресурсов во множественном числе!
+Самая распространенная и грубая ошибка - не придерживаться этих простых правил.
+ - **[15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)**
+ - **[10 Best Practices for Better RESTful API](https://medium.com/@mwaysolutions/10-best-practices-for-better-restful-api-cbe81b06f291)**
- [REST resource hierarchy](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources)
-- 10: не смешивайте TO и Entity вместе. Лучше всего, если они будут независимыми друг от друга.
-- 11: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера)
-- 12: [Use for money in java app](http://stackoverflow.com/a/43051227/548473)
-- 13: **Историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.**
-- 14: Еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям!
-- 15: Название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил.
-- 16: **Используйте DATA-JPA** (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository).
-- 17: В DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`).
-- 18: На topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются.
-- 19: Проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое.
-- 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна!
-- 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser).
-- 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB
-- 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)!
-
-## Попробуйте подергать свое API по всем типичным сценариям ТЗ!
-- Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`.
-- Сколько раз пришлось его вызвать API для типичного сценария (нарпимер посмотреть рестораны с едой)?
-- Сколько запросов к базе было сделано? Можно ли сократить (например с FETCH/Graph или через кэширование)?
-- **API ДОЛЖНО соответствовать принципам REST (см. ссылки выше)**
-- **ОБЯЗАТЕЛЬНО: запустите `mvn test`- ошибок быть не должно**
-- **ОБЯЗАТЕЛЬНО: запустите приложение без всяких предварительных настроек (базы, переменных окружения, ..), лучше на другом компьютере. Должно запускаться!**
+ - [Лучшие практики разработки REST API: правила 1-7,15-17](https://tproger.ru/translations/luchshie-praktiki-razrabotki-rest-api-20-sovetov/)
+- 2.5: Разделение на роли я предпочитаю на уровне URL. Сразу и однозначно видно, какой API у админа, какое у пользователя (API админа начинается, например, с */admin/...*).
+- 2.6: На управление (CRUD) ресторанами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу!
+- 2.7: Проверьте в Swagger, что в POST и PUT нет ничего лишнего, а в GET есть все необходимые данные. Например, при запросе голоса должен быть `id` ресторана, а при
+создании-редактировании ресторана не должно быть меню и еды.
+- 2.8: `Profile` означает, что данные принадлежат профилю пользователя. Все остальное называйте по-другому.
+
+### 3: Код:
+- 3.1: Строго соблюдайте соглашения Java по именованию: пакеты ТОЛЬКО маленькими буквами, методы начинаются с маленькой буквы, классы с большой. Незнания Java Core - тестовое задание сразу в корзину.
+- 3.2 В проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы по ТЗ приложения код, ничего лишнего
+ - 3.2.1: НЕ надо делать разные профили базы и работы с ней
+ - 3.2.2: НЕ надо делать абстрактных контроллеров на всякий случай
+ - 3.2.3: НЕ надо делать сервисов, если там нет ничего, кроме делегирования
+ - 3.2.4: НЕ нужны локализация, UI, типы ошибок, Json View
+- 3.3: Название пакетов, имен классов для `model/to/web` стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил
+- 3.4: Проверьте, не торчат ли из кода учебные уши TopJava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение выпускного
+ совсем другое
+- 3.5: Вместо `return ResponseEntity.ok(object)` в контроллерах пишите `return object`. Проще!
+
+### 4: Модель
+
+- 4.1: В БД обычно хранятся все введенные пользователем и админом данные (история). Они не удаляются и не переписываются заново. Есть базовые вещи, которые закладываются в архитектуру приложения, и есть неочевидные доработки к ТЗ, делать не надо.
+- 4.2: Не делайте в модели объектов, которые не будут использоваться в коде (например, не надо двунаправленных связей, если достаточно однонаправленных)
+- 4.3: еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте в модели сравнение по полям!
+- 4.4: ORM работает с объектами. [Иногда, для упрощения логики, fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities)
+
+### 5: Архитектура
+- 5.1: Можно:
+ - или подключить DATA-REST (см.курс [Spring Boot 2.x + HATEOAS](https://javaops.ru/view/bootjava)). Контроллеры генерируются автоматически по репозиториям, требуется настройка ресурсов в кастомных контроллерах
+ - или делать на основе миграции TopJava / кода [TopJava-2](https://github.com/JavaOPs/topjava2)
+
+Нельзя смешивать эти подходы вместе! Я рекомендую 2-й вариант, без data-rest. Обязательно посмотрите в Swagger, какие контроллеры получились в результате.
+- 5.2: Не размещайте бизнес-логику приложения и преобразования в TO в слое доступа к DB
+- 5.3: Не смешивайте TO и Entity вместе. Они должны быть независимыми друг от друга. На TopJava мы смотрели разные варианты [c использованием TO и без](https://stackoverflow.com/a/21569720/548473).
+ Делаем максимально просто.
+- 5.4: [Use for money in java app](http://stackoverflow.com/a/43051227/548473)
+
+### 6: Доступ к БД
+
+- 6.1: Используйте Spring Data JPA (без лишней делегации). Методы Repository можно вызывать напрямую из сервиса или из контроллера.
+- 6.2: Если приложению в объекте требуется только его id, используйте reference (`getById`)
+- 6.3: В DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null-значениями (используйте `orElseThrow`)
+- 6.4: Не делайте при обновлении записи ради экономии пары строчек кода так:
+```
+if(updateCondition)
+ repository.delete(entity)
+}
+repository.save(entity)
+```
+Обновление записи базы должно быть через `UPDATE`.
+
+### 7: База Данных
+
+- 7.1: Берите без установки (H2 или HSQLDB). Одну и **в памяти**! Ваше приложение должно сразу запуститься, без всяких настроек и переменных окружения
+- 7.2: Тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу
+- 7.3: Сделайте [индексы к таблицам](https://ru.wikipedia.org/wiki/Индекс_(базы_данных)). Попробуйте обеспечить UNIQUE (один голос пользователя в день, один уникальный пункт меню в день).
+Следите за **порядком полей в индексе**, от этого зависит индексирование запросов.
+- 7.4: При популировании добавь записи за сегодняшний день - `now()`, чтобы всегда были актуальные исходные данные
+- 7.5: Поля базы case insensitive, не пишите camelStyle (для которых нужны кавычки)
+- 7.6: Таблицы обычно именуются в единственном числе. Исключение - `users`, `orders` и другие зарезервированные слова
+- 7.7: `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании полей
+
+### 8: Security
+
+- 8.1: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser).
+- 8.2: Я предпочитаю четкое разделение ролей на основе URL. Для админа URL содержит `/admin`
+- 8.3: Еще раз - призываю не менять код TopJava
+
+### 9: Кэширование
+
+- 9.1: Кэширование желательно для частых и редко меняющихся запросов от пользователей.
+Тщательно продумайте, что надо кэшировать (самые частые запросы), а что нет (большие или редко запрашиваемые данные)
+- 9.2: Проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение).
+
+### 10: Валидация
+
+- 10.1: Одних аннотаций валидации недостаточно. Должны быть `@Valid/@Validation`
+- 10.2: Проверяйте входные данные при `create/update` в контроллерах! В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()`
+
+### 11: Дополнительно
+
+- 11.1: По возможности сделать JUnit-тесты. Можно не делать 100% покрытие, только основные сценарии
+- 11.2: Уделяйте внимание обработке ошибок.
+
+### 12: `readme.md`:
+
+- 12.1: Поместите вначале `readme` ТЗ или **ссылку на него** - будет понятно о чем твой проект
+- 12.2: Если задание на English, описание пиши также на English (то же самое относится к языку резюме: вакансия на English предполагает резюме на English)
+- 12.3: Требуемые примеры `curl` не прячьте, а пишите здесь! Оптимально здесь должна быть ссылка на `Swagger UI` с креденшелами для выполнения запросов
+- 12.4: Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven
+
+### 13: Git
+
+- 13.1: Должна быть история ваших комитов с внятными комментариями. Это смотрят.
+- 13.2: Не комить служенбые файлы: логи, DB, настройки IDEA и пр., это сразу - уровень Junior.
+- 13.3: Все служебные файлы должны быть в `.gitignore`
+
+## Проверка
+
+- Попробуй подергать свое API по всем типичным сценариям ТЗ!
+ - Удобно использовать? Можно сделать проще? Например, чтобы проголосовать за ресторан залогиненному юзеру, достаточно `restorauntId`.
+ - Сколько раз пришлось его вызвать API для типичного сценария (например посмотреть рестораны с едой на сегодня)?
+ - Сколько запросов к базе было сделано? Можно ли сократить (например с `FETCH/Graph` или через кэширование)?
+ - **Еще раз - проверь все запросы в Sawgger, смотри на формат запросов и данные в ответе. Все должно работать, есть все данные и нет ничего лишнего**
+- 13.2: API ДОЛЖЕН соответствовать принципам REST (см. ссылки выше)
+- 13.3: ОБЯЗАТЕЛЬНО: запустите `mvn test` - ошибок быть не должно
+- 13.4: ОБЯЗАТЕЛЬНО: запустите приложение без всяких предварительных настроек (базы, переменных окружения, ..), лучше на другом компьютере. Приложение должно запускаться и работать!
diff --git a/pom.xml b/pom.xml
index 0b1c2896da5b..4dbe953d10bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
ru.javawebinar
topjava
- jar
+ war
1.0-SNAPSHOT
@@ -15,11 +15,15 @@
1.8
UTF-8
UTF-8
+
+
+ 1.2.3
+ 1.7.28
topjava
- install
+ package
org.apache.maven.plugins
@@ -34,6 +38,35 @@
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ compile
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+ ${slf4j.version}
+ runtime
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ runtime
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java
new file mode 100644
index 000000000000..943ff5cd59fa
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java
@@ -0,0 +1,39 @@
+package ru.javawebinar.topjava.model;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+public class Meal {
+ private final LocalDateTime dateTime;
+
+ private final String description;
+
+ private final int calories;
+
+ public Meal(LocalDateTime dateTime, String description, int calories) {
+ this.dateTime = dateTime;
+ this.description = description;
+ this.calories = calories;
+ }
+
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getCalories() {
+ return calories;
+ }
+
+ public LocalDate getDate() {
+ return dateTime.toLocalDate();
+ }
+
+ public LocalTime getTime() {
+ return dateTime.toLocalTime();
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java
new file mode 100644
index 000000000000..4ec4560c7864
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java
@@ -0,0 +1,36 @@
+package ru.javawebinar.topjava.model;
+
+import java.time.LocalDateTime;
+
+public class MealTo {
+ private final LocalDateTime dateTime;
+
+ private final String description;
+
+ private final int calories;
+
+// private final Supplier excess;
+// private final AtomicBoolean excess;
+ private final boolean excess;
+
+ public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) {
+ this.dateTime = dateTime;
+ this.description = description;
+ this.calories = calories;
+ this.excess = excess;
+ }
+
+// public Boolean getExcess() {
+// return excess.get();
+// }
+
+ @Override
+ public String toString() {
+ return "UserMealWithExcess{" +
+ "dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ ", excess=" + excess +
+ '}';
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java
new file mode 100644
index 000000000000..bb5ddbf5ccc5
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java
@@ -0,0 +1,39 @@
+package ru.javawebinar.topjava.util;
+
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.model.MealTo;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.util.Arrays;
+import java.util.List;
+
+public class MealsUtil {
+ public static void main(String[] args) {
+ List meals = Arrays.asList(
+ new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500),
+ new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000),
+ new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500),
+ new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100),
+ new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000),
+ new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500),
+ new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410)
+ );
+
+ List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000);
+ mealsTo.forEach(System.out::println);
+
+// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000));
+ }
+
+ public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
+ // TODO return filtered list with excess. Implement by cycles
+ return null;
+ }
+
+ public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
+ // TODO Implement by streams
+ return null;
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java
new file mode 100644
index 000000000000..3d536a5c5ef2
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java
@@ -0,0 +1,9 @@
+package ru.javawebinar.topjava.util;
+
+import java.time.LocalTime;
+
+public class TimeUtil {
+ public static boolean isBetweenInclusive(LocalTime lt, LocalTime startTime, LocalTime endTime) {
+ return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0;
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java
new file mode 100644
index 000000000000..ef52d67576c0
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java
@@ -0,0 +1,23 @@
+package ru.javawebinar.topjava.web;
+
+import org.slf4j.Logger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class UserServlet extends HttpServlet {
+ private static final Logger log = getLogger(UserServlet.class);
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ log.debug("redirect to users");
+
+// request.getRequestDispatcher("/users.jsp").forward(request, response);
+ response.sendRedirect("users.jsp");
+ }
+}
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 000000000000..e9b900b26669
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ ${TOPJAVA_ROOT}/log/topjava.log
+
+
+ UTF-8
+ %date %-5level %logger{0} [%file:%line] %msg%n
+
+
+
+
+
+ UTF-8
+ %-5level %logger{0} [%file:%line] %msg%n
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000000..c63810c43593
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,19 @@
+
+
+ Topjava
+
+
+ userServlet
+ ru.javawebinar.topjava.web.UserServlet
+ 0
+
+
+ userServlet
+ /users
+
+
+
diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
new file mode 100644
index 000000000000..6253517f8b84
--- /dev/null
+++ b/src/main/webapp/index.html
@@ -0,0 +1,13 @@
+
+
+
+ Java Enterprise (Topjava)
+
+
+
+
+
+
+
diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp
new file mode 100644
index 000000000000..60bdc9b83192
--- /dev/null
+++ b/src/main/webapp/users.jsp
@@ -0,0 +1,11 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Users
+
+
+
+
+Users
+
+
\ No newline at end of file