diff --git a/.codacy.yml b/.codacy.yml
new file mode 100644
index 000000000000..311a8f4e0029
--- /dev/null
+++ b/.codacy.yml
@@ -0,0 +1,5 @@
+---
+exclude_paths:
+ - 'src/main/webapp/**'
+ - '**.md'
+ - '**.sql'
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000000..8f44cef8d05a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,26 @@
+# https://docs.travis-ci.com/user/languages/java/
+language: java
+jdk: openjdk8
+
+#https://dzone.com/articles/travis-ci-tutorial-java-projects
+cache:
+ directories:
+ - $HOME/.m2
+
+# https://docs.travis-ci.com/user/database-setup/#PostgreSQL
+before_script:
+ - psql -c 'create database topjava' -U postgres
+ - psql -c 'create user "user"; grant all privileges on database topjava to "user"' -U postgres
+
+# https://docs.travis-ci.com/user/customizing-the-build#Building-Specific-Branches
+branches:
+ only:
+ - master
+
+# https://stackoverflow.com/a/49852690/548473:
+services:
+ - postgresql
+
+# https://docs.travis-ci.com/user/notifications#Configuring-email-notifications
+#notifications:
+# email: false
\ No newline at end of file
diff --git a/README.md b/README.md
index 37200eff9508..d8e5a05a2003 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,15 @@
-[Java Enterprise Online Project](https://javaops.ru/view/topjava)
+<<<<<<< HEAD
+Java Enterprise Online Project
+=======
+[](https://www.codacy.com/gh/JavaWebinar/topjava/dashboard)
+[](https://travis-ci.com/JavaWebinar/topjava)
+
+Java Enterprise Online Project
+>>>>>>> e1593d8c8df53b13f8e284e60dbdd7b77cee37e4
===============================
-Разработка полнофункционального 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 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.
-
+
Когда вы слышите что-то, вы забываете это.
Когда вы видите что-то, вы запоминаете это.
@@ -12,39 +19,42 @@
Старинная китайская поговорка
## Описание и план проекта
-### Демо разрабатываемого приложения
+### Демо разрабатываемого приложения
### [Изменения проекта (Release Notes)](ReleaseNotes.md)
-### Требования к участникам, Wiki
+### Требования к участникам, Wiki
### Составление резюме, подготовка к интервью, поиск работы
-Обновленное вводное занятие (обязательно смотреть все видео)
+Вводное занятие (обязательно смотреть все видео)
===============
-##  1. [Вступление, история, команда, источники](doc/video1.md)
-
-## Обзор наиболее востребованных технологий, которые будут изучаться на курсе TopJava
-##  2.1. [Часть 1: инфраструктура](doc/video2.1.md)
-
-##  2.2. [Часть 2: frameworks Spring, ORM](doc/video2.2.md)
-
-##  2.3. [Часть 3: тренды](doc/video2.3.md)
-
-##  2.3. [Часть 4: обзор разрабатываемого приложения](doc/video2.4.md)
-
-##  3. [Рекомендуемые подходы к обучению на курсе](doc/video3.md)
-
-##  4. [Структура приложения (многоуровневая архитектура)](doc/video4.md)
-### [Демо приложения](http://javaops-demo.ru/topjava)
-
-##  5. [Системы управления версиями. Git](doc/video5-vcs-git.md)
-
-##  6. Работа с проектом (выполнять инструкции)
-- **ВНИМАНИЕ: выбирайте для проекта простой путь без пробелов и русских букв, например, `c:\projects\topjava\` (Windows). Иначе впоследствии будут проблемы**
-- **Плагин Git Intergation уже не требуется, а вкладку `Version control` в IDEA переименовали в `Git`**
-- **C Ultimate IDEA сейчас сложности**, надеемся что временные.
- - Скачать IDEA и установливать плагины [можно по инструкции](https://github.com/JavaOPs/topjava/wiki/IDEA#download)
- - После Trial 30 дней [способы продлить использование](https://github.com/JavaOPs/topjava/wiki/IDEA#licence)
-
-Для переключения режима отображения изменений из вкладки `Commit` в `Git: Local Changes` нужно переключить `Settings/Preferences | Version Control | Commit | Use non-modal commit interface` или в контекстном меню вкладки `Commit`:
+##  1. Осваиваем Java Enterprise. Трудоустройство. Ответы на вопросы.
+- Слайды презентации
+- Java Tools and Technologies Landscape Report 2016
+- [Java in 2017 Survey](http://www.baeldung.com/java-in-2017)
+- Из юниоров в разработчики: получаем первую работу
+
+#### Spring Pet-Clinic
+- Spring PetClinic Sample Application
+- Presentation
+
+##  2. Системы управления версиями. Git.
+- **Wiki по ведению проекта в Git**
+- Система управления версиями. VCS/DVSC.
+- Ресурсы:
+ - Интерактивная Git обучалка
+ - Еще одна интерактивная обучалка, по-русски
+ - Книга Git
+ - Working with remote repositories
+ - Видео по обучению Git
+ - 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`:
 
@@ -53,73 +63,49 @@
> Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео:
> - переименовал класс `UserMealWithExceed` и его поле `exceed` в `UserMealWithExcess.excess`
> - в `UserMeals/UserMealWithExcess` поля изменились на `private`
-> - обновил данные `UserMealsUtil.meals` и переименовал некоторые переменные, поля и методы
+> - обновил данные `UserMealsUtil.meals` и переименовал некоторые пременные, поля и методы
> - добавил `UserMealWithExcess.toString()` и метод для выполнения _Optional домашнего задания_
-> - метод фильтрации в `TimeUtil` переименовал в `isBetweenHalfOpen` (также изменилась логика сравнения: `startTime` включается в интервал, а `endTime` - не включается)
+> - метод фильтрации в `TimeUtil` переименовали в `isBetweenHalfOpen` (также изменилась логика сравнения - `startTime` включается в интервал)
-### GitHub поменял политику: теперь пушить нужно через токен. IDEA предложит его сгенерировать при пуше, или можно [создать токен в настройках](https://www.jetbrains.com/help/idea/github.html#register-account)
-- [Способы авторизации в GitHub](https://topjava.ru/blog/vvedeniye-v-git-github-ustanovka-i-nastroyka#6)
-
## Инструкция по шагам (из видео):
-- Установить ПО (Git, JDK8, IntelliJ IDEA, Maven)
+- Установить ПО (git, JDK8, IntelliJ IDEA, Maven)
- Создать аккаунт на GitHub
- Сделать Fork **ЭТОГО** проекта (https://github.com/JavaOPs/topjava)
- Сделать локальный репозиторий проекта:
-
-> Вместо Fork можно сделать [клонирование проекта](https://github.com/JavaOPs/topjava//Git#user-content-Клонирование-проекта): он не будет привязан к исходному https://github.com/JavaOPs/topjava и у него не будет истории.
-
-- Открыть и настроить проект в IDEA
+- Открыть и настроить проект в IDEA
- Выставить кодировку UTF-8 в консоли
- - Поставить кодировку UTF-8
- - Опционально: поменять шрифт по умолчанию на DejaVu или на **новый [JetBrains Mono](https://habr.com/ru/company/jugru/news/t/484134/)**
-- По ходу видео сделать `Apply Patch...` скачанного патча `Prepare_to_HW0.patch`
-- Закоммитить и запушить изменения (`commit` + `push`)
+ - Поставить кодировку UTF-8
+ - Опционально: Поменять фонт по умолчанию (DejaVu) или на **новый [JetBrains Mono](https://habr.com/ru/company/jugru/news/t/484134/)**
+- По ходу видео сделать Apply Patch... скаченного патча Prepare_ to_ HW0.patch
+- Закоммитить и запушить изменения (commit + push)
- Сделать ветку домашнего задания
-- Выполнить задание и залить на GitHub (`commit` + `push`)
-- Переключиться в основную ветку проекта `master`.
-
-##  7. [Maven](https://drive.google.com/file/d/1qEJTwv9FNUQjx-y9MSydH01xaAne0-hu)
-- [Как установить Maven 3 на Ubuntu или Windows](https://devcolibri.com/как-установить-maven-3-на-ubuntu-или-windows/)
-- [Руководство по Maven](https://topjava.ru/blog/apache-maven-osnovy-1)
-- Wiki: [Apache Maven](https://ru.wikipedia.org/wiki/Apache_Maven)
-- [The Central Repository](http://search.maven.org)
-- Дополнительно:
- - [Мой Wiki по Maven](https://github.com/JavaOPs/topjava/wiki/Maven)
- - [Основы Maven](https://www.youtube.com/watch?v=0uwMKktzixU)
- - JavaRush: [Основы Maven](https://javarush.ru/groups/posts/2523-chastjh-4osnovih-maven)
- - Инструмент сборки проектов [Maven](https://www.examclouds.com/ru/java/java-core-russian/lesson20)
- - [Maven Getting Started Guide](https://maven.apache.org/guides/getting-started/index.html)
- - [Видео: Maven vs Gradle vs SBT (Архипов, Борисов, Садогурский)](https://www.youtube.com/watch?v=21qdRgFsTy0)
- - [Build Lifecycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
- - [Dependency Mechanism](http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)
-
-##  8. [Как правильно относиться к техзаданию (ТЗ). Полуоткрытый интервал.](https://drive.google.com/file/d/1BpTzjNFjS0TSekCyt_xvt6YoLvuw5KTZ)
+- Выполнить задание и залить на GitHub (commit + push)
+- Переключиться в основную ветку проекта master.
+
+##  4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/1BpTzjNFjS0TSekCyt_xvt6YoLvuw5KTZ/view)
- [Типы промежутков](https://ru.wikipedia.org/wiki/Промежуток_(математика))
##  Домашнее задание HW0
-
-### ВНИМАНИЕ: НЕ НАДО в репозиторий делать Pull Request со своими решениями! См. видео выше ("Работа с проектом")
-
+```
Реализовать метод `UserMealsUtil.filteredByCycles` через циклы (`forEach`):
- должны возвращаться только записи между `startTime` и `endTime`
-- поле `UserMealWithExcess.excess` должно показывать, превышает ли сумма калорий за весь день значение `caloriesPerDay`
+- поле `UserMealWithExcess.excess` должно показывать,
+ превышает ли сумма калорий за весь день значение `caloriesPerDay`
-Т. е. `UserMealWithExcess` - это запись одной еды, но поле `excess` будет одинаково для всех записей за этот день.
+Т.е `UserMealWithExcess` - это запись одной еды, но поле `excess` будет одинаково для всех записей за этот день.
-> - Проверьте результат выполнения ДЗ (можно проверить логику в [http://javaops-demo.ru/topjava](http://javaops-demo.ru/topjava), список еды)
-> - Оцените Time complexity алгоритма. Если она больше O(N), например O(N*N) или N*log(N), сделайте O(N).
-> **Внимание: внимательно прочитайте про O(N). O - это любой коэффициент, 2*N это тоже O(N).**
-
+- Проверьте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды)
+- Оцените Time complexity алгоритма. Если она больше O(N), например O(N*N) или N*log(N), сделайте O(N).
+```
- Java 8 Date and Time API
- Алгоритмы и структуры данных для начинающих: сложность алгоритмов
-- [Сложность алгоритмов и Big O Notation](https://threadreaderapp.com/thread/1470666237286010881)
- [Головач: сложность алгоритмов в теме коллекций](https://www.youtube.com/watch?v=Ek9ijOiplNE&feature=youtu.be&t=778)
-- Time complexity
+- Time complexity
- Временная сложность алгоритма
- Вычислительная сложность
-#### ВНИМАНИЕ: варианты Optional делайте в одной ветке в разных методах `UserMealsUtil`. Проще делать, проще проверять
+#### ВНИМАНИЕ: варианты Optional делайте в `UserMealsUtil` в одной ветке в разных методах. Проще делать, проще проверять
### Optional (Java 8 Stream API)
```
@@ -134,67 +120,68 @@
- Лямбда-выражения в Java 8
- A Guide to Java 8
- Шпаргалка Java Stream API
-- Алексей Владыкин: Элементы функционального программирования в Java
+- Алексея Владыкин: Элементы функционального программирования в Java
- Yakov Fain о новом в Java 8
- stream.map vs forEach`
- - без циклов по другим коллекциям/массивам (к ним также относим методы коллекций `addAll()/removeAll()`)
-- через Stream API за 1 проход по исходному списку `meals.stream()`
- - нельзя использовать внешние коллекции, не являющиеся частью коллектора
- - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента.
-
-Временная сложность реализации должна быть O(N) (обратите внимание на п. 13 замечаний)
-Решение должно быть рабочим в общем случае (должно работать в приложении с многими пользователями, не только при запуске `main`)
-Нельзя 2 раза проходить по исходному списку (в том числе по его отфильтрованной или преобразованной копии)
+ - без циклов по другим коллекциям/массивам (к ним также относим методы коллекций `addAll()/removeAll()`)
+ - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main)
+- через Stream API за 1 проход по исходному списку `meals.streem()`
+ - нельзя использовать внешние коллекции, не являющиеся частью коллектора
+ - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной или преобразованной копии)
+ - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента.
+
Ресурсы:
-- [Baeldung: Custom Collectors](https://www.baeldung.com/java-8-collectors#Custom)
+- [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)
-- [Хватит писать циклы! Топ-10 лучших методов для работы с коллекциями из Java 8](https://javarush.ru/groups/posts/524-khvatit-pisatjh-ciklih-top-10-luchshikh-metodov-dlja-rabotih-s-kollekcijami-iz-java8)
-- [Понять Java Stream API](https://vc.ru/u/604567-yerlan-akzhanov/194409-ponyat-java-stream-api)
### Замечания по использованию Stream API:
-- Когда встречаешь что-то непривычное, приходится перестраивать мозги. Например, переход с процедурного на ООП-программирование дается непросто. Те, кто не знает шаблонов (и не хотят учить), также их встречают плохо. Хорошая новость в том, что если это принять и начать использовать, то начинаешь получать от этого удовольствие. И тут главное не впасть в другую крайность:
+- Когда встречаешь что-то непривычное, приходится перестраивать мозги. Например, переход с процедурного на ООП программирование дается непросто. Те, кто не знает шаблонов (и не хотят учить) также их встречают плохо. Хорошая новость в том, что если это принять и начать использовать, то начинаешь получать от этого удовольствие. И тут главное не впасть в другую крайность:
- [Используйте Stream API проще (или не используйте вообще)](https://habrahabr.ru/post/337350/)
-- Если вас беспокоит производительность стримов, обязательно прочитайте про оптимизацию
+- Если вас беспокоить производительность стримов, обязательно прочитайте про оптимизацию
- ["Что? Где? Когда?"](http://optimization.guide/intro.html)
- [Перформанс: что в имени тебе моём?](https://habrahabr.ru/company/jugru/blog/338732/)
- [Performance это праздник](https://habrahabr.ru/post/326242/)
-При использовании Stream API производительность улучшится только на больших задачах, где возможно распараллеливание.
-Еще: просто так запустить и померить скорость JVM нельзя (как минимум надо дать прогреться и запустить очень большое число раз). Лучше использовать какие-нибудь бенчмарки, например [JMH](http://tutorials.jenkov.com/java-performance/jmh.html), который мы используем на другом проекте (Mastejava).
+При использовании Stream API производительность улучшиться только на больших задачах, где возможно распараллеливание.
+Еще - просто так запустить и померять скорость JVM нельзя (как минимум дать прогреться и запустить очень большое число раз). Лучше использовать какие-нибудь бенчмарки, например [JMH](http://tutorials.jenkov.com/java-performance/jmh.html), который мы юзаем на другом проекте (Mastejava).
##  Замечания к HW0
-- 1: Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно, при правках в рабочем проекте обязательно нужно проконсультироваться/проревьюироваться у авторов кода (находятся по истории VCS)
-- 2: Наследовать `UserMealWithExcess` от `UserMeal` нельзя, т. к. это разные сущности: Transfer Object и Entity. Мы будем их проходить на 2-м уроке. Это относится и к их зависимости друг от друга.
-- 3: Правильная реализация должна быть простой и красивой, можно сделать 2-мя способами: через стримы и через циклы. Сложность должна быть O(N), т. е. без вложенных стримов и циклов.
+- 1: Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно при правках в рабочем проекте обязательно нужно проконсультироваться/проревьюироваться у авторов кода (находится по истории VCS)
+- 2: Наследовать `UserMealWithExcess` от `UserMeal` нельзя, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. Это относится и к зависимости.
+- 3: Правильная реализация должна быть простой и красивой, можно сделать 2-мя способами: через стримы и через циклы. Сложность должна быть O(N), т.е. без вложенных стримов и циклов.
- 4: При реализации через циклы посмотрите в `Map` на методы `getOrDefault` или `merge`
- 5: **При реализации через `Stream` заменяйте `forEach` оператором `stream.map(..)`**
-- 6: Объявляйте переменные непосредственно перед использованием (если возможно - сразу с инициализацией). При объявлении коллекций в качестве типа переменной используйте интерфейс (Map, List, ..)
-- 7: Если IDEA предлагает оптимизацию (желтым подчеркивает), например, заменить лямбду на ссылку на метод (method reference), соглашайтесь (Alt+Enter)
+- 6: Объявляйте переменные непосредственно перед использованием (если возможно - сразу с инициализацией). При объявлении коллекций используйте тип переменной - интерфейс (Map, List, ..)
+- 7: Если IDEA предлагает оптимизацию (желтым подчеркивает), например заменить лямбду на метод-референс, соглашайтесь (Alt+Enter)
- 8: Пользуйтесь форматированием кода в IDEA: `Alt+Ctrl+L`
-- 9: Перед check-in (отправкой изменений на GitHub) просматривайте внесенные изменения (Git -> [Log](https://www.jetbrains.com/help/idea/log-tab.html) -> курсор на файл и Ctrl+D): не оставляйте в коде ничего лишнего (закомментированный код, TODO и пр.). Если файл не меняется (например только пробелы или переводы строк), не надо его чекинить, делайте ему `revert` (Git -> Revert / `Ctrl+Alt+Z`).
-- 10: `System.out.println` нельзя использовать нигде, кроме как в `main`. Позже введем логирование.
-- 11: Результаты, возвращаемые `UserMealsUtil.filteredByStreams`, мы будем использовать [в нашем приложении](http://javaops-demo.ru/topjava) для фильтрации по времени и отображения еды правильным цветом.
-- 12: Обращайте внимание на комментарии к вашим коммитам в Git. Они должны быть короткие и информативные (лучше на english)
+- 9: Перед check-in проверяйте чендж-лист (курсор на файл и Ctrl+D): не оставляйте в коде ничего лишнего (закомментированный код, TODO и пр.). Если файл не меняется (например только пробелы или переводы строк), не надо его чекинить, делайте ему `revert` (Git -> Revert / `Ctrl+Alt+Z`).
+- 10: `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование.
+- 11: Результаты, возвращаемые `UserMealsUtil.filteredByStreams` мы будем использовать [в нашем приложении](http://topjava.herokuapp.com/) для фильтрации по времени и отображения еды правильным цветом.
+- 12: Обращайте внимание на комментарии к вашим коммитам в git. Они должны быть короткие и информативные (лучше на english)
- 13: Не полагайтесь в решении на то, что список еды будет подаваться отсортированным. Такого условия нет.
-----
-
-> - ДЗ первого урока будет связано с созданием небольшого [CRUD](https://ru.wikipedia.org/wiki/CRUD)-приложения (в памяти, без базы данных) на JSP и сервлетах
-> - основы JavaScript необходимы для понимания проекта, начиная с 8-го занятия
+## [Пример 7-го занятия онлайн стажировки, несколько видео открыто](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md)
### Полезные ресурсы
+> ВНИМАНИЕ:
+> - **ДЗ первого урока будет связано с [созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах](https://danielniko.wordpress.com/2012/04/17/simple-crud-using-jsp-servlet-and-mysql/)**. Введение будет, но предварительное знакомство не помешает.
+> - основы 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/)
-- [Видео по WEB-технологиям](https://www.youtube.com/user/WebMagistersRu/playlists)
-- [Изучение JavaScript в одном видеоуроке за час](https://www.youtube.com/watch?v=QBWWplFkdzw)
+- [Видео по WEB технологиям](https://www.youtube.com/user/WebMagistersRu/playlists)
+- [Изучение JavaScript в одном видео уроке за час](https://www.youtube.com/watch?v=QBWWplFkdzw)
- HTML, CSS, JAVASCRIPT, SQL, JQUERY, BOOTSTRAP
- Введение в программирование на JavaScript
- Стандарты кодирования для HTML, CSS и JavaScript’a
@@ -205,8 +192,8 @@
- jQuery для начинающих
#### Java (базовые вещи)
-- [Сборник видео "Изучаем Java"](https://www.youtube.com/playlist?list=PLyxk-1FCKqockmP-fXZmHQ7UlYP3qvZRa)
-- 1-й урок MasterJava: Многопоточность
+- Интуит. Программирование на Java
+- 1й урок MasterJava: Многопоточность
- [Основы Java garbage collection](http://web.archive.org/web/20180831013112/https://ggenikus.github.io/blog/2014/05/04/gc)
- Размер Java объектов
- Введение в Java Reflection API
@@ -226,12 +213,6 @@
- Как создать Servlet? Полное руководство.
- [Сервлеты](https://metanit.com/java/javaee/4.1.php)
-#### Туториалы по Spring
-- [Юрий Ткач: Spring Framework - The Basics](https://www.youtube.com/playlist?list=PL6jg6AGdCNaWF-sUH2QDudBRXo54zuN1t)
-- [Java Brains: Spring Framework](https://www.youtube.com/playlist?list=PLC97BDEFDCDD169D7)
-- [Тимур Батыршинов: Spring Core - основы фреймворка, ядро](https://www.youtube.com/watch?v=CfHDr-19WWY&list=PL8X2nqRlWfaYYP1-qXjdPKE7bXYkl6aL4)
-- [alishev: Spring Framework](https://www.youtube.com/playlist?list=PLAma_mKffTOR5o0WNHnY0mTjKxnCgSXrZ)
-
#### JDBC, SQL
- Основы SQL на примере задачи
- Уроки по JDBC
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 40fef33ddfb3..7c03206fe59c 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1,43 +1,6 @@
# TopJava Release Notes
-### TopJava 26
-- починили шаблоны запросов на создание в Swagger через [ApiModelProperty](https://www.baeldung.com/spring-swagger-hide-field)
-- в новой версии Data JPA `getById`->`getReferenceById`
-- мелкие правки
-
-### TopJava 25
-- поправил `NoHtmlValidator`: `Jsoup.clean().equals` -> `Jsoup.isValid`
-- починил `role VARCHAR NOT NULL`
-- добавил нового тестового пользователя `guest`
-
-### TopJava 24
-- migrate to LTS JDK 17
-- add `AbstractBaseEntity.id()`
-- fix for `User.registered`: `@Column(..., updatable = false)`
-- add `@QueryHints` in `CrudUserRepository`
-- add loggin in `RootController`
-- fix `@Sql(.., , executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)` for controller tests after service tests
-- rename `MATCHER` to `USER_MATCHER/MEAL_MATCHER`
-- treat in `ActiveDbProfileResolver` attribute `profiles`
-
-### 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
+### Topjava 21
- **добавили документирование REST API: Swagger**
- мигрировали на JDK 15 и используем текстовые блоки
- Вынес `produces = MediaType.APPLICATION_JSON_VALUE` на уровень контроллеров
@@ -47,7 +10,7 @@
- Из тестов сервисов убрал `throws Exception` (в IDEA больше не генерятся по умолчанию)
- **Мигрировали на Spring Boot 2.4.1**
-### TopJava 20
+### Topjava 20
- мигрировали на JDK 14
- в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`)
- в `topjava.common.js` в `makeEditable()` вместо объекта контекст передаю 3 параметра
@@ -57,7 +20,7 @@
- в 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
+### Topjava 19
- Изменилась логика для интервалов времени (исключаем `endTime`)
- Заменил собственный `MessageUtil` велосипед на спринговый `MessageSourceAccessor`
- В ролях убрал префиксы `ROLE_` ([Role and GrantedAuthority](https://stackoverflow.com/a/19542316/548473))
@@ -73,7 +36,7 @@
- Для `InMemory` тестов подключаю только `inmemory.xml` (добавил туда необходимую конфигурацию из `spring-app.xml`)
-### TopJava 18
+### Topjava 18
- В `ErrorType` добавил `HttpStatus status`
- В PostgreSQL обнаружилась бага: граничное значение `0:00` из-за ошибок округления попадает в предыдущий интервал.
@@ -91,7 +54,7 @@
- В тестах `delete` и `create` проверяю результат напрямую (не через `getAll`)
-### TopJava 17
+### Topjava 17
- Удалил `Impl` из названий репозиториев
- Удалил интерфейсы к сервисам, использую классы
- Добавил `AdminRestController.enable`, вызов через PATCH метод
@@ -99,13 +62,13 @@
- Перенес работу в UI с профилем из `RootController` в `ProfileUIController`
- `SLF4JBridgeHandler` инициализирую только в профиле `postgres`
-### TopJava 16
+### Topjava 16
- Выделил общий код реализации хранения в памяти в `InMemoryBaseRepositoryImpl`
- Сделал подтверждение для удаления записей
- Обновились видео 7-го занятия. [Выложил его как пример занятия, некоторые видео открыты](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md)
- Сделали валидации дублирования email через `WebDataBinder` и `Validator`
-### TopJava 15
+### Topjava 15
- Миграция на Servlet API 4.0 / Tomcat 9.x
- [Миграция на JDK11](http://javaops.ru/view/resources/jdk8_11)
- JUnit5 fix: junit-platform-surefire-provider не нужен
@@ -115,7 +78,7 @@
- В javascript место глабальных переменных и одинаковой функции обновления таблицы задаю их в объекте контекст, который передаю в `makeEditable()` как параметр
- Починил `back` в браузере после логина. Кнопки входа и регистрации отображаю только для `isAnonymous()`
-### TopJava 14
+### Topjava 14
- [Миграция на JUnit 5](http://javaops.ru/view/resources/junit5)
- Для измерения времени в тестах использую [Spring StopWatch](https://www.logicbig.com/how-to/code-snippets/jcode-spring-framework-stopwatch.html)
- `SimpleJdbcInsert` и `NamedParameterJdbcTemplate` конструируются (и берут настройки) из `jdbcTemplate`
@@ -128,13 +91,13 @@
- Преименовал js файлы согласно [javascript filename naming convention](https://stackoverflow.com/questions/7273316/what-is-the-javascript-filename-naming-convention)
- Сделал проверку startTime/endTime на фильтре времени (после обновления datetimepicker до 2.5.20)
-### TopJava 13
+### Topjava 13
- [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/)
- Добавил [Responsive behaviors](https://getbootstrap.com/docs/4.1/components/navbar/#responsive-behaviors) - при уменшении ширины экрана навигация сворачивается в кнопку
- Для отображения цвета еды и выключенного юзера использую [data-* атрибуты](https://developer.mozilla.org/ru/docs/Web/Guide/HTML/Using_data_attributes)
- В `inputField.tag` передаю как параметр код для локализации label, а в `i18n.jsp` передаю как параметр `page`. См. [JSP include action with parameter example](https://beginnersbook.com/2013/12/jsp-include-with-parameter-example)
-### TopJava 12
+### Topjava 12
- [Миграция на Spring 5](http://javaops.ru/view/resources/spring5)
- обновил версии: Ehcache 3.x, datatables, datetimepicker
- добавил видео решений HW0 с одним проходом
@@ -147,7 +110,7 @@
- заменил в jQuery [success на done](https://stackoverflow.com/a/22213543/548473)
- вместо `lang.jsp` сделал общий `bodyHeader.jsp`
-### TopJava 11
+### Topjava 11
- добавил
- доп. решение HW1 через одним return и O(N)
- раскрасил лог ([Logback layouts coloring](https://logback.qos.ch/manual/layouts.html#coloring))
@@ -173,7 +136,7 @@
- переименовал `ModelMatcher` в `BeanMatcher` и починил: можно сравнивать только упорядоченные коллекции (List)
- поменял зависимость `org.hibernate:hibernate-validator` на `org.hibernate.validator:hibernate-validator` (warning при сборке)
-### TopJava 10
+### Topjava 10
- добавил
- доступ к AuthorizedUser через [`@AuthenticationPrincipal`](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#mvc-authentication-principal) и [authentication Tag](http://docs.spring.io/spring-security/site/docs/current/reference/html/taglibs.html#the-authentication-tag)
- [Обработку 404 NotFound](https://stackoverflow.com/questions/18322279/spring-mvc-spring-security-and-error-handling)
@@ -202,7 +165,7 @@
- distinct из запроса Hibernate на пользователей с ролями. [Оптимизация запроса distinct: 15.16.2](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct)
- лишние `
` тэги (`shadow` и `view-box`)
-### TopJava 9
+### Topjava 9
- добавил
- выбор профиля базы через `ActiveProfilesResolver`/`AllActiveProfileResolver` на основе драйвера базы в classpath
- видео Cascade. Auto generate DDL.
@@ -221,7 +184,7 @@
- вместо `Persistable` ввел интерфейс `HasId` и наследую от него как Entity, так и TO
- сделал универсальную обработку исключений дублирования email и dateTime
-### TopJava 8
+### Topjava 8
- добавил:
- [защиту от XSS (Cross-Site Scripting)](http://stackoverflow.com/a/40644276/548473)
- интеграцию с Dependency Ci и Travis Ci
@@ -245,7 +208,7 @@
- перенес вызовы `UserUtil.prepareToSave` из `AbstractUserController` в `UserServiceImpl`
- зарефакторил обработку ошибок (`ExceptionInfoHandler`)
-### TopJava 7
+### Topjava 7
- добавил:
- [JPA 2.1 EntityGraph](https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs002.htm)
- [Jackson @JsonView](https://habrahabr.ru/post/307392/)
@@ -260,7 +223,7 @@
- матчеров тестирования (сделал автоматические обертки и сравнение на основе передаваемого компаратора)
- вынес форматирование даты в `functions.tld`
-### TopJava 3-6
+### Topjava 3-6
- добавил
- [выпускной проект](https://drive.google.com/open?id=0B9Ye2auQ_NsFcG83dEVDVTVMamc)
- в таблицу meals составной индекс
diff --git a/cv.md b/cv.md
index 2f01659fea12..73ca6a62be71 100644
--- a/cv.md
+++ b/cv.md
@@ -3,9 +3,8 @@

- Научиться программировать сложнее, чем кажется
-- [Собеседование. Разработка ПО. Вопросы.](https://drive.google.com/file/d/0B9Ye2auQ_NsFQVc2WUdCR0xvLWM/view?usp=sharing&resourcekey=0-HaWoRxoyboMSKjg5P2I1cQ)
+- [Собеседование. Разработка ПО. Вопросы.](https://drive.google.com/open?id=0B9Ye2auQ_NsFQVc2WUdCR0xvLWM)
- [Набор ссылок для тренировки и прохождения интервью](https://github.com/andreis/interview)
-- [Полезные советы Jun-ам](https://github.com/JavaOPs)
### Составление резюме:
- [VisualCV: create resume in minutes](https://www.visualcv.com/)
@@ -35,26 +34,21 @@
## [Тестовое собеседование, самые спрашиваемые темы](http://javaops.ru/interview/test.html)
### Интервью:
-- [Собеседования для бэкендеров: как готовиться, тренироваться и не облажаться](https://habr.com/ru/companies/getmatch/articles/744760/)
-- [10 неочевидных фактов об ИТ-собеседованиях](https://proglib.io/p/molchanie-intervyuera-ili-10-neochevidnyh-faktov-ob-it-sobesedovaniyah-2022-06-14)
- Михаил Портнов. Собеседование на работу: как продать себя грамотно
- Михаил Портнов. Какие вопросы мы задаем на собеседовании?
- Михаил Портнов. Собеседование на работу: жизненный путь
-- [Лёша Корепанов. Признаки плохих компаний для программиста](https://www.youtube.com/watch?v=Sj-WSWr-n7U)
-- [Лёша Корепанов. Как отвечать на вопросы, которые ты не знаешь. Техническое интервью для программиста](https://www.youtube.com/watch?v=Beoh3tfgPEk)
- Канал: Резюме, поиск работы, интервью
- Яков Файн: Как стать профессиональным Java разработчиком
- Ответы на вопросы на собеседовании Junior Java Developer
- Список вопросов с ответами для собеседования по Java
- Сборка по вопросам на интервью
- Сборка вопросов-ответов от JavaStudy
-- [Видео технических интервью от Максима Добрынина](https://www.youtube.com/playlist?list=PLxqzxxW1gWwJvVK11R_lJKAlP_9m3Gu2H)
- [Вопросы по классам коллекциям от JavaRush-1](http://info.javarush.ru/translation/2013/10/08/Часто-задаваемые-на-собеседованиях-вопросы-по-классам-коллекциям-в-Java-Часть-1-.html)
- [Вопросы по классам коллекциям от JavaRush-2](http://info.javarush.ru/translation/2013/10/08/Часто-задаваемые-на-собеседованиях-вопросы-по-классам-коллекциям-в-Java-Часть-2-.html)
- Тест на знание SQL
- Вопросы на собеседовании Java Junior Developer
- Java вопросы с собеседований на Android
-- Сборка вопросов от JavaRush
+- Сборка вопросов от JavaRush
> про clone и finalize объязательно прочтите Джошуа Блох: Java. Эффективное программирование (второе издание)
- Cracking the Coding Interview
@@ -65,24 +59,24 @@
- email, skype - очень желательно, чтобы по ним вы были узнаваемы. Заведите рабочие, если не так.
- написать ВЕСЬ IT опыт (исключая опыт пользователя: Windows, MS Word, Photophop, Yandex disk, Google docs, ..): технологии, какие задачи решали (конкретные), какие инструменты использовали, VCS, DB, инструменты сборки, ... включая опыт в ВУЗе.
- на English иметь желательно. Если вакансия опублинована на Englsih - шлите на нем. Часто могут на нем попросить, если работодатель иностранный.
-- удобно иметь резюме где-то в Интернете (hh, linkedin, google doc, чтобы им было удобно делиться).
+- удобно иметь резюме где то в инете (hh, linkedin, google doc, чтобы им было удобно делиться).
-### Позиционирование проекта TopJava:
+### Позиционирование проекта Topjava:
- Обязательно убери из резюме **любое упоминание Junior**. Количество обращений возрастет на порядок. Ссылку на стажировку можно поставить: http://javaops.ru/view/topjava (в linkedin: https://www.linkedin.com/company/java-online-projects).
-- После завершения проекта ты освоишь все заявленные в нем технологии - вставь их в квалификацию (включая Java 8 Stream and Time API).
+- После завершения проекта ты освошь все заявленные в нем технологии - вставь их в квалификацию (включая java 8 Stream and Time API).
- В разделе опыт работы (если нет коммерческого опыта) вставь:
Участие в разработке Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей
на стеке Maven/ Spring MVC/ Security/ REST(Jackson)/ Java 8 Stream API:
- реализация сохранения в базы Postgres и HSQLDB на основе Spring JBDC, JPA(Hibernate) и Spring-Data-JPA
- реализация и тестирование REST и AJAX контроллеров
- - реализация клиента на Bootstrap (css/js), DataTables, jQuery + plugins.
+ - реализация клиента на Bootstrap (css/js), datatables, jQuery + plugins.
- собственная доработка проекта
- Делай упор не на обучение, а на **участие в проекте**. Выполнение домашних заданий это полноценное участие с написанием функционала по всем пройденным технологиям. На собеседовании смотрят не на то, что ты заканчивал, а на опыт и знания.
### В процессе обучения
-- Если рассматриваешь предложения по работе, подними в своем профиле этот флаг и обязательно заполни ссылку на резюме. Обновления нашей базы выпускников смотрят уже более 125 партнеров по трудоустройству (компании и индивидуальные рекрутеры). Проверь содержание "Информация для HR": по нему принимают решение, открывать резюме или нет.
+- Если рассмотриваешь предложения по работе, подними в своем профиле этот флаг и обязательно заполни ссылку на резюме. Обновления нашей базы выпускников смотрят уже более 125 партнеров по трудоустройству (компании и индивидуальные рекрутеры). Проверь содержание "Информация для HR": по нему принимают решение, открывать резюме или нет.
- Вступайте в нашу группу участников Slack: каналы помощи с Java, отзывы о работодателях, обсуждение тестовых заданий, вакансии, цены на рынке труда, IT события, интересные видео и многое другое.
@@ -91,7 +85,6 @@
### После прохождения испытательного срока жду твою [историю успеха](http://javaops.ru/view/story)
### Основные сайты поиска работы:
-- [Актуальная подборка Junior вакансий от CodeReview](https://jobs.yourcodereview.com?utm_source=partner&utm_medium=javaops&utm_campaign=landing)
- Яндекс агрегатор
- HH
- LinkedIn
@@ -99,34 +92,28 @@
- [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)
-
Как выжить на испытательном сроке
+## Как выжить на испытательном сроке
- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В
процессе формулирования очень часто приходит ее решение.
- Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки
разработчика. В логах надо читать верх самого нижнего эксепшена - там причина всей портянки.
-- Грамотно уделяй время каждой проблеме. Две крайности: сразу бросаться за помощью и
- биться над проблемой часами.
- Пробуй решить ее сам и, в зависимости от проблемы, выделяй на это разумное время.
-- Не бросайся сразу писать код, поищи в проекте схожий функционал! И далее - минимум своих подходов, если хотите внести что-то свое - обязательно спросите!
-Проект должен быть однотипным по максимуму во всех мелочах: - описание, пакеты, имена классов и методов и даже переменных, реализация, хелп.
-Обычно можно найти сходный функционал, взять его себе и делать в нем изменения под свою реализацию.
-- Если тебе что-то объясняют по проекту - обязательно записывай.
-- Когда получаешь задачу - уточни все очень подробно. Если задач несколько - обязательно выясни приоритеты!
-- Получай в процессе решения обратную связь - в том ли направлении ты идешь
-- Не игнорируй совместные ланчи (курилки)
-- Готовься к стендапам/летучкам. Задавай на них вменяемые вопросы. Выказывай заинтересованность
+- Грамотно уделяй время каждой проблеме. Две крайности - сразу бросаться за помощью и
+ бится нам ней часами.
+ Пробуй решить ее сам и в зависимости от проблемы выделяй на это разумное время.
+- Если тебе что-то объясняют по проекту - обязательно записывай.
+- Когда получаешь задачу - уточни все очень подробно.
+- Получай в процессе решения обратную связь - в том ли направлении ты идешь.
+- Не игнорируй совместные ланчи (курилки)
+- Готовься к стендапам/летучкам. Задавай на них вменяемые вопросы. Выказывай заинтересованность
- Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи.
- [**5 вещей, которые разработчик должен сделать прежде чем попросить о помощи**](https://techrocks.ru/2018/07/16/5-things-a-developer-should-do-before-asking-for-help/)
-- [**Советы новичкам**](https://blog.csssr.com/ru/article/how-to-be-a-beginner-developer)
+- [**Советы новичкам**](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)
+## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246)
diff --git a/description.md b/description.md
index aa98eb24fea0..58fb5d2cdf74 100644
--- a/description.md
+++ b/description.md
@@ -1,18 +1,19 @@
-#### Разработка полнофункционального 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 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-кодированию и архитектуре приложения.
+- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения.
- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий.
-- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit-тестов.
-- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект: например, используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой Hibernate, делаем конвертеры для типов LocalDateTime (Java 8 time API).
+- Большое внимание уделяется тестированию кода: в проекте более 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.
+- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и 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
@@ -26,9 +27,9 @@
- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM.
- Настройка Database в IDEA.
- Скрипты инициализации базы. Spring Jdbc Template.
-- Spring: инициализация и популирование БД
+- Spring: инициализация и популирование DB
- ORM. Hibernate. JPA.
-- [Тестирование JPA-сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw)
+- [Тестирование JPA сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw)
- Поддержка HSQLDB
- Транзакции
- Профили Maven и Spring
@@ -44,8 +45,8 @@
- Spring Web MVC
- Spring Internationalization
- Тестирование Spring MVC
-- REST-контроллеры
-- Тестирование REST-контроллеров. Jackson.
+- REST контроллеры
+- Тестирование REST контроллеров. Jackson.
- jackson-datatype-hibernate. Тестирование через матчеры.
- Тестирование через SoapUi. UTF-8
- WebJars.
@@ -53,9 +54,9 @@
- AJAX. jQuery. Notifications.
- Spring Security
- Spring Binding/Validation
-- Работа с DataTables через Ajax.
+- Работа с datatables через Ajax.
- Spring Security Test
-- [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/file/d/0B9Ye2auQ_NsFRTFsTjVHR2dXczA/view?usp=sharing&resourcekey=0-Ou4A_gRor5HaRho4Fciqdw)
+- [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/open?id=0B9Ye2auQ_NsFRTFsTjVHR2dXczA)
- Encoding password
- CSRF (добавление в проект защиты от межсайтовой подделки запроса)
- form-login. Spring Security Taglib
@@ -64,16 +65,8 @@
- Смена локали
- Фильтрация JSON с помощью @JsonView
- Защита от XSS (Cross Site Scripting)
-- Деплой на [собственный выделенный сервер](https://github.com/JavaOPs/startup)
+- Деплой в 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 b958f9d2a74a..c4f4992a61df 100644
--- a/doc/lesson07.md
+++ b/doc/lesson07.md
@@ -1,183 +1,71 @@
-# [Онлайн стажировка Spring 5/JPA Enterprise (TopJava)](http://javaops.ru/view/topjava)
+# [Онлайн стажировка Spring 5/JPA Enterprise (Topjava)](http://javaops.ru/view/topjava)
## [Почему мы?](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](#user-content--8-тестирование-rest-контроллеров-через-jsonassert-и-матчеры)
-- Не стоит стремиться прочитать все ссылки урока, их можно использовать как справочник. Гораздо важнее пройти основной материал урока и сделать домашнее задание
+## 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)
+- Не стоит стремиться прочитать все ссылки урока, их можно использовать как справочник. Гораздо важнее пройти основной материал урока и сделать Домашнее Задание
- Обязательно посмотри правила работы с патчами на проекте
-- Делать Apply Patch лучше по одному непосредственно перед видео на эту тему, а при просмотре видео сразу отслеживать все изменения кода проекта по изменению в патче (`Version Control -> Local Changes -> Ctrl+D`)
+- Делать Apply Patch лучше по одному, непосредственно перед видео на эту тему, а при просмотре видео сразу отслеживать все изменения кода проекта по изменению в патче (`Version Control->Local Changes-> Ctrl+D`)
- При первом Apply удобнее выбрать имя локального ченджлиста Name: Default. Далее все остальные патчи также будут в него попадать.
-- Код проекта обновляется и не всегда совпадает с видео (можно увидеть, как развивался проект). Изменения в проекте указываю после соответствующего патча.
+- Код проекта обновляется и не всегда совпадает с видео (можно увидеть как развивался проект). Изменения в проекте указываю после соответствующего патча.
##  Разбор домашнего задания HW6
###  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
-
-> сделал фильтрацию еды через `get`: операция идемпотентная, можно делать в браузере обновление по F5
-
-### Внимание: чиним пути в следующем патче
+При переходе на AJAX `JspMealController` удалим за ненадобностью, возвращение всей еды `meals()` останется в `RootController`.
#### 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_jdbc_transaction_roles.patch`
+`JdbcUserServiceTest` отвалились. Будем чинить в `7_06_HW6_optional_jdbc.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.
-- В `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_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` и проверкой профилей мы можем ...
+- Тест `DataJpaUserServiceTest.testGetWithMeals()` не работает для admin (у админа 2 роли, и еда при JOIN дублируется). `DISTINCT` при нескольких JOIN не помогает.
+Оставил в графе только `meals`. Корректно поставить тип `LOAD`, чтобы остальные ассоциации доставались по стратегии модели. Однако [с типом по умолчанию `FETCH` роли также достаются](https://stackoverflow.com/a/46013654/548473) (похоже, что бага).
-#### 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)
+#### 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
+```
## Занятие 7:
-
-###  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
-
+###  3. Тестирование Spring MVC
+#### Apply 7_07_controller_test.patch
> - в `MockMvc` добавился `CharacterEncodingFilter`
-> - добавил [`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` (который будет вызван по окончанию работы тестового класса) - выведем результат работы этого секундомера в лог;
+> - добавил `AllActiveProfileResolver`
+> - реализация Ehcache нетранзакционная, после отката транзакции в тестах по `@Transactional` Hibernate-кеш не восстанавливал роль для USER. Добавил очистку кэша Hibernate в `AbstractControllerTest.setUp` (с учетом того, что `Profiles.REPOSITORY_IMPLEMENTATION` можно переключить на `JDBC`).
-8. Аннотации `@ContextConfiguration` и `@ExtendWith(SpringExtension.class)` (замена `@RunWith`) мы можем заменить одной `@SpringJUnitConfiguration` (старые версии IDEA ее не понимают)
-
-
-
-#### Apply 7_10_JUnit5.patch
+- Hamcrest
+- Unit Testing of Spring MVC Controllers
+###  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)
+> - [Наконец пофиксили баг с `@SpringJUnitConfig`](https://youtrack.jetbrains.com/issue/IDEA-166549). Можно обновить IDEA до 2019.2 (обновите, только если достаточно памяти: минимум 3Gb. `Help menu -> Edit Custom VM Options`)
- [JUnit 5 homepage](https://junit.org/junit5)
- [Overview](https://junit.org/junit5/docs/snapshot/user-guide/#overview)
@@ -191,376 +79,114 @@ JUnit предоставляет нам набор коллбэков — инт
- [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` выполняем аналогичным способом с учетом того, что пользователь имеет доступ только к своим данным.
-Если на данном этапе попытаться запустить приложение и обратиться к какому-либо методу контроллера, сервер ответит нам ошибкой со статусом 406,
-так как Spring не знает, как преобразовать объект User в JSON...
-
-
-
-#### Apply 7_11_rest_controller.patch
-
-- Понимание REST
-- JSON (JavaScript Object Notation)
+###  5. [Принципы REST. REST контроллеры](https://youtu.be/33_2yOck4ak)
+#### Apply 7_09_rest_controller.patch
+
+- Понимание REST
+- JSON (JavaScript Object Notation)
- [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)
-- [10 Best Practices for Better RESTful](https://medium.com/@mwaysolutions/10-best-practices-for-better-restful-api-cbe81b06f291)
+- [10 Best Practices for Better RESTful](http://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/)
- [Best practices for rest nested resources](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources)
--
- 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).
-Теперь Spring будет автоматически использовать эту библиотеку для сериализации/десериализации объектов в 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` - утильный класс и не является
-бином Spring, а для его работы требуется наш кастомный маппер, который находится под управлением
-Spring и расположен в контейнере зависимостей. Поэтому, чтобы была возможность получить
-наш маппер из других классов, сделаем его синглтоном и сделаем в нем статический
-метод, который будет возвращать его экземпляр. Теперь `JsonUtil` сможет его получить.
-И нам нужно указать Spring, чтобы он не создавал второй экземпляр этого объекта, а клал в свой контекст существующий.
-Для этого в конфигурации `spring-mvc.xml` определим factory-метод, с помощью которого Spring должен
-получить экземпляр (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`
-
+- 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
- [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_15_tests_refactoring.patch
-
-> - Методы из класса `TestUtil` перенес в `MatcherFactory`, лишние удалил.
-> - Раньше в тестах я для проверок получал всех users из базы и сравнивал с эталонным списком. При этом тесты становятся чувствительными ко всем users в базе и ломаются при добавлении/удалении новых тестовых данных.
-
-- [Java @SafeVarargs Annotation](https://www.baeldung.com/java-safevarargs)
-
-###  9. Тестирование через SoapUI. UTF-8
-
-
- Краткое содержание
-
-SoapUI - это один из инструментов для тестирования API приложений, которые работают по REST и по SOAP.
-Он позволяет нам по протоколу HTTP дернуть методы нашего API и увидеть ответ контроллеров.
-
-Если в контроллер мы добавим метод, который в теле ответа будет возвращать текст на кириллице, то увидим, что кодировка теряется.
-Для сохранения кодировки используем `StringHttpMessageConverter`, который конфигурируем в `spring-mvc.xml`.
-При этом мы должны явно указать, что конвертор будет работать только с текстом в кодировке *UTF-8*.
-
-
+#### Apply 7_13_tests_refactoring.patch
-#### Apply 7_16_soapui_utf8_converter.patch
+###  9. Тестирование через SoapUi. UTF-8
+#### Apply 7_14_soapui_utf8_converter.patch
- Инструменты тестирования 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
+ - Написание 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 из `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](http://web.archive.org/web/20190102203042/http://forum.spring.io/forum/spring-projects/web/74209-responsebody-and-utf-8)
+ResponseBody and UTF-8
##  Ваши вопросы
-
-> Зачем у нас и UI-контроллеры, и REST-контроллеры? То есть в общем случае 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 API запросы GET, POST, PUT, DELETE, PATCH и возвращают только данные (обычно JSON)
-
-...и в способе авторизации:
-
-- для REST API у нас будет базовая авторизация
-- для 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 мы просто подключаем в помнике, и Spring будет с ним работать без любых других настроек?
+> Jackson мы просто подключаем в помнике, и Спринг будет с ним работать без любых других настроек?
Да, Spring смотрит в classpath и если видит там Jackson, то подключает интеграцию с ним.
-> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ?
-
-Так делают только при
-отношении
-агрегация, например, если давать админу право смотреть еду любого юзера, URL мог бы быть похож
-на `http://localhost/topjava/rest/users/{userId}/meals/{mealId}` (не рекомендуется, см. ссылку ниже). В случае критериев
-поиска или страничных данных они передаются как параметр. Смотри также:
+> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ?
+Так делают, только при отношении агрегация, например, если давать админу право смотреть еду любого юзера, 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.getMeals` для `meals.jsp`
- - 1.2 Сделать `ResourceControllerTest` для `style.css` (проверить `status` и `ContentType`)
+ - 1.1 `RootControllerTest.testMeals` для `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), пока без проверки на `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. Если у вас там
-пробелы, пройдите его основы**
----------------------
+ - 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. Если у вас там пробелы, пройдите его основы**
+---------------------
##  Типичные ошибки и подсказки по реализации
-
- 1: Ошибка в тесте _Invalid read array from JSON_ обычно расшифровывается немного ниже: читайте внимательно.
-- 2: Jackson и неизменяемые объекты (для
- сериализации `MealTo`)
-- 3: Если у meal, приходящий в контроллер, поля `null`, проверьте `@RequestBody` перед параметром (данные приходят в
- формате JSON)
-- 4: При проблемах с собственным форматтером убедитесь, что в конфигурации `Jackson и неизменяемые объекты
+- 3: Jackson JSON Tutorial
+- 4: Если у meal, приходящий в контроллер, поля `null`, проверьте `@RequestBody` перед параметром (данные приходят в формате JSON)
+- 5: При проблемах с собственным форматтером убедитесь, что в конфигурации `[JVM Ecosystem Report 2021](https://snyk.io/jvm-ecosystem-report-2021/)
->показывает, что Spring является абсолютным
->лидером среди фреймворков.
-
-На первом месте упоминается **Spring Boot**, а на втором - **Spring MVC**.
-
-Здесь хотелось бы внести некоторую ясность.
-"Сердцем" Spring является Spring Core, который реализует Dependency
-Injection.
-Этот компонент, как правило, используется во всех остальных
-проектах Spring Framework.
-
-Spring Boot - это надстройка над Spring Framework,
-которая позволяет быстро создавать приложения разных типов,
-использовать автоматическую конфигурацию для некоторых
-компонентов и использовать некоторые удобные встроенные
-инструменты, например, готовые инструменты для мониторинга
-работы приложения (Spring Actuator).
-
-Если мы создаем web-приложение, то мы можем создавать его с
-помощью Spring Boot, но Spring MVC, который является именно
-web-фреймворком, также будет присутствовать в таком приложении
-и играть ключевую роль.
-
-Spring Boot позволяет создавать очень простые приложения без
-единой строчки конфигурации. Но как только мы пытаемся
-создать что-то более сложное, нам все равно приходится
-создавать конфигурационные файлы и классы и настраивать все вручную
-точно так же, как в традиционном Spring-приложении.
-По своему опыту могу сказать, что на реальных проектах
-такая ситуация возникает в большинстве случаев.
-Например, как только нужно настроить подключение
-к нескольким базам данных, а не к одной, мы вынуждены
-создавать конфигурационные классы вручную.
-
-В этом случае Spring Boot в какой-то степени может даже
-навредить вашей продуктивности, потому что вам придется
-разбираться, какие автоконфигурационные классы отключить, и т. д.
-
-Мы изучаем Spring довольно глубоко.
-Традиционно хорошо показал себя подход, при котором
-вначале изучается чистый Spring Framework, после чего
-мы переходим к использованию Spring Boot. Такой подход
-сохраняется и в этой версии курса. Без этого Spring Boot
-будет для вас "черным ящиком", который вам будет очень
-сложно понять, настраивать и отлаживать.
-
-### ORM frameworks
-Отчеты, которые мы использовали, обычно не включают информацию
-о самых популярных ORM-фреймворках.
-
->Известно,
->что **Hibernate** является достаточно популярным фреймворком
->и наиболее часто упоминается в вакансиях, если сравнивать
->его с другими ORM-фреймворками и инструментами для работы Java-приложения
->с базами данных.
-
-Вот одна из статей, которая также говорит о
-популярности Hibernate: [Top 5 Java ORM tools - 2022](https://www.knowledgefactory.net/2021/09/top-java-orm-tools-20XX.html)
-
-**ORM** или **Object-relational-mapping** можно перевести как
-"объектно-реляционное отображение (преобразование)".
-Это техника, которая позволяет создать виртуальную базу
-данных с помощью объектно ориентированного языка
-программирования и взаимодействовать с ней, в то время
-как взаимодействие уже с реальной базой данных выполняется
-фреймворком незаметно для нас.
-Это облегчает работу, позволяет работать с привычными
-и удобными для использования Java-объектами, вызывая
-их методы, вместо того, чтобы писать SQL-запросы к базе данных и код для интерпретации полученных данных.
-
->Hibernate является реализацией спецификации JPA,
->так же, как и менее популярный EclipseLink.
-
-Говоря простым языком, JPA (Java Persistence API) - это
-набор интерфейсов без реализации для работы с ORM,
-который включен в стандарт Java EE. Отдельные провайдеры
-могут предоставлять свои реализации этого интерфейса.
-
-Недостатками JPA и его реализации Hibernate являются удар по производительности
-и недостаточная гибкость. Вопрос производительности частично
-может быть решен с помощью различных техник оптимизации кода,
-написанного с использованием JPA, однако для некоторых
-приложений тот оверхед, который несет в себе JPA, является
-недопустимым, и вы можете увидеть на некоторых проектах
-использование других подходов, от использования
-чистого JDBC до использования MyBatis, JOOQ или каких-то
-альтернативных решений.
-
-В реальных приложениях те запросы к базе данных,
-которые создают наибольшую нагрузку на систему,
-могут быть оптимизированы для улучшения производительности: для
-них могут быть применены JDBC, альтернативные
-JPA-фреймворки, также запросы могут быть написаны
-с помощью нативных SQL-запросов в JPA.
-Для остальных запросов, которые не приводят к
-большой нагрузке на систему, в том же приложении может
-быть применен Hibernate.
-Таким образом, в одном приложении могут быть применены одновременно разные технологии.
-
-
-На курсе вы изучите, как работать с БД с помощью нескольких
-технологий: JDBC (а точнее, Spring JDBC template),
-а также JPA и Spring DataJPA.
-Вы сами не только увидите, но и почувствуете
-при написании кода в ваших домашних заданиях плюсы и минусы каждого подхода.
-
-### Обзор пройденных тем
-Давайте подведем промежуточные итоги по результатам этого урока.
-
-В этом уроке мы:
-
-* познакомились с тем, что такое фреймворки
-и для чего они нужны;
-* посмотрели статистику, которая определяет
-Spring Framework как самый популярный фреймворк на рынке;
-* познакомились с ORM - подходом для упрощения работы
-с базами данных с использованием объектно-ориентированного подхода;
-* узнали про JPA (Java Persistence API) - протокол для работы с
-ORM - и про реализации JPA, среди которых Hibernate является самой популярной;
-* обсудили недостатки подхода ORM и познакомились с инструментами,
-которые могут быть использованы в качестве альтернативы.
-
-В следующем коротком уроке мы завершим обзор используемых технологий.
-Увидимся в следующем видео.
diff --git a/doc/video2.3.md b/doc/video2.3.md
deleted file mode 100644
index af9506cb4596..000000000000
--- a/doc/video2.3.md
+++ /dev/null
@@ -1,115 +0,0 @@
-## Обзор наиболее востребованных технологий. Тренды
-
-##  [Видео](https://drive.google.com/file/d/1XcTRkArj2guek9OiPuFEq_U1V4Dg0N-j)
-
-### Тренд на отказ от reflection
-
-Говоря о трендах, хотелось бы сказать, что в настоящее
-время развиваются фреймворки, построенные на отказе
-от использования Reflection API.
-Dependency injection в Spring, сериализация с помощью
-Jackson построены на использовании Reflection, что
-сильно бьет по производительности, но было достаточно
-удобным решение до недавнего времени.
-Фреймворк Micronaut полностью построен на отказе от
-Reflection, и это дает существенный прирост
-производительности при измерении ряда параметров.
-Micronaut использует продвинутый компилятор и
-генерацию байткода, что позволяет создать все
-бины (управляемые фреймворком объекты) в ходе компиляции.
-
-Пока доля Micronaut очень мала, но он используется
-в продакшене. Я предполагаю, что в какой-то момент
-Spring может также внедрить подобный подход.
-
-### Тренд на развитие реактивного программирования
-
-Продолжает развиваться реактивное программирование,
-которое является в какой-то степени новой парадигмой
-в программировании.
-
-Spring развивает свой реактивный фреймворк Spring WebFlux,
-который поддерживает библиотеку Reactor.
-
-
-Ряд современных задач не решается традиционными
-методами, такими как блокирующий Input/Output,
-HTTP-протокол. Одним из решений данных вопросов
-является применение подходов реактивного
-программирования (Reactor, RxJava), использование
-новых протоколов передачи данных (RSocket).
-У нас также готовится курс [ReactJava](https://javaops.ru/#inprogress) как
-продолжение TopJava на реактивном стеке.
-
-### Тренд на микросервисы, тезисы
-
-ПО становиться большим и сложным. Очень большое приложение
-сложно поддерживать одной командой, управлять его жизненным
-циклом - разработкой, релизами.
-
-Если в большое монолитное приложение добавляется новая
-функциональность приходиться выполнять повторного тестирование
-всей большой системы, в результате срок выхода новых фич увеличивается.
-
-Микросервисная архитектура позволяет решить эту проблему - она
-предполагает разделение большого приложения на отдельные
-приложения-модули, которые взаимодействуют друг с другом.
-Над одним модулем-сервисом может работать один человек или
-небольшая команда. У такого модуля-сервиса будет отдельный
-Git-репозиторий, он может быть задеплоен (развернут) независимо
-от остальной системы. При таком подходе, команды могут вести работу
-над различными сервисами параллельно, параллельно деплоить
-их на серверах. При внесении изменений в микросервис “А”,
-вероятность вызывать проблемы в микросервисе “B” снижается.
-У разных микросервисов могут быть собственные базы данных.
-Это также повышает надежность и гибкость. Например, если по каким то причинам после обновления банковского микросервиса, ответственного за выдачу кредитов возникли сбои и база данных оказалась недоступна, другие компоненты системы, имеющие отдельные базы данных, продолжат свою работу без сбоев.
-
-Это существенно упрощает доработку, тестирование и деплой таких систем.
-
-Использование микросервисов дает большую гибкость в выборе
-технологий. Нам не нужно ограничивать себя в использовании
-технологий для того, чтобы все стандартизировать внутри
-одной большой системы. Работая с отдельными микросервисами,
-мы можем выбрать идеально подходящие технологии для отдельных задач.
-Например, один микросервис может использовать реляционные
-базы данных, а второй эффективнее решает свою задачу
-используя NoSQL базу данных или in memory базу данных.
-
-Еще одно важное преимущество, которое дает микросервисная
-архитектура это возможность легко масштабировать систему горизонтально.
-
-Предположим, что банк испытывает быстрый рост обращений
-об открытии новых счетов.
-
-Если банк использует монолитную архитектуру,
-то есть одно большое приложение, ему может потребоваться
-запускать отдельный экземпляр всего своего приложения
-и направлять часть обращений от клиентов на этот экземпляр (instance).
-
-Если же банк использует микросервисную архитектуру,
-он может запустить несколько экземпляров только сервиса,
-ответственного за открытие новых счетов и распределять
-нагрузку между этими экземплярами.
-При этом микросервис, ответственный за выдачу кредитов,
-который не испытывает повышенной нагрузки, может остаться
-в одном экземпляре. При этом увеличение числа микросервисов,
-ответственных за открытие счетов может происходить автоматически
-с помощью инструментов DevOps и Kubernetes.
-
-Однако использование микросервисов также создает сложности.
-Разработка систем, построенных на микросервисной архитектуре
-на порядок сложнее. Обслуживание таких систем также
-существенно сложнее.
-
-Компания должна обслуживать множество серверов,
-обеспечивать мониторинг каждого микросервиса и так далее.
-Также security таких систем существенно сложнее,
-поскольку мы имеем дело с множеством приложений,
-коммуницирующих друг с другом и все эти коммуникации
-должны быть безопасными.
-
-
-Следующий, готовый стать самым популярным
-после TopJava курс - [Микросервисы](https://javaops.ru/view/cloudjava).
-
-[Первое занятие открытое](https://javaops.ru/view/cloudjava/lesson01)
diff --git a/doc/video2.4.md b/doc/video2.4.md
deleted file mode 100644
index 14041e795564..000000000000
--- a/doc/video2.4.md
+++ /dev/null
@@ -1,24 +0,0 @@
-## 2.4 Обзор наиболее востребованных технологий. Обзор разрабатываемого приложения
-
-##  [Видео](https://drive.google.com/file/d/1LHI18LZK1MRIEBpVe3WjCE890EydN5Gz)
-
-### О работе с фронтендом и JavaScript
-Курс включает себя минимальную практику работы с JavaScript.
-Почему это нужно?
-В вакансиях backend разработчиков, как правило, отсутствуют
-требования глубокого знания JavaScript, но каждый разработчик
-должен уметь пользовать DevTools - инструментами разработчика
-в браузере, чтобы при работе над рабочими задачами по меньшей
-мере иметь возможность понять, происходит ли ошибка на стороне
-фронтенда или на стороне бэкенда, увидеть, какие данные уходят
-на сервер с фронтенда и так далее.
-
-Также, конечно, минимальные знания JavaScript приятны, поскольку
-они дают вам возможность написать свой несложный проект хотя
-бы с минимальным фронтендом.
-
-Существуют также fullstack-разработчики, от которых требуется
-способность полноценно решать задачи фронтенда на продакшене.
-Это требует довольно глубокого знания фронтенд-технологий и
-таких фреймворков, как Angular или React. Это требует много
-времени, и такая подготовка не входит в курс TopJava.
\ No newline at end of file
diff --git a/doc/video3.md b/doc/video3.md
deleted file mode 100644
index 1821aac3f3af..000000000000
--- a/doc/video3.md
+++ /dev/null
@@ -1,168 +0,0 @@
-## Рекомендуемые подходы обучения на курсе
-
-##  [Видео](https://drive.google.com/file/d/1v5sVL8ivNvSXEPVlrYibFLD5byywRkmT)
-
-В предыдущих уроках мы сделали обзор технологий,
-которые будут использоваться в нашем курсе и создаваемом проекте.
-Теперь давайте поговорим о том, как начать
-изучать эти технологии и практиковаться в их использовании.
-
----
-В 1980 году National Training Laboratories в
-США провели исследования эффективности разных
-способов обучения.
-Выяснилось, что у лекций и чтения книг крайне
-низкая эффективность — всего 5-10%.
-Дальше идет просмотр видео лекций и прослушивание аудио.
-
-Максимальная эффективность в 90% — это обучение
-людьми других людей — менторинг и немедленное
-применение полученных знаний на практике.
-
-
-
-
-
-Я хочу подчеркнуть этот момент.
-
->Когда вы смотрите видео, вам может казаться, что вы все понимаете, но, поверьте, когда вы попытаетесь повторить это самостоятельно, у вас возникнет множество вопросов и сложностей. Практика - это важнейшая часть обучения, не пропускайте ее.
-
-На нашем курсе мы:
-- обсуждаем занятия с коллегами и преподавателями в Slack - эффективность 50%
-- выполняем практические домашние задания по каждой пройденной теме - 75%
-- помогаем коллегам и разрабатываем собственный выпускной проект - 90%
-
-### О проверке домашних заданий
-Также очень важная часть обучения - проверка
-ваших домашних заданий и ревью выпускного
-проекта нашими кураторами.
-
->**Это самый эффективный способ научиться программировать!**
-
-При устройстве на работу, на собеседовании обязательно
-задавай вопрос про ревью кода.
-Если его нет, фирма занимается разработкой
-непрофессионально? и рост там будет достаточно
-медленным и ограниченным.
-
-В ревью укажут именно твои ошибки
-в стиле, структурах данных, алгоритмах и кодировании.
-До вечера вторника участники шлют ссылку
-на свой GitHub-репозиторий с домашним
-заданием занятия, проверка делается
-ассистентами, результат пишется в Slack.
-Получается эффективно и оперативно.
-
-После проверки можно исправить замечания
-и пройти ее еще раз.
-
-В конце стажировки делается ревью вашего выпускного проекта.
-
-
-### Участие на стажировке: ожидания и реальность
-
-Давайте кратко обсудим, чем курс TopJava является
-и чем он не является, что следует от него ожидать
-и чего не следует.
-
-#### 1-й тип ложного представления о стажировке:
-
->Я увижу, как с нуля строится web-приложение A с использованием технологий B, просмотрю видео по темам, этого будет достаточно
-
-
-
-Почему такой подход не верный:
-TopJava - это стажировка, поэтому НЕ рассчитывайте
-пройти ее на диване с пакетом поп-корна.
-Тебе придется на ней РАБОТАТЬ (выполнять ДЗ,
-самостоятельно решать какие-то задачи, читать логи,
-дебажить, ходить на StackOverflow и даже думать
-об этом, засыпая)
-
-#### 2-й тип ложного представления о стажировке:
->Меня научат шаблонам работы с технологией А, и я
-> смогу их применять в любой ситуации
-
-Почему это тоже не вполне верный подход:
-Используемые на стажировке технологии представляют
-собой инструменты, которые позволяет сделать
-что-то проще. Мы поделимся практикой их использования,
-неочевидными особенностями и т. п., покажем
-"грабли", на которые вы рано или поздно наступите.
-Нет гарантии, что, устроившись на работу, вы
-увидите точно такие же подходы.
-Все проекты и команды индивидуальны: используются
-различные инструменты и различные решения.
-Столкновение с технологиями, с которыми
-ты ранее не был знаком, - это нормальная
-часть жизни любого программиста. Нужно быть к этому готовым.
-
-Хорошее представление о решении проблем
-дает поиск на StackOverflow, где почти
-на любую проблему дается большое количество
-вариантов решения. Поиск решений и выбор
-лучшего - это основная работа Java-разработчика,
-и мы максимально постараемся этому научить:
-каждый раз в конкретной ситуации вы должны
-будете САМИ думать, что применять и как.
-Выполняя домашние задания, вы должны приложить
-все усилия, чтобы самостоятельно найти решение.
-Далее в начале следующего занятия вы также
-посмотрите разбор решения, подготовленного
-Григорием Кислиным.
-
-#### 3-й вариант ошибочного представления о стажировке TopJava связан с неверным представлением о том зачем нужна проверка домашних заданий. Например, человек может ошибочно рассуждать так:
-
->я хочу проходить стажировку с проверкой ДЗ,
-> чтобы мне рассказали, как нужно правильно выполнять задания
-
-Задача проверки не в том, чтобы общими усилиями
-написать код, который ты и так увидишь в разборе.
-Если у тебя что-то не получается, наша
-задача - не найти ошибку/подебажить за тебя/почитать
-логи и т. п., а подсказать способ самостоятельно
-найти решение (хотя для этого мы сначала сами
-ищем/дебажим/читаем)
-Вторая важная задача, которую решает
-проверяющий, - увидеть то, что не увидел ты.
-Когда ты сдаешь задание на ревью, тебе
-может казаться, что все почти идеально. Проверяющий
-подскажет тебе, где ты что-то пропустил из-за
-недостатка опыта, что позволит тебе улучшить код.
-
----
-
->Любое знание стоит воспринимать как подобие семантического дерева: убедитесь в том, что понимаете фундаментальные принципы, то есть ствол и крупные ветки, прежде чем лезть в мелкие листья-детали. Иначе последним не на чем будет держаться
-— Илон Маск
-
-Обычно в занятии дается много дополнительного
-материала и ссылок. Не стоит стремиться прочитать
-все ссылки урока, их можно использовать как
-справочник. Гораздо важнее пройти основной
-материал урока и сделать домашнее
-задание - этого достаточно для усвоения
-материала и получения той самой
-основы - ствола и крупных веток, на
-которых впоследствии можно наращивать листву.
-
-
-Как правило, подбираются участники разного
-уровня. Поэтому главное – не стеснятся
-задавать вопросы (после самостоятельного
-гугления и поиска решения). Всегда есть
-поддержка группы (в том числе от пришедших
-на бесплатный повтор участников), моя и ассистентов.
-
----
-### Основные навыки программиста, которые необходимо развить на курсе
-Давайте перечислим набор навыков, которые
-вам необходимо развивать в ходе курса
-и которые необходимы любому программисту:
-
-- умение и привычка искать
-информацию, чтобы иметь больший выбор из
-доступных вариантов технологий и подходов
-к решению задачи, умение пользоваться StackOverflow;
-- умение пользоваться дебаггером в Intellij Idea;
-- умение пользоваться DevTools в браузере;
-- определенный кругозор и опыт для того, чтобы придумывать поисковые запросы.
diff --git a/doc/video4.md b/doc/video4.md
deleted file mode 100644
index 7d9b00716dca..000000000000
--- a/doc/video4.md
+++ /dev/null
@@ -1,110 +0,0 @@
-## Структура приложения (многоуровневая архитектура)
-##  [Видео](https://drive.google.com/file/d/1UHzSy9i-uonmTMFoR5v69Y-vyWLCLQWd)
-
-Приложение, которое мы будем разрабатывать, это [программа для подсчета калорий](http://javaops-demo.ru/topjava).
-
-В этом видео обсудим структуру этого приложения.
-
----
-Ссылки на отчеты, которые будут использоваться в этом уроке:
-
-- [Многоуровневая архитектура (русскоязыная статья в Wikipedia)](https://ru.wikipedia.org/wiki/%D0%9C%D0%BD%D0%BE%D0%B3%D0%BE%D1%83%D1%80%D0%BE%D0%B2%D0%BD%D0%B5%D0%B2%D0%B0%D1%8F_%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0)
-- [Multitier architecture (англоязычная статья в Wikipedia)](https://en.wikipedia.org/wiki/Multitier_architecture)
-
----
-
-
-
-На структурной схеме приложения вы видите, что оно условно разделено на 4 части: **Views**,
-**Controller**, **Service** и **Repository**.
-
-Такой подход является реализацией многоуровневой архитектуры в программировании.
-По-английски этот подход называется **_Multitier architecture_**.
-
-Его суть заключается в разделении приложения на несколько слоев,
-каждый из которых ответственен за конкретную задачу.
-
-### Слой отображения (View)
-Views соответствует слою отображения (или presentation layer). Это user interface (UI) или
-фронтенд - все то, что мы видим и с чем взаимодействуем в браузере.
-В качестве View могут быть HTML-страницы, созданные с использованием специальных
-движков шаблонов, например, JSP (встроен в Tomcat), или
-Thymeleaf (шаблоны по умолчанию в Spring Boot), или отдельное frontend-приложение,
-написанное на одном из JavaScript-фреймворков.
-
-В случае с движками шаблонов HTML-страницы будут располагаться в одном проекте
-с основным кодом приложения. Для приложений с "небогатым" UI используются именно шаблоны.
-Этот подход отличается от так называемых RIA - rich internet application - приложений со сложным UI.
-
-Главное отличие rich internet application от приложений с фронтендом
-на движках шаблонов заключается в том, что фронтенд, созданный на движке
-шаблонов, работает на сервере, и это что-то простое.
-В случае с rich internet application фронтенд-приложение загружается
-через Интернет к вам на компьютер и запускается в браузере.
-Оно может быть максимально сложным и выполнять функции традиционных
-десктоп-приложений.
-Иногда в одном приложении смешиваются оба способа: например, страница
-логина-пароля в RIA делаются на шаблонах.
-
-Создание простого фронтенда на движке шаблонов проще, поэтому
-в курсе мы будем использовать этот способ.
-
-### Слой контроллеров (Controller)
-Следующий слой, который мы видим - это **Controller**.
-
-В многоуровневой архитектуре он соответствует слою, который
-называется "**_Слой приложения_**" или "**_Application layer_**").
-В GRASP (General Responsibility Assignment Software Patterns)
-он так и называется - Controller layer.
-
-Это слой приложения, который ответственен за обработку HTTP-запросов и проверку корректности входных данных. Если мы открываем главную страницу на сайте или отправляем заполненную на сайте форму, фронтенд-приложение отправляет HTTP-запрос серверу (в нашем случае контейнеру сервлетов), который принимает запрос и перенаправляет его в контроллер, соответствующий введенному в браузере URL или адресу, который вызывается при отправке формы через сайт.
-Также контроллеры могут принимать запросы не только от фронтенда, но и от других приложений.
-
-Слой контроллеров не имеет доступа к базе данных. Контроллеры общаются только с сервисами.
-
-### Слой сервисов (Service layer)
-Слой **Service** на схеме приложения соответствует слою
-бизнес-логики (или **_Business layer_**) в многоуровневой архитектуре.
-В слое Service инкапсулирована вся бизнес-логика нашего приложения.
-Если коммуникация с фронтендом или другими приложениями - это ответственность контроллеров,
-то обработка данных - это ответственность сервисов.
-
-### Слой доступа к данным (Data layer)
-Слой сервисов общается со слоем, ответственным за работу с базами данных.
-Этот слой называют **Data layer** (также можно встретить
-названия **Persistence Layer** или **Data access layer**), и он
-представлен в виде **_Data Access Object_** классов
-(коротко - **_DAO-классы_**) или классов, реализующих паттерн "репозиторий".
-С обоими видами классов вы попрактикуетесь в ходе курса.
-[Репозиторий - это также один из архитектурных паттернов](https://martinfowler.com/eaaCatalog/repository.html)
-
-Подобное разделение приложения на слои дает гибкость
-и существенно упрощает доработку и переиспользование приложения.
-Например, создав по такому принципу приложение,
-содержащее слои репозиториев, сервисов и контроллеров,
-мы в дальнейшем можем легко использовать это приложение
-с различными фронтенд-приложениями или мобильными приложениями.
-Если мы решим перейти на другую базу данных, мы можем
-переписать только слой репозиториев, и нам не требуется
-вносить изменения в слои сервисов и контроллеров.
-
->Для маленького приложение такой подход может показаться
->избыточно сложным, но по мере расширения это является спасением.
-
-Подавляющее большинство реальных приложений построено с использованием именно этой архитектуры.
-
-### Краткие итоги
-В этом видео мы познакомились с концепцией многоуровневой архитектуры,
-которую мы применим при создании приложения на курсе.
-Многоуровневая архитектура предполагает разделение приложения на слои:
-- Views - слой отображения является фронтендом;
-- Controller - слой приложения ответственен за прием и валидацию входных данных;
-- слой Service включает в себя весь код, отражающий бизнес логику;
-- Repository или Data layer отвечает за взаимодействие с базой данных.
-
-Также мы обсудили какие преимущества дает такой подход.
-Среди преимуществ в первую очередь возможность повторного
-использования различных слоев и упрощение их доработки и изменения.
-
-
-
diff --git a/doc/video5-vcs-git.md b/doc/video5-vcs-git.md
deleted file mode 100644
index 7a172e517bc8..000000000000
--- a/doc/video5-vcs-git.md
+++ /dev/null
@@ -1,180 +0,0 @@
-## Системы управления версиями, Git
-##  [Видео](https://drive.google.com/file/d/1uFjIsxsaSAXxFSwSpjJIGK7Ug2VXf6yH)
-
-video5-vcs-git.md
-
-В этом уроке мы рассмотрим системы управления версиями
-и самую популярную из них - Git.
-
----
-* [StackOverflow 2021 survey](https://insights.stackoverflow.com/survey/2021#technology-most-popular-technologies)
-* [Введение в Git и GitHub: установка и настройка](https://topjava.ru/blog/vvedeniye-v-git-github-ustanovka-i-nastroyka)
-* [Введение в Git и GitHub: базовые команды](https://topjava.ru/blog/vvedeniye-v-git-github-bazovyye-komandy)
-* [Введение в Git и GitHub: ошибки использования](https://topjava.ru/blog/vvedeniye-v-git-oshibki-ispolzovaniya-ch-9)
-* [Бесплатная русскоязычная книга Pro Git](https://git-scm.com/book/ru/v2/)
----
-
-Git является де-факто стандартом среди систем управления версиями.
-Опрос, проведенный StackOverflow в 2021 году показывает,
-что Git используют почти 95% опрошенных разработчиков.
-
-Когда-то популярная централизованная система контроля
-версий SVN (Subversion) практически полностью заменена Git.
-Но некоторые большие проекты все еще используют SVN.
-Примером такого проекта является WordPress.
-Я также все еще иногда встречаю SVN в вакансиях российских компаний.
-
-### Что такое Git и GitHub
-В ходе курса мы будем использовать Git.
-Git - это распределенная система управления версиями.
-Это означает, что код, над которым работает команда,
-и ранее сохраненные (закоммиченные) версии проекта
-хранится на компьютерах каждого члена команды,
-а также в удаленном репозитории, который можно
-сравнить с облачным хранилищем.
-
-Некоторые путают Git и GitHub, но это не одно и то же.
-
-**Git** — это утилита, которую
-программист устанавливает у себя на компьютере для
-сохранения состояний проектов и контроля версий проекта.
-
-**GitHub** — это провайдер удаленных репозиториев,
-сайт (хостинг) для хранения кода проекта и его изменений,
-для обмена файлами с членами команды проекта.
-Программисты могут создавать на GitHub публичные репозитории,
-в которых код доступен всем. Компании могут приобрести платный
-аккаунт на GitHub и вести свои проекты в закрытых репозиториях,
-доступ к которым имеют только члены команды проекта.
-Существуют и другие подобные сайты - провайдеры удаленных
-репозиториев, например, BitBucket, SourceForge, GitLab и т. д.
-
-
-### Как работают Git и GitHub
-
-
-Говоря кратко, работа с Git и удаленным репозиторием
-(в нашем случае это Github) выглядит следующим образом.
-
-Первый разработчик, который начинал работу над проектом,
-использовал команду **_git init_** в корневой директории
-проекта для того, чтобы инициализировать пустой Git-репозиторий.
-В этот момент Git создает в директории с проектом скрытую
-директорию, содержащую файлы, необходимые для его работы.
-Теперь первый разработчик может с помощью команды **_git add_**
-добавлять файлы проекта в индекс - то есть в зону,
-отслеживаемую git, а также может фиксировать изменения,
-создавая коммиты (**_git commit_**), и загружать их в
-удаленный репозиторий (например, на GitHub) с помощью
-команды **_git push_**.
-
-Спустя некоторое время к работе над проектом подключается второй разработчик.
-
-Поскольку первый разработчик пушил изменения файлов проекта
-на удаленный репозиторий, второй разработчик может скачать
-их себе на компьютер с помощью команды **_git pull_**
-и продолжить работу с учетом этих изменений.
-
-Давайте повторим.
-В описанной работе использовались команды:
-
-* **_git init_** - эта команда инициализирует работу Git
-для конкретного проекта. В папке с проектом создается
-скрытая папка, хранящая все файлы, необходимые Git
-для работы. Папка с этими файлами впоследствии будет
-загружена в удаленный репозиторий, и второй программист,
-скачавший проект, увидит у себя на компьютере все ранее
-созданные с помощью Git версии.
-* **_git add_** - это команда, которая добавляет файлы
-проекта в индекс (или отслеживаемую зону), чтобы их можно
-было в какой-то момент закоммитить (то есть сохранить, зафиксировать).
-* **_git commit_** - сохраняет все файлы проекта в
-текущем состоянии так, что мы в любой момент сможем
-вернуться к этому состоянию - посмотреть, как изменились
-файлы, или даже полностью вернуть проект к состоянию
-данного коммита.
-* **_git push_** - загружает файлы в удаленный Git-репозиторий,
-чтобы их могли скачать другие программисты.
-* **_git pull_** используют для того, чтобы скачать
-файлы из удаленного репозитория себе на компьютер.
-
-Также Git позволяет создавать ветки. Это означает,
-что вы можете создать копию стабильного кода и
-продолжить работу в этой копии, не подвергая риску
-стабильную версию. Когда код с новой функциональностью
-готов, отлажен и протестирован, он может быть
-слит ("смержен", от слова "merge") в основную ветку со стабильным кодом.
-
-С помощью этих функций Git позволяет команде разработчиков
-работать одновременно над разными задачами и поддерживать
-код в стабильном состоянии.
-
-Также Git позволяет в любой момент понять, кто и когда внес
-изменения в определенный код, что существенно упрощает взаимодействия
-с коллегами. Например, в случае, если вы видите новые
-строки кода, которые вызывают у вас вопросы, с помощью
-Git вы можете разобраться, кто внес изменения именно
-в эти строки, и связаться с этим человеком
-для уточнения деталей.
-
-Если произошла ситуация, при которой два разработчика
-изменили один и тот же файл, Git потребует от вас вручную
-отрегулировать такой конфликт.
-
-Также благодаря Git у вас всегда под рукой детальная
-история изменений. С помощью комментариев к коммитам
-вы можете детально документировать ход работы над
-проектом и в случае необходимости разобраться, в
-какой момент что-то пошло не так, и откатиться
-к той версии, где ошибка еще не была сделана.
-Или просто выяснить, как выглядел код определенного
-класса или метода в определенной версии и внести правки
-в текущую версию, если это необходимо.
-
-### Дополнительные материалы
-
-Для изучения Git рекомендуем в первую очередь ознакомиться со статьями на сайте [topjava.ru](https://topjava.ru):
-
-* [Введение в Git и GitHub: установка и настройка](https://topjava.ru/blog/vvedeniye-v-git-github-ustanovka-i-nastroyka)
-* [Введение в Git и GitHub: базовые команды](https://topjava.ru/blog/vvedeniye-v-git-github-bazovyye-komandy)
-* [Введение в Git и GitHub: ошибки использования](https://topjava.ru/blog/vvedeniye-v-git-oshibki-ispolzovaniya-ch-9)
-
-В них дается детальная инструкция по первоначальной настройке
-Git, GitHub, в том числе инструкция по настройке
-Access token в GitHub, что вам обязательно нужно будет сделать.
-
-Также можно упомянуть официальную русскоязычную книгу Pro Git,
-которая доступна [бесплатно в электронном виде](https://git-scm.com/book/ru/v2/).
-
-### Как мы будем использовать Git на курсе
-В Git очень много команд, но в ходе курса мы будем использовать
-только самые необходимые из них.
-С git можно работать как через терминал с помощью команд,
-так и с помощью различных программ с графическим интерфейсом.
-Мы будем преимущественно использовать интеграцию
-IntelliJ Idea с Git, поскольку она очень удобна
-и существенно упрощает работу.
-Однако знать, как выполнять аналогичные операции
-в терминале, очень полезно.
-
-### Резюме
-В этом уроке мы сделали краткий обзор систем управления
-версиями и самой популярной из них - Git:
-
-* разобрались в различиях Git и GitHub.
-Git - это система контроля версий, а
-GitHub - провайдер удаленных репозиториев, один из многих, доступных на рынке.
-* кратко познакомились с тем, как Git работает.
-Узнали о командах git init, git add, git commit, git push и git pull.
-* Также мы обсудили, что такое ветки и какие возможности они дают.
-
----
-
-Если вы не работали раньше с Git, не беспокойтесь.
-В последующих уроках работа с Git будет изучаться
-на практике в ходе работы над проектом. Но дополнительно
-почитать о Git все же стоит.
-
-
-
-
diff --git a/graduation.md b/graduation.md
index a2192f1845c8..442ce82b8c47 100644
--- a/graduation.md
+++ b/graduation.md
@@ -1,6 +1,5 @@
-## Выпускной проект [стажировки TopJava](https://javaops.ru/view/topjava)
-
-Design and implement a REST API using Spring-Boot/Spring Data JPA **without frontend**.
+## Выпускной проект
+Design and implement a REST API using Hibernate/Spring/SpringMVC (or Spring-Boot) **without frontend**.
The task is:
@@ -9,7 +8,7 @@ Build a voting system for deciding where to have lunch.
* 2 types of users: admin and regular users
* Admin can input a restaurant and it's lunch menu of the day (2-5 items usually, just a dish name and price)
* Menu changes each day (admins do the updates)
- * Users can vote for a restaurant they want to have lunch at today
+ * Users can vote on which restaurant they want to have lunch at
* Only one vote counted per user
* If user votes again the same day:
- If it is before 11:00 we assume that he changed his mind.
@@ -17,156 +16,77 @@ Build a voting system for deciding where to have lunch.
Each restaurant provides a new menu each day.
-As a result, provide a link to github repository. It should contain the code, README.md with API documentation and couple curl commands to test it (**better - link to Swagger**).
+As a result, provide a link to github repository. It should contain the code, README.md with API documentation and couple curl commands to test it.
-----------------------------
-P.S.: Make sure everything works with latest version that is on github :)
+P.S.: Make sure everything works with latest version that is on github :)
+
P.P.S.: Assume that your API will be used by a frontend developer to build frontend on top of that.
-----------------------------
+###  Рекомендации
-##  Рекомендации
-
-**Пишем выпускной проект как тестовое задание на работу**
-
-- **Не изобретай велосипедов!** Грубая ошибка - пытаться сделать стандартные вещи по-своему, чаще всего криво. На проекте все должно быть единообразно! Ваш проект TopJava - сделай все МАКСИМАЛЬНО в этом
- стиле. Если тебе кажется, что есть лучшее решение, чем в TopJava - пишите мне в личку, я всегда открыт для улучшений.
-- **Рекомендую писать проект на востребованном на рынке стеке**: Spring Boot + Spring Data JPA (работа с БД) + Swagger/OpenAPI 3.0 (REST документация).
-- **Оптимально подойдет код миграции TopJava на Spring Boot в конце стажировки или [курса BootJava](https://javaops.ru/view/bootjava)**
+- **Сделай новый проект и добавляй туда из Topjava только то что нужно! Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!**
+- **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)**
+- **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.**
-*Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас, конечно, есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно.
-НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все строго по ТЗ, максимально просто, удобно для доработок и для использования со стороны клиента.*
+*Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас, конечно, есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если, конечно, в ТЗ нет оговорок). Все свои вопросы, предложения и хотелки оформляйте отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например, справочник еды) - обязательно напишите в read.me. Как и выбор стратегии кэширования.*
> Совершенство достигнуто не тогда, когда нечего добавить, а тогда, когда нечего отнять
_Антуан де Сент-Экзюпери_
-### 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}][параметры]`, см. ниже ссылки про REST и иерархию). Имена ресурсов во множественном числе!
-Самая распространенная и грубая ошибка - не придерживаться этих простых правил.
+- 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять**
+- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле.
+- 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации**
+- 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО**
+- 5: в проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы приложения код, ничего лишнего:
+ - 5.1 НЕ надо делать разные профили базы и работы с ней
+ - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай
+ - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования
+ - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. То есть НИЧЕГО ЛИШНЕГО.
+- 6: База Данных
+ - берите без установки (H2 или HSQLDB). Одну!! Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения**
+ - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе
+ - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.**
+ - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные
+ - таблицы обычно именуются в единственном числе. Исключение - users, т. к. user - зарезервированное слово. `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании
+- 7: по возможности сделать JUnit тесты
+- 8: уделяйте внимание обработке ошибок
+- 9: делаем 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)**
- - [Лучшие практики разработки 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` ресторана, а не весь объект, при создании-редактировании ресторана в примерах swagger не должно быть меню и еды.
-- 2.8: `Profile` означает, что данные принадлежат профилю пользователя. Все остальное называйте по-другому.
-- 2.9: Отсутствие данных часто бывает "бизнес кейсом", те НЕ ошибкой в запросе или приложении. Исключение - это ошибка, например неверный `id`. Запрос на данные, которые могут быть, могут нет, не должен приводить к исключениям. Посмотрите в сторону `ResponseEntity.of()`
-- 2.10: По REST URL должно быть однозначно понятно, какие будут параметры на входе и что ждать на выходе. Без сюрпризов!
-
-### 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: В БД обычно хранятся все введенные пользователем и админом данные c возможность их редактирования. Это означает, что мы не удаляем прошлые меню, а храним их в базе, как и историю голосования. Есть базовые вещи, которые закладываются в архитектуру приложения, и есть неочевидные доработки к ТЗ, делать не надо.
-- 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: Архитектура / pom
-- 5.1: Можно:
- - или подключить DATA-REST (см.курс [Spring Boot 2.x + HATEOAS](https://javaops.ru/view/bootjava)). Контроллеры генерируются автоматически по репозиториям, требуется настройка ресурсов в кастомных контроллерах
- - **РЕКОМЕНДУЕТСЯ** делать на основе миграции TopJava / кода [курса BootJava](https://javaops.ru/view/bootjava)
-
-Нельзя смешивать эти подходы вместе! Я рекомендую 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)
-- 5.5 Не надо явно указывать версии зависимостей в `pom`, если они наследуются от `spring-boot-starter-parent`
-
-### 6: Доступ к БД
-
-- 6.1: Используйте Spring Data JPA (без лишней делегации). Методы Repository можно вызывать напрямую из сервиса или из контроллера.
-- 6.2: В DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null-значениями (используйте `orElseThrow`)
-- 6.3: Не делайте при обновлении записи ради экономии пары строчек кода так:
-```
-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: Прячте `id` при `create/update` в примерах Swagger. Если их передали - проверяйте на соответствие (в 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: ОБЯЗАТЕЛЬНО: запустите приложение без всяких предварительных настроек (базы, переменных окружения, ..), лучше на другом компьютере. Приложение должно запускаться и работать!
-
-## Оценка
-ТЗ очень скромное, не дает понимания, что хочет получить заказчик, именно поэтому моих комментариев к этому ТЗ больше на 2 порядка. Это реальное тестовое задание на работу. При оценке учитывается отсутствие грубых ляпов, простота и красота решения + его расширяемость, если понадобится дорабатывать приложение. Если практический опыт небольшой, еще раз очень рекомендую держаться как можно ближе к нашему коду на Spring Boot.
-Тестовое задание дается для того, чтобы оценить вас, как программиста. Нужны ли вы компании или нет. Это умение спроектировать грамотную модель и API, придерживаясь принципов REST и без своих велосипедов, и ваш чистый красивый код.
+ - **10 Best Practices for Better RESTful API**
+ - [REST resource hierarchy](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources)
+- 10: не смешивайте TO и Entity вместе. Лучше всего, если они будут независимыми друг от друга.
+- 11: не размещайте логику приложения и преобразования в TO в слое доступа к DB
+- 12: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера)
+- 13: [Use for money in java app](http://stackoverflow.com/a/43051227/548473)
+- 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 их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverflow в коде обязательна!
+- 21: проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser).
+- 22: обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк
+- 23: Кэширование
+ - необязательно, но желательно
+ - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редко запрашиваемые данные)!
+ - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение).
+- 24: Валидация
+ - желательна
+ - одних аннотаций недостаточно. Должны быть `@Valid/@Validation`
+ - проверяйте входные данные при `create/update` **в контроллерах!** В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()`
+- 25: `readme.md`:
+ - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English)
+ - Требуемые примеры `curl` не прячьте, а пишите здесь!
+ - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:)
+
+## Попробуйте подергать свое API по всем типичным сценариям ТЗ!
+- Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`.
+- Сколько раз пришлось его вызвать API для типичного сценария (нарпимер посмотреть рестораны с едой)?
+- Сколько запросов к базе было сделано? Можно ли сократить (например с FETCH/Graph или через кэширование)?
+- **API ДОЛЖНО соответствовать принципам REST (см. ссылки выше)**
+- **ОБЯЗАТЕЛЬНО: запустите `mvn test`- ошибок быть не должно**
+- **ОБЯЗАТЕЛЬНО: запустите приложение без всяких предварительных настроек (базы, переменных окружения, ..), лучше на другом компьютере. Должно запускаться!**
diff --git a/pom.xml b/pom.xml
index cf231320e25d..aa719b6440c7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,36 +4,161 @@
ru.javawebinartopjava
- jar
+ war1.0-SNAPSHOTCalories Management
- https://javaops-demo.ru/topjava
+ http://topjava.herokuapp.com/1.8UTF-8UTF-8
+
+ 5.3.3
+
+
+ 1.2.3
+ 1.7.30
+
+
+ 42.2.18
+
+ 4.13.2
+ 3.19.0
+
+
+ 5.4.28.Final
+ 7.0.1.Final
+ 3.0.1-b12topjava
- install
+ packageorg.apache.maven.pluginsmaven-compiler-plugin
- 3.14.0
+ 3.8.1${java.version}${java.version}
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+ -Dfile.encoding=UTF-8
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ compile
+
+
+
+ org.slf4j
+ jul-to-slf4j
+ ${slf4j.version}
+ runtime
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ runtime
+
+
+
+
+ org.springframework
+ spring-context
+ ${spring.version}
+
+
+ org.springframework
+ spring-orm
+ ${spring.version}
+
+
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+
+
+ org.hsqldb
+ hsqldb
+ 2.3.4
+
+
+
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+
+
+ org.hibernate.validator
+ hibernate-validator
+ ${hibernate-validator.version}
+
+
+
+
+ org.glassfish
+ javax.el
+ ${javax-el.version}
+ provided
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+
+ javax.servlet
+ jstl
+ 1.2
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ org.springframework
+ spring-test
+ ${spring.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java
index 723742bacade..c2f9cc618f7c 100644
--- a/src/main/java/ru/javawebinar/topjava/Main.java
+++ b/src/main/java/ru/javawebinar/topjava/Main.java
@@ -1,7 +1,7 @@
package ru.javawebinar.topjava;
/**
- * @see Demo application
+ * @see Demo application
* @see Initial project
*/
public class Main {
diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java
new file mode 100644
index 000000000000..d9343a0740a8
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java
@@ -0,0 +1,63 @@
+package ru.javawebinar.topjava.model;
+
+import org.springframework.util.Assert;
+
+import javax.persistence.*;
+
+@MappedSuperclass
+// http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access
+@Access(AccessType.FIELD)
+public abstract class AbstractBaseEntity {
+ public static final int START_SEQ = 100000;
+
+ @Id
+ @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ)
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq")
+ protected Integer id;
+
+ protected AbstractBaseEntity() {
+ }
+
+ protected AbstractBaseEntity(Integer id) {
+ this.id = id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public int id() {
+ Assert.notNull(id, "Entity must have id");
+ return id;
+ }
+
+ public boolean isNew() {
+ return this.id == null;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + ":" + id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AbstractBaseEntity that = (AbstractBaseEntity) o;
+ return id != null && id.equals(that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id == null ? 0 : id;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java
new file mode 100644
index 000000000000..47a758c6c378
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java
@@ -0,0 +1,38 @@
+package ru.javawebinar.topjava.model;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+import javax.persistence.Column;
+import javax.persistence.MappedSuperclass;
+
+
+@MappedSuperclass
+public abstract class AbstractNamedEntity extends AbstractBaseEntity {
+
+ @NotBlank
+ @Size(min = 2, max = 100)
+ @Column(name = "name", nullable = false)
+ protected String name;
+
+ protected AbstractNamedEntity() {
+ }
+
+ protected AbstractNamedEntity(Integer id, String name) {
+ super(id);
+ this.name = name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + '(' + name + ')';
+ }
+}
\ No newline at end of file
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..788ae8f6fe5b
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java
@@ -0,0 +1,82 @@
+package ru.javawebinar.topjava.model;
+
+import javax.persistence.FetchType;
+import javax.persistence.ManyToOne;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+public class Meal extends AbstractBaseEntity {
+ private LocalDateTime dateTime;
+
+ private String description;
+
+ private int calories;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ private User user;
+
+ public Meal() {
+ }
+
+ public Meal(LocalDateTime dateTime, String description, int calories) {
+ this(null, dateTime, description, calories);
+ }
+
+ public Meal(Integer id, LocalDateTime dateTime, String description, int calories) {
+ super(id);
+ 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();
+ }
+
+ public void setDateTime(LocalDateTime dateTime) {
+ this.dateTime = dateTime;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setCalories(int calories) {
+ this.calories = calories;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public String toString() {
+ return "Meal{" +
+ "id=" + id +
+ ", dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ '}';
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/model/Role.java b/src/main/java/ru/javawebinar/topjava/model/Role.java
new file mode 100644
index 000000000000..acb7a276f6dc
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/Role.java
@@ -0,0 +1,6 @@
+package ru.javawebinar.topjava.model;
+
+public enum Role {
+ USER,
+ ADMIN
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java
new file mode 100644
index 000000000000..4699802e41e5
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/User.java
@@ -0,0 +1,140 @@
+package ru.javawebinar.topjava.model;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import org.hibernate.validator.constraints.Range;
+import org.springframework.util.CollectionUtils;
+
+import javax.persistence.*;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.Set;
+
+import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY;
+
+@NamedQueries({
+ @NamedQuery(name = User.DELETE, query = "DELETE FROM User u WHERE u.id=:id"),
+ @NamedQuery(name = User.BY_EMAIL, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email=?1"),
+ @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"),
+})
+@Entity
+@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")})
+public class User extends AbstractNamedEntity {
+
+ public static final String DELETE = "User.delete";
+ public static final String BY_EMAIL = "User.getByEmail";
+ public static final String ALL_SORTED = "User.getAllSorted";
+
+ @Column(name = "email", nullable = false, unique = true)
+ @Email
+ @NotBlank
+ @Size(max = 100)
+ private String email;
+
+ @Column(name = "password", nullable = false)
+ @NotBlank
+ @Size(min = 5, max = 100)
+ private String password;
+
+ @Column(name = "enabled", nullable = false, columnDefinition = "bool default true")
+ private boolean enabled = true;
+
+ @Column(name = "registered", nullable = false, columnDefinition = "timestamp default now()")
+ @NotNull
+ private Date registered = new Date();
+
+ @Enumerated(EnumType.STRING)
+ @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"),
+ uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "role"}, name = "user_roles_unique_idx")})
+ @Column(name = "role")
+ @ElementCollection(fetch = FetchType.EAGER)
+ private Set roles;
+
+ @Column(name = "calories_per_day", nullable = false, columnDefinition = "int default 2000")
+ @Range(min = 10, max = 10000)
+ private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY;
+
+ public User() {
+ }
+
+ public User(User u) {
+ this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRegistered(), u.getRoles());
+ }
+
+ public User(Integer id, String name, String email, String password, Role role, Role... roles) {
+ this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, new Date(), EnumSet.of(role, roles));
+ }
+
+ public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Date registered, Collection roles) {
+ super(id, name);
+ this.email = email;
+ this.password = password;
+ this.caloriesPerDay = caloriesPerDay;
+ this.enabled = enabled;
+ this.registered = registered;
+ setRoles(roles);
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Date getRegistered() {
+ return registered;
+ }
+
+ public void setRegistered(Date registered) {
+ this.registered = registered;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public int getCaloriesPerDay() {
+ return caloriesPerDay;
+ }
+
+ public void setCaloriesPerDay(int caloriesPerDay) {
+ this.caloriesPerDay = caloriesPerDay;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public Set getRoles() {
+ return roles;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setRoles(Collection roles) {
+ this.roles = CollectionUtils.isEmpty(roles) ? EnumSet.noneOf(Role.class) : EnumSet.copyOf(roles);
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "id=" + id +
+ ", email=" + email +
+ ", name=" + name +
+ ", enabled=" + enabled +
+ ", roles=" + roles +
+ ", caloriesPerDay=" + caloriesPerDay +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java
new file mode 100644
index 000000000000..9461d5f9f693
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java
@@ -0,0 +1,23 @@
+package ru.javawebinar.topjava.repository;
+
+import ru.javawebinar.topjava.model.Meal;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface MealRepository {
+ // null if updated meal does not belong to userId
+ Meal save(Meal meal, int userId);
+
+ // false if meal does not belong to userId
+ boolean delete(int id, int userId);
+
+ // null if meal does not belong to userId
+ Meal get(int id, int userId);
+
+ // ORDERED dateTime desc
+ List getAll(int userId);
+
+ // ORDERED dateTime desc
+ List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId);
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java
new file mode 100644
index 000000000000..138369789175
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java
@@ -0,0 +1,21 @@
+package ru.javawebinar.topjava.repository;
+
+import ru.javawebinar.topjava.model.User;
+
+import java.util.List;
+
+public interface UserRepository {
+ // null if not found, when updated
+ User save(User user);
+
+ // false if not found
+ boolean delete(int id);
+
+ // null if not found
+ User get(int id);
+
+ // null if not found
+ User getByEmail(String email);
+
+ List getAll();
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java
new file mode 100644
index 000000000000..fa26d566383f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java
@@ -0,0 +1,86 @@
+package ru.javawebinar.topjava.repository.jdbc;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.support.DataAccessUtils;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import org.springframework.stereotype.Repository;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.repository.MealRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Repository
+public class JdbcMealRepository implements MealRepository {
+
+ private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class);
+
+ private final JdbcTemplate jdbcTemplate;
+
+ private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ private final SimpleJdbcInsert insertMeal;
+
+ @Autowired
+ public JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
+ this.insertMeal = new SimpleJdbcInsert(jdbcTemplate)
+ .withTableName("meals")
+ .usingGeneratedKeyColumns("id");
+
+ this.jdbcTemplate = jdbcTemplate;
+ this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
+ }
+
+ @Override
+ public Meal save(Meal meal, int userId) {
+ MapSqlParameterSource map = new MapSqlParameterSource()
+ .addValue("id", meal.getId())
+ .addValue("description", meal.getDescription())
+ .addValue("calories", meal.getCalories())
+ .addValue("date_time", meal.getDateTime())
+ .addValue("user_id", userId);
+
+ if (meal.isNew()) {
+ Number newId = insertMeal.executeAndReturnKey(map);
+ meal.setId(newId.intValue());
+ } else {
+ if (namedParameterJdbcTemplate.update("" +
+ "UPDATE meals " +
+ " SET description=:description, calories=:calories, date_time=:date_time " +
+ " WHERE id=:id AND user_id=:user_id", map) == 0) {
+ return null;
+ }
+ }
+ return meal;
+ }
+
+ @Override
+ public boolean delete(int id, int userId) {
+ return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0;
+ }
+
+ @Override
+ public Meal get(int id, int userId) {
+ List meals = jdbcTemplate.query(
+ "SELECT * FROM meals WHERE id = ? AND user_id = ?", ROW_MAPPER, id, userId);
+ return DataAccessUtils.singleResult(meals);
+ }
+
+ @Override
+ public List getAll(int userId) {
+ return jdbcTemplate.query(
+ "SELECT * FROM meals WHERE user_id=? ORDER BY date_time DESC", ROW_MAPPER, userId);
+ }
+
+ @Override
+ public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) {
+ return jdbcTemplate.query(
+ "SELECT * FROM meals WHERE user_id=? AND date_time >= ? AND date_time < ? ORDER BY date_time DESC",
+ ROW_MAPPER, userId, startDateTime, endDateTime);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java
new file mode 100644
index 000000000000..412bfbebc5df
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java
@@ -0,0 +1,74 @@
+package ru.javawebinar.topjava.repository.jdbc;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.support.DataAccessUtils;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import org.springframework.stereotype.Repository;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+
+import java.util.List;
+
+@Repository
+public class JdbcUserRepository implements UserRepository {
+
+ private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class);
+
+ private final JdbcTemplate jdbcTemplate;
+
+ private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ private final SimpleJdbcInsert insertUser;
+
+ @Autowired
+ public JdbcUserRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
+ this.insertUser = new SimpleJdbcInsert(jdbcTemplate)
+ .withTableName("users")
+ .usingGeneratedKeyColumns("id");
+
+ this.jdbcTemplate = jdbcTemplate;
+ this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
+ }
+
+ @Override
+ public User save(User user) {
+ BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);
+
+ if (user.isNew()) {
+ Number newKey = insertUser.executeAndReturnKey(parameterSource);
+ user.setId(newKey.intValue());
+ } else if (namedParameterJdbcTemplate.update(
+ "UPDATE users SET name=:name, email=:email, password=:password, " +
+ "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) {
+ return null;
+ }
+ return user;
+ }
+
+ @Override
+ public boolean delete(int id) {
+ return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0;
+ }
+
+ @Override
+ public User get(int id) {
+ List users = jdbcTemplate.query("SELECT * FROM users WHERE id=?", ROW_MAPPER, id);
+ return DataAccessUtils.singleResult(users);
+ }
+
+ @Override
+ public User getByEmail(String email) {
+// return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email);
+ List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email);
+ return DataAccessUtils.singleResult(users);
+ }
+
+ @Override
+ public List getAll() {
+ return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java
new file mode 100644
index 000000000000..9cc19a4dc31b
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java
@@ -0,0 +1,37 @@
+package ru.javawebinar.topjava.repository.jpa;
+
+import org.springframework.stereotype.Repository;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.repository.MealRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Repository
+public class JpaMealRepository implements MealRepository {
+
+ @Override
+ public Meal save(Meal meal, int userId) {
+ return null;
+ }
+
+ @Override
+ public boolean delete(int id, int userId) {
+ return false;
+ }
+
+ @Override
+ public Meal get(int id, int userId) {
+ return null;
+ }
+
+ @Override
+ public List getAll(int userId) {
+ return null;
+ }
+
+ @Override
+ public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java
new file mode 100644
index 000000000000..9ade13354ac1
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java
@@ -0,0 +1,72 @@
+package ru.javawebinar.topjava.repository.jpa;
+
+import org.springframework.dao.support.DataAccessUtils;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.List;
+
+@Repository
+@Transactional(readOnly = true)
+public class JpaUserRepository implements UserRepository {
+
+/*
+ @Autowired
+ private SessionFactory sessionFactory;
+
+ private Session openSession() {
+ return sessionFactory.getCurrentSession();
+ }
+*/
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ @Transactional
+ public User save(User user) {
+ if (user.isNew()) {
+ em.persist(user);
+ return user;
+ } else {
+ return em.merge(user);
+ }
+ }
+
+ @Override
+ public User get(int id) {
+ return em.find(User.class, id);
+ }
+
+ @Override
+ @Transactional
+ public boolean delete(int id) {
+
+/* User ref = em.getReference(User.class, id);
+ em.remove(ref);
+
+ Query query = em.createQuery("DELETE FROM User u WHERE u.id=:id");
+ return query.setParameter("id", id).executeUpdate() != 0;
+*/
+ return em.createNamedQuery(User.DELETE)
+ .setParameter("id", id)
+ .executeUpdate() != 0;
+ }
+
+ @Override
+ public User getByEmail(String email) {
+ List users = em.createNamedQuery(User.BY_EMAIL, User.class)
+ .setParameter(1, email)
+ .getResultList();
+ return DataAccessUtils.singleResult(users);
+ }
+
+ @Override
+ public List getAll() {
+ return em.createNamedQuery(User.ALL_SORTED, User.class).getResultList();
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java
new file mode 100644
index 000000000000..df874378eb82
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java
@@ -0,0 +1,50 @@
+package ru.javawebinar.topjava.service;
+
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.repository.MealRepository;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import static ru.javawebinar.topjava.util.DateTimeUtil.atStartOfDayOrMin;
+import static ru.javawebinar.topjava.util.DateTimeUtil.atStartOfNextDayOrMax;
+import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId;
+
+@Service
+public class MealService {
+
+ private final MealRepository repository;
+
+ public MealService(MealRepository repository) {
+ this.repository = repository;
+ }
+
+ public Meal get(int id, int userId) {
+ return checkNotFoundWithId(repository.get(id, userId), id);
+ }
+
+ public void delete(int id, int userId) {
+ checkNotFoundWithId(repository.delete(id, userId), id);
+ }
+
+ public List getBetweenInclusive(@Nullable LocalDate startDate, @Nullable LocalDate endDate, int userId) {
+ return repository.getBetweenHalfOpen(atStartOfDayOrMin(startDate), atStartOfNextDayOrMax(endDate), userId);
+ }
+
+ public List getAll(int userId) {
+ return repository.getAll(userId);
+ }
+
+ public void update(Meal meal, int userId) {
+ Assert.notNull(meal, "meal must not be null");
+ checkNotFoundWithId(repository.save(meal, userId), meal.id());
+ }
+
+ public Meal create(Meal meal, int userId) {
+ Assert.notNull(meal, "meal must not be null");
+ return repository.save(meal, userId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java
new file mode 100644
index 000000000000..09ccee68c43f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java
@@ -0,0 +1,48 @@
+package ru.javawebinar.topjava.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+
+import java.util.List;
+
+import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound;
+import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId;
+
+@Service
+public class UserService {
+
+ private final UserRepository repository;
+
+ public UserService(UserRepository repository) {
+ this.repository = repository;
+ }
+
+ public User create(User user) {
+ Assert.notNull(user, "user must not be null");
+ return repository.save(user);
+ }
+
+ public void delete(int id) {
+ checkNotFoundWithId(repository.delete(id), id);
+ }
+
+ public User get(int id) {
+ return checkNotFoundWithId(repository.get(id), id);
+ }
+
+ public User getByEmail(String email) {
+ Assert.notNull(email, "email must not be null");
+ return checkNotFound(repository.getByEmail(email), "email=" + email);
+ }
+
+ public List getAll() {
+ return repository.getAll();
+ }
+
+ public void update(User user) {
+ Assert.notNull(user, "user must not be null");
+ checkNotFoundWithId(repository.save(user), user.id());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/to/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java
new file mode 100644
index 000000000000..d14feae792b7
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java
@@ -0,0 +1,54 @@
+package ru.javawebinar.topjava.to;
+
+import java.time.LocalDateTime;
+
+public class MealTo {
+ private final Integer id;
+
+ private final LocalDateTime dateTime;
+
+ private final String description;
+
+ private final int calories;
+
+ private final boolean excess;
+
+ public MealTo(Integer id, LocalDateTime dateTime, String description, int calories, boolean excess) {
+ this.id = id;
+ this.dateTime = dateTime;
+ this.description = description;
+ this.calories = calories;
+ this.excess = excess;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getCalories() {
+ return calories;
+ }
+
+ public boolean isExcess() {
+ return excess;
+ }
+
+ @Override
+ public String toString() {
+ return "MealTo{" +
+ "id=" + id +
+ ", dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ ", excess=" + excess +
+ '}';
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java
new file mode 100644
index 000000000000..a4665e2ae0e8
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java
@@ -0,0 +1,43 @@
+package ru.javawebinar.topjava.util;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+public class DateTimeUtil {
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+
+ // DB doesn't support LocalDate.MIN/MAX
+ private static final LocalDateTime MIN_DATE = LocalDateTime.of(1, 1, 1, 0, 0);
+ private static final LocalDateTime MAX_DATE = LocalDateTime.of(3000, 1, 1, 0, 0);
+
+ private DateTimeUtil() {
+ }
+
+ public static LocalDateTime atStartOfDayOrMin(LocalDate localDate) {
+ return localDate != null ? localDate.atStartOfDay() : MIN_DATE;
+ }
+
+ public static LocalDateTime atStartOfNextDayOrMax(LocalDate localDate) {
+ return localDate != null ? localDate.plus(1, ChronoUnit.DAYS).atStartOfDay() : MAX_DATE;
+ }
+
+ public static String toString(LocalDateTime ldt) {
+ return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER);
+ }
+
+ public static @Nullable
+ LocalDate parseLocalDate(@Nullable String str) {
+ return StringUtils.hasLength(str) ? LocalDate.parse(str) : null;
+ }
+
+ public static @Nullable
+ LocalTime parseLocalTime(@Nullable String str) {
+ return StringUtils.hasLength(str) ? LocalTime.parse(str) : null;
+ }
+}
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..c99874f6ce3b
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java
@@ -0,0 +1,44 @@
+package ru.javawebinar.topjava.util;
+
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.to.MealTo;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class MealsUtil {
+ public static final int DEFAULT_CALORIES_PER_DAY = 2000;
+
+ private MealsUtil() {
+ }
+
+ public static List getTos(Collection meals, int caloriesPerDay) {
+ return filterByPredicate(meals, caloriesPerDay, meal -> true);
+ }
+
+ public static List getFilteredTos(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) {
+ return filterByPredicate(meals, caloriesPerDay, meal -> Util.isBetweenHalfOpen(meal.getTime(), startTime, endTime));
+ }
+
+ public static List filterByPredicate(Collection meals, int caloriesPerDay, Predicate filter) {
+ Map caloriesSumByDate = meals.stream()
+ .collect(
+ Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories))
+// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum)
+ );
+
+ return meals.stream()
+ .filter(filter)
+ .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay))
+ .collect(Collectors.toList());
+ }
+
+ private static MealTo createTo(Meal meal, boolean excess) {
+ return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java
new file mode 100644
index 000000000000..0860f5c6bf3c
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/Util.java
@@ -0,0 +1,12 @@
+package ru.javawebinar.topjava.util;
+
+import org.springframework.lang.Nullable;
+
+public class Util {
+ private Util() {
+ }
+
+ public static > boolean isBetweenHalfOpen(T value, @Nullable T start, @Nullable T end) {
+ return (start == null || value.compareTo(start) >= 0) && (end == null || value.compareTo(end) < 0);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java
new file mode 100644
index 000000000000..0f35f22a05d3
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java
@@ -0,0 +1,45 @@
+package ru.javawebinar.topjava.util;
+
+
+import ru.javawebinar.topjava.model.AbstractBaseEntity;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+public class ValidationUtil {
+ private ValidationUtil() {
+ }
+
+ public static T checkNotFoundWithId(T object, int id) {
+ checkNotFoundWithId(object != null, id);
+ return object;
+ }
+
+ public static void checkNotFoundWithId(boolean found, int id) {
+ checkNotFound(found, "id=" + id);
+ }
+
+ public static T checkNotFound(T object, String msg) {
+ checkNotFound(object != null, msg);
+ return object;
+ }
+
+ public static void checkNotFound(boolean found, String msg) {
+ if (!found) {
+ throw new NotFoundException("Not found entity with " + msg);
+ }
+ }
+
+ public static void checkNew(AbstractBaseEntity entity) {
+ if (!entity.isNew()) {
+ throw new IllegalArgumentException(entity + " must be new (id=null)");
+ }
+ }
+
+ public static void assureIdConsistent(AbstractBaseEntity entity, int id) {
+// conservative when you reply, but accept liberally (http://stackoverflow.com/a/32728226/548473)
+ if (entity.isNew()) {
+ entity.setId(id);
+ } else if (entity.id() != id) {
+ throw new IllegalArgumentException(entity + " must be with id=" + id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java
new file mode 100644
index 000000000000..f1e9b0e46376
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java
@@ -0,0 +1,7 @@
+package ru.javawebinar.topjava.util.exception;
+
+public class NotFoundException extends RuntimeException {
+ public NotFoundException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java
new file mode 100644
index 000000000000..a8f60993833f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java
@@ -0,0 +1,94 @@
+package ru.javawebinar.topjava.web;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.util.StringUtils;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.web.meal.MealRestController;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Objects;
+
+import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate;
+import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime;
+
+public class MealServlet extends HttpServlet {
+
+ private ConfigurableApplicationContext springContext;
+ private MealRestController mealController;
+
+ @Override
+ public void init() {
+ springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-db.xml");
+ mealController = springContext.getBean(MealRestController.class);
+ }
+
+ @Override
+ public void destroy() {
+ springContext.close();
+ super.destroy();
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ request.setCharacterEncoding("UTF-8");
+ Meal meal = new Meal(
+ LocalDateTime.parse(request.getParameter("dateTime")),
+ request.getParameter("description"),
+ Integer.parseInt(request.getParameter("calories")));
+
+ if (StringUtils.hasLength(request.getParameter("id"))) {
+ mealController.update(meal, getId(request));
+ } else {
+ mealController.create(meal);
+ }
+ response.sendRedirect("meals");
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String action = request.getParameter("action");
+
+ switch (action == null ? "all" : action) {
+ case "delete":
+ int id = getId(request);
+ mealController.delete(id);
+ response.sendRedirect("meals");
+ break;
+ case "create":
+ case "update":
+ final Meal meal = "create".equals(action) ?
+ new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) :
+ mealController.get(getId(request));
+ request.setAttribute("meal", meal);
+ request.getRequestDispatcher("/mealForm.jsp").forward(request, response);
+ break;
+ case "filter":
+ LocalDate startDate = parseLocalDate(request.getParameter("startDate"));
+ LocalDate endDate = parseLocalDate(request.getParameter("endDate"));
+ LocalTime startTime = parseLocalTime(request.getParameter("startTime"));
+ LocalTime endTime = parseLocalTime(request.getParameter("endTime"));
+ request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime));
+ request.getRequestDispatcher("/meals.jsp").forward(request, response);
+ break;
+ case "all":
+ default:
+ request.setAttribute("meals", mealController.getAll());
+ request.getRequestDispatcher("/meals.jsp").forward(request, response);
+ break;
+ }
+ }
+
+ private int getId(HttpServletRequest request) {
+ String paramId = Objects.requireNonNull(request.getParameter("id"));
+ return Integer.parseInt(paramId);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java
new file mode 100644
index 000000000000..4bad5863e3c6
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java
@@ -0,0 +1,25 @@
+package ru.javawebinar.topjava.web;
+
+import ru.javawebinar.topjava.model.AbstractBaseEntity;
+
+import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY;
+
+public class SecurityUtil {
+
+ private static int id = AbstractBaseEntity.START_SEQ;
+
+ private SecurityUtil() {
+ }
+
+ public static int authUserId() {
+ return id;
+ }
+
+ public static void setAuthUserId(int id) {
+ SecurityUtil.id = id;
+ }
+
+ public static int authUserCaloriesPerDay() {
+ return DEFAULT_CALORIES_PER_DAY;
+ }
+}
\ No newline at end of file
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..226023400c70
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java
@@ -0,0 +1,28 @@
+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 doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ int userId = Integer.parseInt(request.getParameter("userId"));
+ SecurityUtil.setAuthUserId(userId);
+ response.sendRedirect("meals");
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ log.debug("forward to users");
+ request.getRequestDispatcher("/users.jsp").forward(request, response);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java
new file mode 100644
index 000000000000..bbfe35e3f0fa
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java
@@ -0,0 +1,76 @@
+package ru.javawebinar.topjava.web.meal;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Controller;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.service.MealService;
+import ru.javawebinar.topjava.to.MealTo;
+import ru.javawebinar.topjava.util.MealsUtil;
+import ru.javawebinar.topjava.web.SecurityUtil;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.List;
+
+import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent;
+import static ru.javawebinar.topjava.util.ValidationUtil.checkNew;
+
+@Controller
+public class MealRestController {
+ private static final Logger log = LoggerFactory.getLogger(MealRestController.class);
+
+ private final MealService service;
+
+ public MealRestController(MealService service) {
+ this.service = service;
+ }
+
+ public Meal get(int id) {
+ int userId = SecurityUtil.authUserId();
+ log.info("get meal {} for user {}", id, userId);
+ return service.get(id, userId);
+ }
+
+ public void delete(int id) {
+ int userId = SecurityUtil.authUserId();
+ log.info("delete meal {} for user {}", id, userId);
+ service.delete(id, userId);
+ }
+
+ public List getAll() {
+ int userId = SecurityUtil.authUserId();
+ log.info("getAll for user {}", userId);
+ return MealsUtil.getTos(service.getAll(userId), SecurityUtil.authUserCaloriesPerDay());
+ }
+
+ public Meal create(Meal meal) {
+ int userId = SecurityUtil.authUserId();
+ checkNew(meal);
+ log.info("create {} for user {}", meal, userId);
+ return service.create(meal, userId);
+ }
+
+ public void update(Meal meal, int id) {
+ int userId = SecurityUtil.authUserId();
+ assureIdConsistent(meal, id);
+ log.info("update {} for user {}", meal, userId);
+ service.update(meal, userId);
+ }
+
+ /**
+ * Filter separately
+ *
by date
+ *
by time for every date
+ *
+ */
+ public List getBetween(@Nullable LocalDate startDate, @Nullable LocalTime startTime,
+ @Nullable LocalDate endDate, @Nullable LocalTime endTime) {
+ int userId = SecurityUtil.authUserId();
+ log.info("getBetween dates({} - {}) time({} - {}) for user {}", startDate, endDate, startTime, endTime, userId);
+
+ List mealsDateFiltered = service.getBetweenInclusive(startDate, endDate, userId);
+ return MealsUtil.getFilteredTos(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), startTime, endTime);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java
new file mode 100644
index 000000000000..0000f1c1e02f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java
@@ -0,0 +1,51 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.service.UserService;
+
+import java.util.List;
+
+import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent;
+import static ru.javawebinar.topjava.util.ValidationUtil.checkNew;
+
+public abstract class AbstractUserController {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Autowired
+ private UserService service;
+
+ public List getAll() {
+ log.info("getAll");
+ return service.getAll();
+ }
+
+ public User get(int id) {
+ log.info("get {}", id);
+ return service.get(id);
+ }
+
+ public User create(User user) {
+ log.info("create {}", user);
+ checkNew(user);
+ return service.create(user);
+ }
+
+ public void delete(int id) {
+ log.info("delete {}", id);
+ service.delete(id);
+ }
+
+ public void update(User user, int id) {
+ log.info("update {} with id={}", user, id);
+ assureIdConsistent(user, id);
+ service.update(user);
+ }
+
+ public User getByMail(String email) {
+ log.info("getByEmail {}", email);
+ return service.getByEmail(email);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java
new file mode 100644
index 000000000000..b37a8ed6c8a5
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java
@@ -0,0 +1,40 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.springframework.stereotype.Controller;
+import ru.javawebinar.topjava.model.User;
+
+import java.util.List;
+
+@Controller
+public class AdminRestController extends AbstractUserController {
+
+ @Override
+ public List getAll() {
+ return super.getAll();
+ }
+
+ @Override
+ public User get(int id) {
+ return super.get(id);
+ }
+
+ @Override
+ public User create(User user) {
+ return super.create(user);
+ }
+
+ @Override
+ public void delete(int id) {
+ super.delete(id);
+ }
+
+ @Override
+ public void update(User user, int id) {
+ super.update(user, id);
+ }
+
+ @Override
+ public User getByMail(String email) {
+ return super.getByMail(email);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java
new file mode 100644
index 000000000000..7d3702c31c46
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java
@@ -0,0 +1,22 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.springframework.stereotype.Controller;
+import ru.javawebinar.topjava.model.User;
+
+import static ru.javawebinar.topjava.web.SecurityUtil.authUserId;
+
+@Controller
+public class ProfileRestController extends AbstractUserController {
+
+ public User get() {
+ return super.get(authUserId());
+ }
+
+ public void delete() {
+ super.delete(authUserId());
+ }
+
+ public void update(User user) {
+ super.update(user, authUserId());
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties
new file mode 100644
index 000000000000..c7944e25c223
--- /dev/null
+++ b/src/main/resources/db/hsqldb.properties
@@ -0,0 +1,12 @@
+#database.url=jdbc:hsqldb:file:D:/temp/topjava
+
+database.url=jdbc:hsqldb:mem:topjava
+database.username=sa
+database.password=
+database.driverClassName=org.hsqldb.jdbcDriver
+
+database.init=true
+jdbc.initLocation=classpath:db/initDB_hsql.sql
+jpa.showSql=true
+hibernate.format_sql=true
+hibernate.use_sql_comments=true
\ No newline at end of file
diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql
new file mode 100644
index 000000000000..af0ff307cb0a
--- /dev/null
+++ b/src/main/resources/db/initDB.sql
@@ -0,0 +1,37 @@
+DROP TABLE IF EXISTS user_roles;
+DROP TABLE IF EXISTS meals;
+DROP TABLE IF EXISTS users;
+DROP SEQUENCE IF EXISTS global_seq;
+
+CREATE SEQUENCE global_seq START WITH 100000;
+
+CREATE TABLE users
+(
+ id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
+ name VARCHAR NOT NULL,
+ email VARCHAR NOT NULL,
+ password VARCHAR NOT NULL,
+ registered TIMESTAMP DEFAULT now() NOT NULL,
+ enabled BOOL DEFAULT TRUE NOT NULL,
+ calories_per_day INTEGER DEFAULT 2000 NOT NULL
+);
+CREATE UNIQUE INDEX users_unique_email_idx ON users (email);
+
+CREATE TABLE user_roles
+(
+ user_id INTEGER NOT NULL,
+ role VARCHAR,
+ CONSTRAINT user_roles_idx UNIQUE (user_id, role),
+ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
+);
+
+CREATE TABLE meals
+(
+ id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
+ user_id INTEGER NOT NULL,
+ date_time TIMESTAMP NOT NULL,
+ description TEXT NOT NULL,
+ calories INT NOT NULL,
+ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
+);
+CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals (user_id, date_time);
\ No newline at end of file
diff --git a/src/main/resources/db/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql
new file mode 100644
index 000000000000..37f2da1bf3d8
--- /dev/null
+++ b/src/main/resources/db/initDB_hsql.sql
@@ -0,0 +1,39 @@
+DROP TABLE user_roles IF EXISTS;
+DROP TABLE meals IF EXISTS;
+DROP TABLE users IF EXISTS;
+DROP SEQUENCE global_seq IF EXISTS;
+
+CREATE SEQUENCE GLOBAL_SEQ AS INTEGER START WITH 100000;
+
+CREATE TABLE users
+(
+ id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ email VARCHAR(255) NOT NULL,
+ password VARCHAR(255) NOT NULL,
+ registered TIMESTAMP DEFAULT now() NOT NULL,
+ enabled BOOLEAN DEFAULT TRUE NOT NULL,
+ calories_per_day INTEGER DEFAULT 2000 NOT NULL
+);
+CREATE UNIQUE INDEX users_unique_email_idx
+ ON USERS (email);
+
+CREATE TABLE user_roles
+(
+ user_id INTEGER NOT NULL,
+ role VARCHAR(255),
+ CONSTRAINT user_roles_idx UNIQUE (user_id, role),
+ FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE
+);
+
+CREATE TABLE meals
+(
+ id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
+ date_time TIMESTAMP NOT NULL,
+ description VARCHAR(255) NOT NULL,
+ calories INT NOT NULL,
+ user_id INTEGER NOT NULL,
+ FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE
+);
+CREATE UNIQUE INDEX meals_unique_user_datetime_idx
+ ON meals (user_id, date_time)
\ No newline at end of file
diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql
new file mode 100644
index 000000000000..a6c79fb907dc
--- /dev/null
+++ b/src/main/resources/db/populateDB.sql
@@ -0,0 +1,23 @@
+DELETE FROM user_roles;
+DELETE FROM meals;
+DELETE FROM users;
+ALTER SEQUENCE global_seq RESTART WITH 100000;
+
+INSERT INTO users (name, email, password)
+VALUES ('User', 'user@yandex.ru', 'password'),
+ ('Admin', 'admin@gmail.com', 'admin');
+
+INSERT INTO user_roles (role, user_id)
+VALUES ('USER', 100000),
+ ('ADMIN', 100001);
+
+INSERT INTO meals (date_time, description, calories, user_id)
+VALUES ('2020-01-30 10:00:00', 'Завтрак', 500, 100000),
+ ('2020-01-30 13:00:00', 'Обед', 1000, 100000),
+ ('2020-01-30 20:00:00', 'Ужин', 500, 100000),
+ ('2020-01-31 0:00:00', 'Еда на граничное значение', 100, 100000),
+ ('2020-01-31 10:00:00', 'Завтрак', 500, 100000),
+ ('2020-01-31 13:00:00', 'Обед', 1000, 100000),
+ ('2020-01-31 20:00:00', 'Ужин', 510, 100000),
+ ('2020-01-31 14:00:00', 'Админ ланч', 510, 100001),
+ ('2020-01-31 21:00:00', 'Админ ужин', 1500, 100001);
\ No newline at end of file
diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties
new file mode 100644
index 000000000000..5ea981f0a5ca
--- /dev/null
+++ b/src/main/resources/db/postgres.properties
@@ -0,0 +1,14 @@
+#database.url=jdbc:postgresql://ec2-54-247-74-197.eu-west-1.compute.amazonaws.com:5432/de4fjsqhdvl7ld?ssl=true&sslmode=require&sslfactory=org.postgresql.ssl.NonValidatingFactory
+#database.username=anbxkjtzukqacj
+#database.password=da1f25b2a38784fb0d46858e5b8fc168e08c9e1e9c72faea5bbac9c0e1f9c24f
+
+database.url=jdbc:postgresql://localhost:5432/topjava
+database.username=user
+database.password=password
+database.driverClassName=org.postgresql.Driver
+
+database.init=true
+jdbc.initLocation=classpath:db/initDB.sql
+jpa.showSql=true
+hibernate.format_sql=true
+hibernate.use_sql_comments=true
\ No newline at end of file
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 000000000000..c7bffc3a958c
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ ${TOPJAVA_ROOT}/log/topjava.log
+
+
+ UTF-8
+ %date %-5level %logger{50}.%M:%L - %msg%n
+
+
+
+
+
+ UTF-8
+ %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml
new file mode 100644
index 000000000000..4c17228b7a87
--- /dev/null
+++ b/src/main/resources/spring/spring-app.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml
new file mode 100644
index 000000000000..970261d06d54
--- /dev/null
+++ b/src/main/resources/spring/spring-db.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld
new file mode 100644
index 000000000000..d138fecdbfb5
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tld/functions.tld
@@ -0,0 +1,16 @@
+
+
+
+ 1.0
+ functions
+ http://topjava.javawebinar.ru/functions
+
+
+ formatDateTime
+ ru.javawebinar.topjava.util.DateTimeUtil
+ java.lang.String toString(java.time.LocalDateTime)
+
+
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000000..bd98d3bf3f6a
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,29 @@
+
+
+ Topjava
+
+
+ userServlet
+ ru.javawebinar.topjava.web.UserServlet
+ 0
+
+
+ userServlet
+ /users
+
+
+
+ mealServlet
+ ru.javawebinar.topjava.web.MealServlet
+ 0
+
+
+ mealServlet
+ /meals
+
+
+
diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css
new file mode 100644
index 000000000000..26a14ce43e6d
--- /dev/null
+++ b/src/main/webapp/css/style.css
@@ -0,0 +1,24 @@
+dl {
+ background: none repeat scroll 0 0 #FAFAFA;
+ margin: 8px 0;
+ padding: 0;
+}
+
+dt {
+ display: inline-block;
+ width: 170px;
+}
+
+dd {
+ display: inline-block;
+ margin-left: 8px;
+ vertical-align: top;
+}
+
+tr[data-mealExcess="false"] {
+ color: green;
+}
+
+tr[data-mealExcess="true"] {
+ color: red;
+}
diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
new file mode 100644
index 000000000000..ce44f5b05bf7
--- /dev/null
+++ b/src/main/webapp/index.html
@@ -0,0 +1,18 @@
+
+
+
+ Java Enterprise (Topjava)
+
+
+