From 572d0459e0c109ad051119757ef65d11b70f6302 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 18 Feb 2020 11:51:18 +0300 Subject: [PATCH 001/127] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 228b708b7094..ab4d97e44213 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ Java Enterprise Online Project - Выполнить задание и залить на GitHub (commit + push) - Переключиться в основную ветку проекта master. +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/123XyBYVeKLC3ZcRr_dUkwyvO9NC6WLkY/view?usp=sharing) +- [Типы промежутков](https://ru.wikipedia.org/wiki/Промежуток_(математика)) + ## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW0 ``` Реализовать метод `UserMealsUtil.filteredByCycles` через циклы (`forEach`): From 796d81b362a3e087d016e90e503fe208eb55740b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 5 Mar 2020 23:56:35 +0300 Subject: [PATCH 002/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab4d97e44213..6b840eac3b03 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Java Enterprise Online Project #### Java (базовые вещи) - Интуит. Программирование на Java - 1й урок MasterJava: Многопоточность -- Основы Java garbage collection +- [Основы Java garbage collection](http://web.archive.org/web/20180831013112/https://ggenikus.github.io/blog/2014/05/04/gc) - Размер Java объектов - Введение в Java Reflection API - Структуры данных в картинках From 80a3c742a574cfaf871982aeedb95fcd62722f66 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 26 Mar 2020 16:02:15 +0300 Subject: [PATCH 003/127] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index a24d881fdeba..60860899fb3c 100644 --- a/cv.md +++ b/cv.md @@ -106,6 +106,8 @@ - Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи. - [**5 вещей, которые разработчик должен сделать прежде чем попросить о помощи**](https://techrocks.ru/2018/07/16/5-things-a-developer-should-do-before-asking-for-help/) - [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) +- [ТОП-13 ошибок начинающего программиста](https://proglib.io/p/beginners-fails/) +- [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) ## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246) From bee6ffcbffc764b82c1abd2fb4f6ff1087faa0a1 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 23 Apr 2020 14:05:13 +0300 Subject: [PATCH 004/127] Update ReleaseNotes.md --- ReleaseNotes.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index f4e7412eb816..f0cf1ed046f5 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,20 @@ # TopJava Release Notes +### Topjava 19 +- Изменилась логика для интервалов времени (исключаем `endTime`) +- Заменил собственный `MessageUtil` велосипед на спринговый `MessageSourceAccessor` +- В ролях убрал префиксы `ROLE_` ([Role and GrantedAuthority](https://stackoverflow.com/a/19542316/548473)) +- Добавился удобный метод `int AbstractBaseEntity.id()` +- Фикс `Location` в `ProfileRestController.register` +- Фикс валидации `UniqueMailValidator` для REST update без `user.id` +- Заменил `jdbc.initLocation` на полный путь - IDEA не ругается +- В конфигурации `cargo-maven2-plugin` сделал [индивидуальный контекст приложения](https://stackoverflow.com/a/60797999/548473) +- Тесты + - Обновил даты еды на 2020г. + - Зарефакторил тесты сервисов на удаление - `NotFoundException` может бросаться при `delete()` + - В тестах контроллеров вернулся к реализации без обертки над `MockMvcRequestBuilders` + - Для `InMemory` тестов подключаю только `inmemory.xml` (добавил туда необходимую конфигурацию из `spring-app.xml`) + + ### Topjava 18 - В `ErrorType` добавил `HttpStatus status` From 2620c6f93e413d51271a5398e8545f3569036638 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 10 May 2020 23:16:00 +0300 Subject: [PATCH 005/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b840eac3b03..f2777b677f7a 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Java Enterprise Online Project - без циклов по другим коллекциям - решение должно быть рабочим в общем случае (не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (его копиям). + - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованому). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка From 3449156db3deb9fb8cb0d4c8d9bd5e8809729304 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 10 May 2020 23:26:52 +0300 Subject: [PATCH 006/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2777b677f7a..eece6c31d014 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Java Enterprise Online Project - без циклов по другим коллекциям - решение должно быть рабочим в общем случае (не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованому). + - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка From 052adf67944cf47671f94293014fbcfce7caa270 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 13 May 2020 22:03:35 +0300 Subject: [PATCH 007/127] Update cv.md --- cv.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cv.md b/cv.md index 60860899fb3c..f765942cf700 100644 --- a/cv.md +++ b/cv.md @@ -20,6 +20,7 @@ ### Наши истории (делимся опытом и успехом) ### Тесты/задачи онлайн: +-[Interviewing: the most profitable skill you can learn](https://www.pramp.com) - [Java Programming Test](https://tests4geeks.com/java) - game: test Java skills - Codility lesson tests From 7c31a8ee0c7a6f87064a23a6d69f8b053a4b1cbf Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 13 May 2020 22:04:00 +0300 Subject: [PATCH 008/127] Update cv.md --- cv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cv.md b/cv.md index f765942cf700..51f5a7527ad9 100644 --- a/cv.md +++ b/cv.md @@ -20,7 +20,7 @@ ### Наши истории (делимся опытом и успехом) ### Тесты/задачи онлайн: --[Interviewing: the most profitable skill you can learn](https://www.pramp.com) +- [Interviewing: the most profitable skill you can learn](https://www.pramp.com) - [Java Programming Test](https://tests4geeks.com/java) - game: test Java skills - Codility lesson tests From 54f4ec0d6363ef814f3001f831b484dff60f3a7b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 13 May 2020 22:05:03 +0300 Subject: [PATCH 009/127] Update cv.md --- cv.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cv.md b/cv.md index 51f5a7527ad9..a330bc3c71cb 100644 --- a/cv.md +++ b/cv.md @@ -20,7 +20,7 @@ ### Наши истории (делимся опытом и успехом) ### Тесты/задачи онлайн: -- [Interviewing: the most profitable skill you can learn](https://www.pramp.com) +- [Interviewing: the most profitable skill you can learn (pramp.com)](https://www.pramp.com/) - [Java Programming Test](https://tests4geeks.com/java) - game: test Java skills - Codility lesson tests @@ -29,7 +29,6 @@ - Sphere online judge - Codility programmers lessons - Hackerrank practice coding -- [Interviewing: the most profitable skill you can learn (pramp.com)](https://www.pramp.com/) - [start.interviewing.io](https://start.interviewing.io/) ## [Тестовое собеседование, самые спрашиваемые темы](http://javaops.ru/interview/test.html) From 5fe982c3fa982a20b6d31aee74dfc15bfff8ab62 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 17 May 2020 23:21:23 +0300 Subject: [PATCH 010/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eece6c31d014..a91e1e814c5a 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Java Enterprise Online Project ### Полезные ресурсы > ВНИМАНИЕ: -> - **ДЗ первого урока будет связано с [созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах](http://danielniko.com/2012/04/17/simple-crud-using-jsp-servlet-and-mysql/)**. Введение будет, но предварительное знакомство не помешает. +> - **ДЗ первого урока будет связано с [созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах](https://danielniko.wordpress.com/2012/04/17/simple-crud-using-jsp-servlet-and-mysql/)**. Введение будет, но предварительное знакомство не помешает. > - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! Все остальное - опционально. From c2180201f5095afdaf632c3e71681acf44d56014 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 19 May 2020 11:43:39 +0300 Subject: [PATCH 011/127] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a91e1e814c5a..040f70a6e44e 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,8 @@ Java Enterprise Online Project - Шпаргалка Java Stream API - Алексея Владыкин: Элементы функционального программирования в Java - Yakov Fain о новом в Java 8 -- stream.map vs forEach +- stream.map vs forEach Date: Tue, 19 May 2020 11:45:35 +0300 Subject: [PATCH 012/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 040f70a6e44e..f7cefcd90fb8 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Java Enterprise Online Project Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка -- [Полное руководство по Java 8 Stream API в картинках и примерах](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor/) +- [Java 8 Stream API, часть шестая: собственный коллектор](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor) - [Руководство по Java 8 Stream API: Collector](https://annimon.com/article/2778#collector) ### Замечания по использованию Stream API: From 3b91874e6df53a7d7fcebb7efa8fc0bf40ad2be9 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 19 May 2020 11:46:05 +0300 Subject: [PATCH 013/127] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f7cefcd90fb8..6a58dbce5545 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,6 @@ Java Enterprise Online Project - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка - - [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) From 18f0374c537cd28f46b4e1db8f98fc5088f8ac9a Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 19 May 2020 11:47:22 +0300 Subject: [PATCH 014/127] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6a58dbce5545..d3a4c1e67cec 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,8 @@ Java Enterprise Online Project - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка + +Ресурсы: - [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) From ba240e81eca53c90ca40ec4bfa771393bc8ef71e Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 20 May 2020 22:16:40 +0300 Subject: [PATCH 015/127] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3a4c1e67cec..8755b22281f5 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ Java Enterprise Online Project - [Git - для новичков](https://www.youtube.com/watch?list=PLY4rE9dstrJyTdVJpv7FibSaXB4BHPInb&v=PEKN8NtBDQ0) ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Работа с проектом (выполнять инструкции) -**ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** +- **ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** +- **Плагин уже Git Intergation не требуется и вкладку `Version control` в IDEA переименовали в `Git`** ### Патч [prepare_to_HW0.patch](https://drive.google.com/file/d/1LNPpu9OkuCpfpD8ZJHO-o0vwu49p2i5M) (скачать и положить в каталог вашего проекта) > Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео: From 7eb4a67c93f47950a420d561b7de6956c1a9eb1b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 23 May 2020 19:10:30 +0300 Subject: [PATCH 016/127] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8755b22281f5..777992140667 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Java Enterprise Online Project - Git Overview - [Основы Git за 20 минут](https://www.youtube.com/watch?v=TMeZGvtQnT8) - [Git - для новичков](https://www.youtube.com/watch?list=PLY4rE9dstrJyTdVJpv7FibSaXB4BHPInb&v=PEKN8NtBDQ0) + - [Руководство по написанию комментариев в коммитах](https://techrocks.ru/2019/12/02/writing-good-commit-messages) ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Работа с проектом (выполнять инструкции) - **ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** From efd86a06ca26f316d5a38a58de6b887789c7cd36 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 24 May 2020 01:00:39 +0300 Subject: [PATCH 017/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 777992140667..754e9d2cdf25 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Java Enterprise Online Project - 10: `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование. - 11: Результаты, возвращаемые `UserMealsUtil.filteredByStreams` мы будем использовать [в нашем приложении](http://topjava.herokuapp.com/) для фильтрации по времени и отображения еды правильным цветом. - 12: Обращайте внимание на комментарии к вашим коммитам в git. Они должны быть короткие и информативные (лучше на english) -- 13: Не полагайтесь в решении на то, что список будет подаваться отсортированным. Такого условия нет. +- 13: Не полагайтесь в решении на то, что список еды будет подаваться отсортированным. Такого условия нет. ----- ## [Пример 7-го занятия онлайн стажировки, несколько видео открыто](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md) From 628eb10dea9635fd23acb08b6477f897d14426d8 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 24 May 2020 13:32:50 +0300 Subject: [PATCH 018/127] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 6e4af3e023cc..c1cb45c337b2 100644 --- a/graduation.md +++ b/graduation.md @@ -66,6 +66,7 @@ _Антуан де Сент-Экзюпери_ - 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). - 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB - 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! +- 24: Если задание на English, `readme.md` тоже пишите на English. То же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English. ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From c7a9d42c53c720477944de607b83e722ef3fb750 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 24 May 2020 13:37:51 +0300 Subject: [PATCH 019/127] Update graduation.md --- graduation.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index c1cb45c337b2..60fdff4b9d1d 100644 --- a/graduation.md +++ b/graduation.md @@ -66,7 +66,10 @@ _Антуан де Сент-Экзюпери_ - 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). - 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB - 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! -- 24: Если задание на English, `readme.md` тоже пишите на English. То же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English. +- 24: `readme.md`: + - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) + - Требуемые примеры `curl` не прячте- пишите здесь! + - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From 7d0145036d3e3e1c69fd053cb6195379aaf9c9ff Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 27 May 2020 13:45:01 +0300 Subject: [PATCH 020/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 754e9d2cdf25..379464cf4f6b 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Java Enterprise Online Project - [Сергей Куксенко — Stream API, часть 1](https://www.youtube.com/watch?v=O8oN4KSZEXE) - [Сергей Куксенко — Stream API, часть 2](https://www.youtube.com/watch?v=i0Jr2l3jrDA) -### Optional 2 (+5 бонусов) +### Optional 2 (+5 бонусов, только после выполнения базового и Optional задания!) Сделать реализацию со сложностью O(N) (обратите внимание на п.13 замечаний): - циклом за 1 проход по `List` - без циклов по другим коллекциям From 6464f3cab8aa6fdfd9732d83ee15e972dc9809b7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 27 May 2020 16:02:09 +0300 Subject: [PATCH 021/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 379464cf4f6b..39f4548d8b6f 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Java Enterprise Online Project ### Optional 2 (+5 бонусов, только после выполнения базового и Optional задания!) Сделать реализацию со сложностью O(N) (обратите внимание на п.13 замечаний): - циклом за 1 проход по `List` - - без циклов по другим коллекциям + - без циклов по другим коллекциям (к ним также относим методы коллекций `addAll()/removeAll()`) - решение должно быть рабочим в общем случае (не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). From 8f2c0376054663ce5c73dcbb68a7a2d3cdab6298 Mon Sep 17 00:00:00 2001 From: JavaOPs Date: Thu, 28 May 2020 11:32:08 +0300 Subject: [PATCH 022/127] Update graduate --- graduation.md | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/graduation.md b/graduation.md index 60fdff4b9d1d..af0f4f74078c 100644 --- a/graduation.md +++ b/graduation.md @@ -26,7 +26,7 @@ P.P.S.: Asume that your API will be used by a frontend developer to build fronte ----------------------------- ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации -- Если ты закончил [стажировку Topjava](http://javaops.ru/reg/topjava/grd), **cделай новый проект и добавляй туда из Topjava только то что нужно!** Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - **не нужны!** +- **Сделай новый проект и добавляй туда из Topjava только то что нужно! Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** - **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** @@ -45,27 +45,36 @@ _Антуан де Сент-Экзюпери_ - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования. - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. -- 6: базу лучше взять без установки (H2 или HSQLDB). Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** +- 6: База Данных + - берите без установки (H2 или HSQLDB). Одну!! Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** + - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. + - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок -- 9: далаем REST API в соответствии с концепцией REST - - [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/) - - 10 Best Practices for Better RESTful API +- 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** - [REST resource hierarchy](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) - 10: не смешивайте TO и Entity вместе. Лучше всего, если они будут независимыми друг от друга. -- 11: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера) -- 12: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) -- 13: **Историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** -- 14: Еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! -- 15: Название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил. +- 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`. Назначение этого проекта совсем другое. +- 17: в DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`). +- 18: на topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. +- 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. - 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! -- 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). -- 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB -- 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! +- 21: проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). +- 22: Кэширование + - необязательно, но желательно + - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! + - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение). +- 23: Валидация + - желательна + - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` + - проверяйте входные данные при `create/update` в контроллерах! В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 24: `readme.md`: - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячте- пишите здесь! From 79efab669a610cb09e16621ff25d9240046911cf Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 31 May 2020 23:26:28 +0300 Subject: [PATCH 023/127] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 39f4548d8b6f..c0d69a45fe7e 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,12 @@ Java Enterprise Online Project ### Optional 2 (+5 бонусов, только после выполнения базового и Optional задания!) Сделать реализацию со сложностью O(N) (обратите внимание на п.13 замечаний): - циклом за 1 проход по `List` - - без циклов по другим коллекциям (к ним также относим методы коллекций `addAll()/removeAll()`) - - решение должно быть рабочим в общем случае (не только при запуске main) + - без циклов по другим коллекциям/массивам (к ним также относим методы коллекций `addAll()/removeAll()`) + - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). - Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - - возможно дополнительные проходы по частям списка + - нельзя использовать внешние коллекции, не являющиеся частью коллектора + - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. + Ресурсы: - [Java 8 Stream API, часть шестая: собственный коллектор](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor) From 7664bb38066ab8843696b719a8bdc13a3e138e07 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 4 Jun 2020 18:14:35 +0300 Subject: [PATCH 024/127] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c0d69a45fe7e..f2020bbfcd53 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ Java Enterprise Online Project - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора + - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной копии) - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. From 2cf32713038aae3a29ed2bb3f49ce8ca75ac9068 Mon Sep 17 00:00:00 2001 From: "admin@javaops.ru" Date: Thu, 4 Jun 2020 18:27:09 +0300 Subject: [PATCH 025/127] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2020bbfcd53..638048568128 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Java Enterprise Online Project > - в `UserMeals/UserMealWithExcess` поля изменились на `private` > - обновил данные `UserMealsUtil.meals` и переименовал некоторые пременные, поля и методы > - добавил `UserMealWithExcess.toString()` и метод для выполнения _Optional домашнего задания_ - +> - метод фильтрации в `TimeUtil` переименовали в `isBetweenHalfOpen` (также изменилась логика сравнения - `startTime` включается в интервал) ## Инструкция по шагам (из видео): - Установить ПО (git, JDK8, IntelliJ IDEA, Maven) From 4331b732f57e2bbef144489026f07581d1fa0d6f Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 17 Jun 2020 15:35:54 +0300 Subject: [PATCH 026/127] Update graduation.md --- graduation.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graduation.md b/graduation.md index af0f4f74078c..4580cd250957 100644 --- a/graduation.md +++ b/graduation.md @@ -67,15 +67,16 @@ _Антуан де Сент-Экзюпери_ - 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. - 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! - 21: проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). -- 22: Кэширование +- 22: обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк +- 23: Кэширование - необязательно, но желательно - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение). -- 23: Валидация +- 24: Валидация - желательна - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` - проверяйте входные данные при `create/update` в контроллерах! В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` -- 24: `readme.md`: +- 25: `readme.md`: - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячте- пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) From 64590f1ca576fbc18c743227f61ca33eb21c14f4 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 17 Jun 2020 15:44:52 +0300 Subject: [PATCH 027/127] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 4580cd250957..3c8d83d87b49 100644 --- a/graduation.md +++ b/graduation.md @@ -39,7 +39,7 @@ _Антуан де Сент-Экзюпери_ - 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** - 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу** - 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** -- 4: **учитывайте, что пользователей может быть ооочень много, а админов- мало** +- 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** - 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: - 5.1 НЕ надо делать разные профили базы и работы с ней. - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. From 3823848bb585a052d04d6f55c4654e8a6c25b0ea Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 15:47:52 +0300 Subject: [PATCH 028/127] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 3c8d83d87b49..7dd13808b7e1 100644 --- a/graduation.md +++ b/graduation.md @@ -37,7 +37,7 @@ P.P.S.: Asume that your API will be used by a frontend developer to build fronte _Антуан де Сент-Экзюпери_ - 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** -- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу** +- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. - 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** - 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** - 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: From f2d884f882826c78d00020049f42a5d81cf1c3ae Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 15:49:23 +0300 Subject: [PATCH 029/127] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 7dd13808b7e1..34809fc454db 100644 --- a/graduation.md +++ b/graduation.md @@ -61,7 +61,7 @@ _Антуан де Сент-Экзюпери_ - 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). +- 16: **Используйте DATA-JPA** (сделайте без лишней делегации, напрямую из сервиса/контроллера дергать Repository). - 17: в DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`). - 18: на topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. - 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. From fd2d8522aab499c9aae6d12832b91d8f48bd68cb Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 16:46:23 +0300 Subject: [PATCH 030/127] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 34809fc454db..d947b113c79f 100644 --- a/graduation.md +++ b/graduation.md @@ -49,6 +49,7 @@ _Антуан де Сент-Экзюпери_ - берите без установки (H2 или HSQLDB). Одну!! Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** + - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок - 9: далаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) From b2b9a19dbf33f24e9a1e532c6d74a498328370e1 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 22:20:46 +0300 Subject: [PATCH 031/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 638048568128..38831adc6f29 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Java Enterprise Online Project - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора - - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной копии) + - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной или преобразованной копии) - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. From 7c7e37e548ddc01369ff9448a1a9d390be387141 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 21 Sep 2020 17:37:53 +0300 Subject: [PATCH 032/127] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index d947b113c79f..208ee73c69cb 100644 --- a/graduation.md +++ b/graduation.md @@ -76,7 +76,7 @@ _Антуан де Сент-Экзюпери_ - 24: Валидация - желательна - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` - - проверяйте входные данные при `create/update` в контроллерах! В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` + - проверяйте входные данные при `create/update` **в контроллерах!** В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 25: `readme.md`: - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячте- пишите здесь! From 9c995f9b94680892e8e805462de14e1797aebc29 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 24 Sep 2020 14:26:51 +0300 Subject: [PATCH 033/127] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 208ee73c69cb..a4468a0f8ec9 100644 --- a/graduation.md +++ b/graduation.md @@ -50,6 +50,7 @@ _Антуан де Сент-Экзюпери_ - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные + - таблицы обычно именуются в единственном числе. Исключение - users, т. к. user - зарезервированное слово. `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок - 9: далаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) From 00424a0eaa980dcfcacc6bc6a9bef81b4472b983 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 28 Sep 2020 22:45:15 +0300 Subject: [PATCH 034/127] Update ReleaseNotes.md --- ReleaseNotes.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index f0cf1ed046f5..bf225b9b0647 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,15 @@ # TopJava Release Notes + +### Topjava 20 +- мигрировали на JDK 14 +- в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`) +- в API добавили `/users/{id}/with-meals` (см. [двунаправленные отношения](https://www.codeflow.site/ru/article/jackson-bidirectional-relationships-and-infinite-recursion)) +- в js убрал объект контекст, передаю 3-мя параметрами +- в UI контроллерах убрал префикс `ajax` +- из тестов сервисов убрал `repository`. При проверке через `assertThrows` он не требуется +- в `TestMatcher` сценарии сравнения сделал параметризируемыми (паттерн стратегия) +- добавил `UserTestData.USER_WITH_MEALS_MATCHER` (проверки пользователя сразу с едой) и константу id `NOT_FOUND` + ### Topjava 19 - Изменилась логика для интервалов времени (исключаем `endTime`) - Заменил собственный `MessageUtil` велосипед на спринговый `MessageSourceAccessor` From 61cd81364ee3d2247ef9a53958d23d21a8671f6b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 28 Sep 2020 22:50:02 +0300 Subject: [PATCH 035/127] Update ReleaseNotes.md --- ReleaseNotes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index bf225b9b0647..b102200d7ac1 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -3,11 +3,11 @@ ### Topjava 20 - мигрировали на JDK 14 - в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`) -- в API добавили `/users/{id}/with-meals` (см. [двунаправленные отношения](https://www.codeflow.site/ru/article/jackson-bidirectional-relationships-and-infinite-recursion)) -- в js убрал объект контекст, передаю 3-мя параметрами +- в `topjava.common.js` в `makeEditable()` вместо объекта контекст передаю 3 параметра - в UI контроллерах убрал префикс `ajax` - из тестов сервисов убрал `repository`. При проверке через `assertThrows` он не требуется - в `TestMatcher` сценарии сравнения сделал параметризируемыми (паттерн стратегия) +- в API добавили `/users/{id}/with-meals` (см. [двунаправленные отношения](https://www.codeflow.site/ru/article/jackson-bidirectional-relationships-and-infinite-recursion)) - добавил `UserTestData.USER_WITH_MEALS_MATCHER` (проверки пользователя сразу с едой) и константу id `NOT_FOUND` ### Topjava 19 From 91841cb912cc4ff8711016f34a166cfe94789469 Mon Sep 17 00:00:00 2001 From: "admin@javaops.ru" Date: Thu, 29 Oct 2020 14:10:49 +0300 Subject: [PATCH 036/127] fix grammar --- ReleaseNotes.md | 2 +- description.md | 125 +++++++++++++++++++++++------------------------- graduation.md | 40 ++++++++-------- 3 files changed, 81 insertions(+), 86 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index b102200d7ac1..65a0c209f4c1 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -29,7 +29,7 @@ ### Topjava 18 - В `ErrorType` добавил `HttpStatus status` -- В PostgreSQL обнаружилась бага: граничное значение `0:00` из за ошибок округления попадает в предыдущий интервал. +- В PostgreSQL обнаружилась бага: граничное значение `0:00` из-за ошибок округления попадает в предыдущий интервал. Мораль: всегда в тестах проверяйте граничные значения. Добавил этот случай в тестовые данные. - Изменил `MealRepository.getBetween` (принимаю `@Nullable LocalDate`). Изменились реализации. - Выделил метод `UserService.prepareAndSave` diff --git a/description.md b/description.md index d2448ca99e57..0619c6d9516d 100644 --- a/description.md +++ b/description.md @@ -1,77 +1,72 @@ #### Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API и сохранением в базах данных Postgresql и HSQLDB. -- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения. -- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий. -- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit тестов. -- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой +- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения. +- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий. +- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit тестов. +- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например, мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой Hibernate, делаем конверторы для типов LocalDateTime (Java 8 time API). - Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object. - Т.е на выходе получается не учебный проект, а хорошо маштабируемый шаблон для большого проекта на всех пройденных технологиях. -- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST - контроллеров, насторойка EntityManagerFactory, - выбор реализации пула коннектов. Особое внимание уделяется работе с базой: через Spring JDBC, Spring ORM и - Spring Data Jpa. -- Используются самые востребованные на сегодняшний момент фреймворки: Maven, Spring Security 4 - вместе с Spring Security Test, наиболее удобный для работы с базой проект Spring Data Jpa, библиотека логирования logback, реализующая SLF4J, повсеместно используемый Bootstrap и jQuery. +- Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object. То есть на выходе получается не учебный проект, а хорошо масштабируемый шаблон для большого проекта на всех пройденных технологиях. +- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST контроллеров, настройка EntityManagerFactory, выбор реализации пула коннектов. Особое внимание уделяется работе с базой: через Spring JDBC, Spring ORM и Spring Data Jpa. +- Используются самые востребованные на сегодняшний момент фреймворки: Maven, Spring Security 4 вместе с Spring Security Test, наиболее удобный для работы с базой проекта Spring Data Jpa, библиотека логирования logback, реализующая SLF4J, повсеместно используемый Bootstrap и jQuery. #### Демо разрабатываемого приложения ## План проекта (ссылки на некоторые темы открыты для просмотра) ### Архитектура проекта. Персистентность. -- Системы управления версиями -- Java 8: Lambda, Stream API -- Обзор используемых в проекте технологий и инструментов. -- Инструмент сборки Maven. -- WAR. Веб-контейнер Tomcat. Сервлеты. -- Логирование. -- Обзор стандартных библиотек. Apache Commons, Guava -- Слои приложения. Создание каркаса приложения. -- Обзор Spring Framework. Spring Context. -- Тестирование через JUnit. -- Spring Test -- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM. -- Настройка Database в IDEA. -- Скрипты инициализации базы. Spring Jdbc Template. -- Spring: инициализация и популирование DB -- ORM. Hibernate. JPA. +- Системы управления версиями +- Java 8: Lambda, Stream API +- Обзор используемых в проекте технологий и инструментов. +- Инструмент сборки Maven. +- WAR. Веб-контейнер Tomcat. Сервлеты. +- Логирование. +- Обзор стандартных библиотек. Apache Commons, Guava +- Слои приложения. Создание каркаса приложения. +- Обзор Spring Framework. Spring Context. +- Тестирование через JUnit. +- Spring Test +- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM. +- Настройка Database в IDEA. +- Скрипты инициализации базы. Spring Jdbc Template. +- Spring: инициализация и популирование DB +- ORM. Hibernate. JPA. - [Тестирование JPA сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw) -- Поддержка HSQLDB -- Транзакции -- Профили Maven и Spring -- Пул коннектов -- Spring Data JPA -- Кэш Hibernate +- Поддержка HSQLDB +- Транзакции +- Профили Maven и Spring +- Пул коннектов +- Spring Data JPA +- Кэш Hibernate ### Разработка WEB -- Spring кэш -- Spring Web -- JSP, JSTL, i18n -- Tomcat maven plugin. JNDI -- Spring Web MVC -- Spring Internationalization -- Тестирование Spring MVC -- REST контроллеры -- Тестирование REST контроллеров. Jackson. -- jackson-datatype-hibernate. Тестирование через матчеры. -- Тестирование через SoapUi. UTF-8 -- WebJars. -- Bootstrap. jQuery datatables. -- AJAX. jQuery. Notifications. -- Spring Security -- Spring Binding/Validation -- Работа с datatables через Ajax. -- Spring Security Test +- Spring кэш +- Spring Web +- JSP, JSTL, i18n +- Tomcat maven plugin. JNDI +- Spring Web MVC +- Spring Internationalization +- Тестирование Spring MVC +- REST контроллеры +- Тестирование REST контроллеров. Jackson. +- jackson-datatype-hibernate. Тестирование через матчеры. +- Тестирование через SoapUi. UTF-8 +- WebJars. +- Bootstrap. jQuery datatables. +- AJAX. jQuery. Notifications. +- Spring Security +- Spring Binding/Validation +- Работа с datatables через Ajax. +- Spring Security Test - [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/open?id=0B9Ye2auQ_NsFRTFsTjVHR2dXczA) -- Encoding password -- CSRF (добавление в проект защиты от межсайтовой подделки запроса) -- form-login. Spring Security Taglib -- Handler interceptor -- Spring Exception Handling -- Смена локали -- Фильтрация JSON через @JsonView -- Защита от XSS (Cross Site Scripting) -- Деплой в Heroku -- Локализация datatables, ошибок валидации -- Обработка ошибок 404 (NotFound) -- Доступ к AuthorizedUser -- Собеседование. Разработка ПО +- Encoding password +- CSRF (добавление в проект защиты от межсайтовой подделки запроса) +- form-login. Spring Security Taglib +- Handler interceptor +- Spring Exception Handling +- Смена локали +- Фильтрация JSON с помощью @JsonView +- Защита от XSS (Cross Site Scripting) +- Деплой в Heroku +- Локализация datatables, ошибок валидации +- Обработка ошибок 404 (NotFound) +- Доступ к AuthorizedUser +- Собеседование. Разработка ПО \ No newline at end of file diff --git a/graduation.md b/graduation.md index a4468a0f8ec9..442ce82b8c47 100644 --- a/graduation.md +++ b/graduation.md @@ -11,17 +11,17 @@ Build a voting system for deciding where to have lunch. * 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 asume that he changed his mind. + - If it is before 11:00 we assume that he changed his mind. - If it is after 11:00 then it is too late, vote can't be changed -Each restaurant provides new menu each day. +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. ----------------------------- P.S.: Make sure everything works with latest version that is on github :) -P.P.S.: Asume that your API will be used by a frontend developer to build frontend on top of that. +P.P.S.: Assume that your API will be used by a frontend developer to build frontend on top of that. ----------------------------- ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации @@ -30,7 +30,7 @@ P.P.S.: Asume that your API will be used by a frontend developer to build fronte - **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** -*Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас конечно есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если конечно в ТЗ нет оговорок). Все свои вопросы и предложения и хотелки оформляйете отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например справочник еды)- обязательно напишите в read.me. Как и выбор стратегии кэширования.* +*Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас, конечно, есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если, конечно, в ТЗ нет оговорок). Все свои вопросы, предложения и хотелки оформляйте отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например, справочник еды) - обязательно напишите в read.me. Как и выбор стратегии кэширования.* > Совершенство достигнуто не тогда, когда нечего добавить, а тогда, когда нечего отнять @@ -40,20 +40,20 @@ _Антуан де Сент-Экзюпери_ - 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. - 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** - 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** -- 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: - - 5.1 НЕ надо делать разные профили базы и работы с ней. - - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. - - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования. - - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. +- 5: в проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы приложения код, ничего лишнего: + - 5.1 НЕ надо делать разные профили базы и работы с ней + - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай + - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования + - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. То есть НИЧЕГО ЛИШНЕГО. - 6: База Данных - берите без установки (H2 или HSQLDB). Одну!! Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** - - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. + - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные - таблицы обычно именуются в единственном числе. Исключение - users, т. к. user - зарезервированное слово. `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок -- 9: далаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) +- 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** - [REST resource hierarchy](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) @@ -62,25 +62,25 @@ _Антуан де Сент-Экзюпери_ - 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 их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! +- 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` не прячте- пишите здесь! + - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) + - Требуемые примеры `curl` не прячьте, а пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! From bddd4a8dffcca8b081a25862586b6e09e850a8b5 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 23 Nov 2020 12:02:40 +0300 Subject: [PATCH 037/127] Update cv.md --- cv.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cv.md b/cv.md index a330bc3c71cb..04a98a01039d 100644 --- a/cv.md +++ b/cv.md @@ -109,5 +109,7 @@ - [ТОП-13 ошибок начинающего программиста](https://proglib.io/p/beginners-fails/) - [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) - +- Типичные ошибки начинающих программистов от 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) ## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246) From 325763b91c1a02b99061b39ede1085dbffda9d09 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 7 Dec 2020 23:33:54 +0300 Subject: [PATCH 038/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38831adc6f29..9636181f4588 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Java Enterprise Online Project - Выполнить задание и залить на GitHub (commit + push) - Переключиться в основную ветку проекта master. -## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/123XyBYVeKLC3ZcRr_dUkwyvO9NC6WLkY/view?usp=sharing) +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/1BpTzjNFjS0TSekCyt_xvt6YoLvuw5KTZ/view) - [Типы промежутков](https://ru.wikipedia.org/wiki/Промежуток_(математика)) ## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW0 From 52f1f76870923b3715f1d9561547de45bcb1fbdf Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 21 Dec 2020 17:32:32 +0300 Subject: [PATCH 039/127] Update cv.md --- cv.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cv.md b/cv.md index 04a98a01039d..cc5ff22da638 100644 --- a/cv.md +++ b/cv.md @@ -88,6 +88,7 @@ - Яндекс агрегатор - HH - LinkedIn +- ХабрКарьера - djinni.co (более актуально для Украины) ## Как выжить на испытательном сроке From 1edcd14a9f5d1c780add0a7f2e56069e711c3b3d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 21 Dec 2020 17:40:10 +0300 Subject: [PATCH 040/127] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index cc5ff22da638..46a3442a81f4 100644 --- a/cv.md +++ b/cv.md @@ -89,8 +89,10 @@ - HH - LinkedIn - ХабрКарьера +- [headz.io](https://app.headz.io/candidates/new) - djinni.co (более актуально для Украины) + ## Как выжить на испытательном сроке - Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В процессе формулирования очень часто приходит ее решение. From 5f6d0c40136522bfa67e6b856997f402f295660e Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 16 Jan 2021 00:31:31 +0300 Subject: [PATCH 041/127] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 9636181f4588..1bf92ee2cdd5 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Java Enterprise Online Project - Java 8: Lambda выражения - Java 8: Потоки - Pуководство по Java 8 Stream -- Java 8 Stream API в картинках и примерах +- [Полное руководство по Java 8 Stream API в картинках и примерах](https://annimon.com/article/2778) - [7 способов использовать groupingBy в Stream API](https://habrahabr.ru/post/348536) - Лямбда-выражения в Java 8 - A Guide to Java 8 @@ -111,7 +111,6 @@ Java Enterprise Online Project - Алексея Владыкин: Элементы функционального программирования в Java - Yakov Fain о новом в Java 8 - stream.map vs forEach Date: Mon, 18 Jan 2021 00:28:34 +0300 Subject: [PATCH 042/127] Update ReleaseNotes.md --- ReleaseNotes.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 65a0c209f4c1..7c03206fe59c 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,15 @@ # TopJava Release Notes +### Topjava 21 +- **добавили документирование REST API: Swagger** +- мигрировали на JDK 15 и используем текстовые блоки +- Вынес `produces = MediaType.APPLICATION_JSON_VALUE` на уровень контроллеров +- Правильно используем [глабальные переменные в js](https://stackoverflow.com/a/5064235/548473) +- Зарефакторил `inputField.tag` +- Тестовые переменные переименовал из UPPERCASE в camelCase +- Из тестов сервисов убрал `throws Exception` (в IDEA больше не генерятся по умолчанию) +- **Мигрировали на Spring Boot 2.4.1** + ### Topjava 20 - мигрировали на JDK 14 - в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`) From c2344fec7ec9f27edb507ec78aa05cbf912be732 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 18 Jan 2021 02:08:41 +0300 Subject: [PATCH 043/127] Update cv.md --- cv.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cv.md b/cv.md index 46a3442a81f4..17e0faf1ee83 100644 --- a/cv.md +++ b/cv.md @@ -111,6 +111,7 @@ - [**Советы новичкам**](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://dou.ua/lenta/articles/impostor-syndrome-guide-part1/) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) - Типичные ошибки начинающих программистов от JavaRush: - [Часть 1](https://javarush.ru/groups/posts/3044-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-1) From 2ec0a12b5ecbbc6191188ae38d2bd42085578b73 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 18 Jan 2021 02:10:14 +0300 Subject: [PATCH 044/127] Update cv.md --- cv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cv.md b/cv.md index 17e0faf1ee83..73ca6a62be71 100644 --- a/cv.md +++ b/cv.md @@ -111,7 +111,7 @@ - [**Советы новичкам**](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://dou.ua/lenta/articles/impostor-syndrome-guide-part1/) +- [Путеводитель по синдрому самозванца](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) - Типичные ошибки начинающих программистов от JavaRush: - [Часть 1](https://javarush.ru/groups/posts/3044-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-1) From 4be539c0796b7cd3028cd2279259e23cb19624aa Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 22 Jan 2021 15:40:25 +0300 Subject: [PATCH 045/127] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1bf92ee2cdd5..7cae831f37ca 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,11 @@ Java Enterprise Online Project ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 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`: + +![image](https://user-images.githubusercontent.com/13649199/105491518-72d8f300-5cc7-11eb-8b79-c46382562deb.png) ![image](https://user-images.githubusercontent.com/13649199/105488663-05c35e80-5cc3-11eb-962e-30f403d623e8.png) + ### Патч [prepare_to_HW0.patch](https://drive.google.com/file/d/1LNPpu9OkuCpfpD8ZJHO-o0vwu49p2i5M) (скачать и положить в каталог вашего проекта) > Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео: From a779519ad62dc8287b7a9296397a3521c453bdf3 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 28 Jan 2021 00:30:29 +0300 Subject: [PATCH 046/127] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7cae831f37ca..cb332ffa033b 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ Java Enterprise Online Project - A Guide to Java 8 ### Туториалы, разное +- [Открытый курс: Spring Boot + HATEOAS](https://javaops.ru/view/bootjava) - [Что нужно знать о бэкенде новичку в веб-разработке](https://tproger.ru/translations/backend-web-development) - [Туториалы: Spring Framework, Hibernate, Java Core, JDBC](http://proselyte.net/tutorials/) From c91877064f1b5b7554849e02d7479cdabac24219 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 28 Jan 2021 11:41:19 +0300 Subject: [PATCH 047/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb332ffa033b..71288706bc75 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Java Enterprise Online Project - Оцените Time complexity алгоритма. Если она больше O(N), например O(N*N) или N*log(N), сделайте O(N). ``` - Java 8 Date and Time API -- Алгоритмы и структуры данных для начинающих: сложность алгоритмов +- Алгоритмы и структуры данных для начинающих: сложность алгоритмов - [Головач: сложность алгоритмов в теме коллекций](https://www.youtube.com/watch?v=Ek9ijOiplNE&feature=youtu.be&t=778) - Time complexity - Временная сложность алгоритма From d026d2cf746fca5b071f3b54422ffeb46dcb3868 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 29 Jan 2021 12:25:27 +0300 Subject: [PATCH 048/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71288706bc75..8a58f7f11632 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Java Enterprise Online Project ## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Замечания к HW0 - 1: Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно при правках в рабочем проекте обязательно нужно проконсультироваться/проревьюироваться у авторов кода (находится по истории VCS) -- 2: Наследовать `UserMealWithExcess` от `UserMeal` я не буду, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. +- 2: Наследовать `UserMealWithExcess` от `UserMeal` нельзя, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. Это относится и к зависимости. - 3: Правильная реализация должна быть простой и красивой, можно сделать 2-мя способами: через стримы и через циклы. Сложность должна быть O(N), т.е. без вложенных стримов и циклов. - 4: При реализации через циклы посмотрите в `Map` на методы `getOrDefault` или `merge` - 5: **При реализации через `Stream` заменяйте `forEach` оператором `stream.map(..)`** From 86a6795a6e1997d32c972f53b7a7c045ac55f1dc Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 2 Feb 2021 20:06:39 +0300 Subject: [PATCH 049/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a58f7f11632..470cce9ecab8 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ Java Enterprise Online Project - Введение в Java Reflection API - Структуры данных в картинках - Обзор java.util.concurrent.* -- Синхронизация потоков +- Синхронизация потоков - String literal pool - Маленькие хитрости Java - A Guide to Java 8 From dc1eed9977324cd4b5f5d066382edc572aa09317 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 2 Feb 2021 20:10:55 +0300 Subject: [PATCH 050/127] Update description.md --- description.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/description.md b/description.md index 0619c6d9516d..58fb5d2cdf74 100644 --- a/description.md +++ b/description.md @@ -16,24 +16,24 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - Системы управления версиями - Java 8: Lambda, Stream API - Обзор используемых в проекте технологий и инструментов. -- Инструмент сборки Maven. +- Инструмент сборки Maven - WAR. Веб-контейнер Tomcat. Сервлеты. - Логирование. - Обзор стандартных библиотек. Apache Commons, Guava - Слои приложения. Создание каркаса приложения. - Обзор Spring Framework. Spring Context. - Тестирование через JUnit. -- Spring Test +- Spring Test - Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM. - Настройка Database в IDEA. - Скрипты инициализации базы. Spring Jdbc Template. -- Spring: инициализация и популирование DB +- Spring: инициализация и популирование DB - ORM. Hibernate. JPA. - [Тестирование JPA сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw) - Поддержка HSQLDB - Транзакции - Профили Maven и Spring -- Пул коннектов +- Пул коннектов - Spring Data JPA - Кэш Hibernate @@ -69,4 +69,4 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - Локализация datatables, ошибок валидации - Обработка ошибок 404 (NotFound) - Доступ к AuthorizedUser -- Собеседование. Разработка ПО \ No newline at end of file +- Собеседование. Разработка ПО From fada483294ca81e52c50622b864646106a66d5bd Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 27 Feb 2021 21:51:50 +0300 Subject: [PATCH 051/127] Update cv.md --- cv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cv.md b/cv.md index 73ca6a62be71..878437e24bb4 100644 --- a/cv.md +++ b/cv.md @@ -93,7 +93,7 @@ - djinni.co (более актуально для Украины) -## Как выжить на испытательном сроке +

Как выжить на испытательном сроке

- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В процессе формулирования очень часто приходит ее решение. - Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки From 1d8f32a0b890d5b7a7c04ed798c10df51841ca9d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 27 Feb 2021 21:54:16 +0300 Subject: [PATCH 052/127] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index 878437e24bb4..d3789a9e796a 100644 --- a/cv.md +++ b/cv.md @@ -94,6 +94,7 @@

Как выжить на испытательном сроке

+ - Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В процессе формулирования очень часто приходит ее решение. - Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки @@ -113,6 +114,7 @@ - [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) From 699c90299d78fcbb30ebb65f7ec2712d4925d29d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 10 Apr 2021 12:12:14 +0300 Subject: [PATCH 053/127] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index d3789a9e796a..f459060b227a 100644 --- a/cv.md +++ b/cv.md @@ -37,6 +37,8 @@ - Михаил Портнов. Собеседование на работу: как продать себя грамотно - Михаил Портнов. Какие вопросы мы задаем на собеседовании? - Михаил Портнов. Собеседование на работу: жизненный путь +- [Лёша Корепанов. Признаки плохих компаний для программиста](https://www.youtube.com/watch?v=Sj-WSWr-n7U) +- [Лёша Корепанов. Как отвечать на вопросы, которые ты не знаешь. Техническое интервью для программиста](https://www.youtube.com/watch?v=Beoh3tfgPEk) - Канал: Резюме, поиск работы, интервью - Яков Файн: Как стать профессиональным Java разработчиком - Ответы на вопросы на собеседовании Junior Java Developer From b7f28e7176958c11c1e49680be14559eb8045ed7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 16 Apr 2021 15:10:41 +0300 Subject: [PATCH 054/127] Update description.md --- description.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/description.md b/description.md index 58fb5d2cdf74..0834140f19a7 100644 --- a/description.md +++ b/description.md @@ -70,3 +70,11 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - Обработка ошибок 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 From bc6880c6100e3abcc932958ae72717fcf369af90 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 26 May 2021 13:42:29 +0300 Subject: [PATCH 055/127] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 442ce82b8c47..50494a26ab8f 100644 --- a/graduation.md +++ b/graduation.md @@ -82,6 +82,7 @@ _Антуан де Сент-Экзюпери_ - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячьте, а пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) +- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ сваливать в одну кучу! ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From 8c4236cdbaa50c5d397be711be33065c4ee388f7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 26 May 2021 17:55:43 +0300 Subject: [PATCH 056/127] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 50494a26ab8f..e04d13c35885 100644 --- a/graduation.md +++ b/graduation.md @@ -82,7 +82,7 @@ _Антуан де Сент-Экзюпери_ - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячьте, а пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) -- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ сваливать в одну кучу! +- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From 657ed5fcd836df938c6d540cd7cc919c5e50ece9 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 28 May 2021 15:52:56 +0300 Subject: [PATCH 057/127] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 470cce9ecab8..2941a385669b 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Java Enterprise Online Project - циклом за 1 проход по `List` - без циклов по другим коллекциям/массивам (к ним также относим методы коллекций `addAll()/removeAll()`) - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) -- через Stream API за 1 проход по исходному списку `meals.streem()` +- через Stream API за 1 проход по исходному списку `meals.stream()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной или преобразованной копии) - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. From 1dcc68b58d81d385c867835fa28561f72396fbea Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 28 May 2021 15:54:28 +0300 Subject: [PATCH 058/127] Create README.md From ad6225f2f50a3edbc5eb46f6af969e2995027df4 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 30 May 2021 10:48:37 +0300 Subject: [PATCH 059/127] Update graduation.md --- graduation.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/graduation.md b/graduation.md index e04d13c35885..4aa2c1b00dae 100644 --- a/graduation.md +++ b/graduation.md @@ -16,7 +16,7 @@ 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. +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 - Swagger). ----------------------------- P.S.: Make sure everything works with latest version that is on github :) @@ -27,8 +27,9 @@ P.P.S.: Assume that your API will be used by a frontend developer to build front ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации - **Сделай новый проект и добавляй туда из Topjava только то что нужно! Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** -- **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)** +- **Рекомендую переписать проект современно: Spring Boot + Swagger/OpenAPI 3.0. Оптимально подойдет код миграции TopJava на Spring Boot в конце стажировки.** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** +- API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend) *Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас, конечно, есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если, конечно, в ТЗ нет оговорок). Все свои вопросы, предложения и хотелки оформляйте отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например, справочник еды) - обязательно напишите в read.me. Как и выбор стратегии кэширования.* @@ -36,11 +37,11 @@ P.P.S.: Assume that your API will be used by a frontend developer to build front _Антуан де Сент-Экзюпери_ -- 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** -- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. -- 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** -- 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** -- 5: в проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы приложения код, ничего лишнего: +- 1: **Читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** +- 2: **Тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. +- 3: **Тщательно считайте количество запросов в вашем API для отображения нужной информации** +- 4: Учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** +- 5: В проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы приложения код, ничего лишнего: - 5.1 НЕ надо делать разные профили базы и работы с ней - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования @@ -51,27 +52,27 @@ _Антуан де Сент-Экзюпери_ - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные - таблицы обычно именуются в единственном числе. Исключение - users, т. к. user - зарезервированное слово. `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании -- 7: по возможности сделать JUnit тесты -- 8: уделяйте внимание обработке ошибок -- 9: делаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) +- 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** - [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 (как мы при сохранении еды вставляем туда юзера) +- 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`). НЕ надо придумывать своих собственных правил +- 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`. Назначение этого проекта совсем другое +- 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 код на несколько строк +- 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). +- 22: Обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк - 23: Кэширование - - необязательно, но желательно + - необязательно, но желательно. Чем проще реализация - тем лучше. - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редко запрашиваемые данные)! - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение). - 24: Валидация @@ -80,13 +81,13 @@ _Антуан де Сент-Экзюпери_ - проверяйте входные данные при `create/update` **в контроллерах!** В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 25: `readme.md`: - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - - Требуемые примеры `curl` не прячьте, а пишите здесь! + - Требуемые примеры `curl` не прячьте, а пишите здесь! Оптимально - ссылка на Swagger. - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) -- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! +- 26: На управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. -- Сколько раз пришлось его вызвать API для типичного сценария (нарпимер посмотреть рестораны с едой)? +- Сколько раз пришлось его вызвать API для типичного сценария (нарпимер посмотреть рестораны с едой на сегодня)? - Сколько запросов к базе было сделано? Можно ли сократить (например с FETCH/Graph или через кэширование)? - **API ДОЛЖНО соответствовать принципам REST (см. ссылки выше)** - **ОБЯЗАТЕЛЬНО: запустите `mvn test`- ошибок быть не должно** From 804582e5c5ad766d827f56bfec741931dd53703c Mon Sep 17 00:00:00 2001 From: login Date: Tue, 1 Jun 2021 04:55:56 +0400 Subject: [PATCH 060/127] after patch --- .../javawebinar/topjava/model/UserMeal.java | 29 ++++++++++++++ .../topjava/model/UserMealWithExcess.java | 30 ++++++++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 9 +++++ .../topjava/util/UserMealsUtil.java | 39 +++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMeal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java new file mode 100644 index 000000000000..d8f91b127f6a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class UserMeal { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + public UserMeal(LocalDateTime dateTime, String description, int calories) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java new file mode 100644 index 000000000000..d0aa431a35d9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java @@ -0,0 +1,30 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class UserMealWithExcess { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private final boolean excess; + + public UserMealWithExcess(LocalDateTime dateTime, String description, int calories, boolean excess) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.excess = excess; + } + + @Override + public String toString() { + return "UserMealWithExcess{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", excess=" + excess + + '}'; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java new file mode 100644 index 000000000000..0ebfdb5fcdcb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalTime; + +public class TimeUtil { + public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java new file mode 100644 index 000000000000..3c171b4a5972 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -0,0 +1,39 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.model.UserMealWithExcess; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +public class UserMealsUtil { + public static void main(String[] args) { + List meals = Arrays.asList( + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); + + List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsTo.forEach(System.out::println); + +// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO return filtered list with excess. Implement by cycles + return null; + } + + public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO Implement by streams + return null; + } +} From 24e26a4dff97671f1d156220dd384345a3bf4007 Mon Sep 17 00:00:00 2001 From: login Date: Sat, 19 Jun 2021 21:25:00 +0400 Subject: [PATCH 061/127] HW0 ready optional --- .../topjava/util/UserMealsUtil.java | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 3c171b4a5972..6d9b6a4dfe05 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -1,13 +1,18 @@ package ru.javawebinar.topjava.util; +import com.sun.javafx.collections.MappingChange; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.model.UserMealWithExcess; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class UserMealsUtil { public static void main(String[] args) { @@ -21,19 +26,54 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) ); - List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 1500); mealsTo.forEach(System.out::println); -// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { // TODO return filtered list with excess. Implement by cycles - return null; +// Map map = new MappingChange.Map<>(); +// map.merge() + + int caloriesTemp = 0; + List mealsTo = new ArrayList<>(); + List filteredMeals = new ArrayList<>(); + for (UserMeal currentMeal : meals){ + LocalDateTime currentMealDateTime = currentMeal.getDateTime(); + LocalTime currentMealTime = currentMealDateTime.toLocalTime(); + if (TimeUtil.isBetweenHalfOpen(currentMealTime, startTime, endTime)){ + filteredMeals.add(currentMeal); + caloriesTemp += currentMeal.getCalories(); + } + } + + boolean excess = caloriesTemp > caloriesPerDay; + + for (UserMeal currentMeal : filteredMeals){ + LocalDateTime currentMealDateTime = currentMeal.getDateTime(); + mealsTo.add(new UserMealWithExcess(currentMealDateTime, currentMeal.getDescription(), currentMeal.getCalories(), excess)); + } + + return mealsTo; } public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO Implement by streams - return null; + int caloriesConsumed = meals + .stream() + .filter(s -> TimeUtil.isBetweenHalfOpen(s.getDateTime().toLocalTime(), startTime, endTime)) + .map(fx->fx.getCalories()) + .reduce(0, (acc, fx) -> acc + fx); + + boolean excess = caloriesConsumed > caloriesPerDay; + + List mealsTo = meals + .stream() + .filter(s -> TimeUtil.isBetweenHalfOpen(s.getDateTime().toLocalTime(), startTime, endTime)) + .map(x -> new UserMealWithExcess(x.getDateTime(), x.getDescription(), x.getCalories(), excess)) + .collect(Collectors.toList()); + + return mealsTo; } } From 2cc9401056481ea0808def11e96580a81144435b Mon Sep 17 00:00:00 2001 From: login Date: Wed, 6 Oct 2021 13:52:52 +0400 Subject: [PATCH 062/127] 1 0 rename --- .../model/{UserMeal.java => Meal.java} | 4 +- .../{UserMealWithExcess.java => MealTo.java} | 6 +- .../javawebinar/topjava/util/MealsUtil.java | 47 +++++++++++ .../topjava/util/UserMealsUtil.java | 79 ------------------- 4 files changed, 52 insertions(+), 84 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{UserMeal.java => Meal.java} (83%) rename src/main/java/ru/javawebinar/topjava/model/{UserMealWithExcess.java => MealTo.java} (77%) create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java similarity index 83% rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java rename to src/main/java/ru/javawebinar/topjava/model/Meal.java index d8f91b127f6a..f546cef0f74a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -2,14 +2,14 @@ import java.time.LocalDateTime; -public class UserMeal { +public class Meal { private final LocalDateTime dateTime; private final String description; private final int calories; - public UserMeal(LocalDateTime dateTime, String description, int calories) { + public Meal(LocalDateTime dateTime, String description, int calories) { this.dateTime = dateTime; this.description = description; this.calories = calories; diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java similarity index 77% rename from src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java rename to src/main/java/ru/javawebinar/topjava/model/MealTo.java index d0aa431a35d9..07f04f8dbb9f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -2,7 +2,7 @@ import java.time.LocalDateTime; -public class UserMealWithExcess { +public class MealTo { private final LocalDateTime dateTime; private final String description; @@ -11,7 +11,7 @@ public class UserMealWithExcess { private final boolean excess; - public UserMealWithExcess(LocalDateTime dateTime, String description, int calories, boolean excess) { + public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) { this.dateTime = dateTime; this.description = description; this.calories = calories; @@ -20,7 +20,7 @@ public UserMealWithExcess(LocalDateTime dateTime, String description, int calori @Override public String toString() { - return "UserMealWithExcess{" + + return "MealTo{" + "dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + 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..c29e1fbbb077 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealTo; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MealsUtil { + public static void main(String[] args) { + List meals = Arrays.asList( + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); + + List mealsTo = filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsTo.forEach(System.out::println); + } + + public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + 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(meal -> TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } + + private static MealTo createTo(Meal meal, boolean excess) { + return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java deleted file mode 100644 index 6d9b6a4dfe05..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -package ru.javawebinar.topjava.util; - -import com.sun.javafx.collections.MappingChange; -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.model.UserMealWithExcess; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Month; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class UserMealsUtil { - public static void main(String[] args) { - List meals = Arrays.asList( - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) - ); - - List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 1500); - mealsTo.forEach(System.out::println); - - System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - } - - public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with excess. Implement by cycles -// Map map = new MappingChange.Map<>(); -// map.merge() - - int caloriesTemp = 0; - List mealsTo = new ArrayList<>(); - List filteredMeals = new ArrayList<>(); - for (UserMeal currentMeal : meals){ - LocalDateTime currentMealDateTime = currentMeal.getDateTime(); - LocalTime currentMealTime = currentMealDateTime.toLocalTime(); - if (TimeUtil.isBetweenHalfOpen(currentMealTime, startTime, endTime)){ - filteredMeals.add(currentMeal); - caloriesTemp += currentMeal.getCalories(); - } - } - - boolean excess = caloriesTemp > caloriesPerDay; - - for (UserMeal currentMeal : filteredMeals){ - LocalDateTime currentMealDateTime = currentMeal.getDateTime(); - mealsTo.add(new UserMealWithExcess(currentMealDateTime, currentMeal.getDescription(), currentMeal.getCalories(), excess)); - } - - return mealsTo; - } - - public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - int caloriesConsumed = meals - .stream() - .filter(s -> TimeUtil.isBetweenHalfOpen(s.getDateTime().toLocalTime(), startTime, endTime)) - .map(fx->fx.getCalories()) - .reduce(0, (acc, fx) -> acc + fx); - - boolean excess = caloriesConsumed > caloriesPerDay; - - List mealsTo = meals - .stream() - .filter(s -> TimeUtil.isBetweenHalfOpen(s.getDateTime().toLocalTime(), startTime, endTime)) - .map(x -> new UserMealWithExcess(x.getDateTime(), x.getDescription(), x.getCalories(), excess)) - .collect(Collectors.toList()); - - return mealsTo; - } -} From 71cdaf4374f29ee5c423520625a91f2861f9a39a Mon Sep 17 00:00:00 2001 From: login Date: Wed, 6 Oct 2021 13:53:00 +0400 Subject: [PATCH 063/127] 1 1 HW0 streams --- src/main/java/ru/javawebinar/topjava/model/Meal.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index f546cef0f74a..943ff5cd59fa 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; public class Meal { private final LocalDateTime dateTime; @@ -26,4 +28,12 @@ public String getDescription() { public int getCalories() { return calories; } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } } From 07bc3e77c4be46a3b24fad32b76627aa0510d4b2 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 6 Oct 2021 14:12:59 +0400 Subject: [PATCH 064/127] 1 2 switch to war --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 19 +++++++++++++++++++ src/main/webapp/index.html | 13 +++++++++++++ src/main/webapp/users.jsp | 11 +++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index 0b1c2896da5b..b4426c1aa2d6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..c63810c43593 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + Topjava + + + userServlet + ru.javawebinar.topjava.web.UserServlet + 0 + + + userServlet + /users + + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..58d8d5ab6aec --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,13 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

+
+ + + diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp new file mode 100644 index 000000000000..650c8dda479c --- /dev/null +++ b/src/main/webapp/users.jsp @@ -0,0 +1,11 @@ +<%@ page contentType="text/html;charset=UTF-8" %> + + + Users + + +

Home

+
+

Users

+ + \ No newline at end of file From b4402e9a06154b3b52bb1b9390c83d48b0e45f73 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 6 Oct 2021 14:19:36 +0400 Subject: [PATCH 065/127] 1 3 add servlet api --- pom.xml | 22 ++++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 23 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/pom.xml b/pom.xml index b4426c1aa2d6..5b1b31f67457 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,28 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java new file mode 100644 index 000000000000..ef52d67576c0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,23 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.slf4j.LoggerFactory.getLogger; + +public class UserServlet extends HttpServlet { + private static final Logger log = getLogger(UserServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("redirect to users"); + +// request.getRequestDispatcher("/users.jsp").forward(request, response); + response.sendRedirect("users.jsp"); + } +} From 8eb8d1dbc60b6836111c4d691de7a63b5e6b7e5c Mon Sep 17 00:00:00 2001 From: login Date: Wed, 6 Oct 2021 14:19:41 +0400 Subject: [PATCH 066/127] 1 4 forward to redirect --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5b1b31f67457..e37c4d94e971 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ topjava - install + package org.apache.maven.plugins From 8e497398771659d714b679811c059c574a2620ed Mon Sep 17 00:00:00 2001 From: login Date: Wed, 6 Oct 2021 14:19:45 +0400 Subject: [PATCH 067/127] 1 5 logging --- pom.xml | 4 ++++ src/main/resources/logback.xml | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index e37c4d94e971..75660f0776f1 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ 1.8 UTF-8 UTF-8 + + + 1.2.3 + 1.7.30 diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..e9b900b26669 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From c304db70b77afc478ea97785d044d54248a5068a Mon Sep 17 00:00:00 2001 From: login Date: Wed, 6 Oct 2021 15:00:13 +0400 Subject: [PATCH 068/127] HW0 final --- .../javawebinar/topjava/model/MealWithExcess.java | 12 ++++++++++++ .../ru/javawebinar/topjava/util/MealsUtil.java | 15 +++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/MealWithExcess.java diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExcess.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExcess.java new file mode 100644 index 000000000000..067127662403 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExcess.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class MealWithExcess extends Meal{ + private boolean excess; + + public MealWithExcess(LocalDateTime dateTime, String description, int calories, boolean excess) { + super(dateTime, description, calories); + this.excess = excess; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c29e1fbbb077..27574da3d57f 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -2,6 +2,7 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.model.MealTo; +import ru.javawebinar.topjava.model.MealWithExcess; import java.time.LocalDate; import java.time.LocalDateTime; @@ -44,4 +45,18 @@ public static List filteredByStreams(List meals, LocalTime startTi private static MealTo createTo(Meal meal, boolean excess) { return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); } + + public static List getFilteredMealsWithExceded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final Map caloriesSumByDate = mealList.stream().collect(Collectors.groupingBy(m -> m.getDateTime().toLocalDate(), + Collectors.summingInt(Meal::getCalories))); + + return mealList.stream() + .filter(m->TimeUtil.isBetweenHalfOpen(m.getDateTime().toLocalTime(), startTime, endTime)) + .map(m->new MealWithExcess(m.getDateTime(), m.getDescription(), m.getCalories(), + caloriesSumByDate.get(m.getDateTime().toLocalDate())>caloriesPerDay)) + .collect(Collectors.toList()); + + } } + + From bc79f7c094ab7e6be1c442cc5700324508ed3e9d Mon Sep 17 00:00:00 2001 From: login Date: Thu, 7 Oct 2021 12:35:22 +0400 Subject: [PATCH 069/127] 2 1 HW1 --- pom.xml | 6 +++ .../java/ru/javawebinar/topjava/Main.java | 11 ----- .../ru/javawebinar/topjava/model/MealTo.java | 16 +++++++ .../topjava/util/DateTimeUtil.java | 18 +++++++ .../javawebinar/topjava/util/MealsUtil.java | 5 +- .../ru/javawebinar/topjava/util/TimeUtil.java | 9 ---- .../javawebinar/topjava/web/MealServlet.java | 22 +++++++++ .../javawebinar/topjava/web/UserServlet.java | 6 +-- src/main/webapp/WEB-INF/tld/functions.tld | 16 +++++++ src/main/webapp/WEB-INF/web.xml | 10 ++++ src/main/webapp/index.html | 3 +- src/main/webapp/meals.jsp | 48 +++++++++++++++++++ 12 files changed, 143 insertions(+), 27 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/meals.jsp diff --git a/pom.xml b/pom.xml index 75660f0776f1..2378d6d7000c 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,12 @@ 4.0.1 provided + + + javax.servlet + jstl + 1.2 + diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java deleted file mode 100644 index c2f9cc618f7c..000000000000 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.javawebinar.topjava; - -/** - * @see Demo application - * @see Initial project - */ -public class Main { - public static void main(String[] args) { - System.out.format("Hello TopJava Enterprise!"); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java index 07f04f8dbb9f..800b1fcbb5c3 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -18,6 +18,22 @@ public MealTo(LocalDateTime dateTime, String description, int calories, boolean this.excess = excess; } + 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{" + 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..3f23f83fde65 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,18 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; + } + + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } +} + diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 27574da3d57f..c612625af7db 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import java.util.stream.Collectors; public class MealsUtil { @@ -29,7 +30,7 @@ public static void main(String[] args) { mealsTo.forEach(System.out::println); } - public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + public static List filterByPredicate(List meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -37,7 +38,7 @@ public static List filteredByStreams(List meals, LocalTime startTi ); return meals.stream() - .filter(meal -> TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .filter(filter) .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) .collect(Collectors.toList()); } diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java deleted file mode 100644 index 0ebfdb5fcdcb..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.javawebinar.topjava.util; - -import java.time.LocalTime; - -public class TimeUtil { - public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java new file mode 100644 index 000000000000..d7ade99e5c92 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.util.MealsUtil; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class MealServlet extends HttpServlet { + private static final Logger log = LoggerFactory.getLogger(MealServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.info("getAll"); + request.setAttribute("meals", MealsUtil.getTos(MealsUtil.meals, MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index ef52d67576c0..f6cf12e69976 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -15,9 +15,7 @@ public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.debug("redirect to users"); - -// request.getRequestDispatcher("/users.jsp").forward(request, response); - response.sendRedirect("users.jsp"); + log.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); } } 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 index c63810c43593..bd98d3bf3f6a 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -16,4 +16,14 @@ /users + + mealServlet + ru.javawebinar.topjava.web.MealServlet + 0 + + + mealServlet + /meals + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 58d8d5ab6aec..714683bd3856 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -6,8 +6,9 @@

Проект Java Enterprise (Topjava)


-
    + diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 000000000000..3ed8945ffc77 --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,48 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> +<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> + + + Meal list + + + +
    +

    Home

    +
    +

    Meals

    + + + + + + + + + + + + + + + + +
    DateDescriptionCalories
    + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}
    +
    + + \ No newline at end of file From bd3515de3d914bf612495b65c00306121f18606c Mon Sep 17 00:00:00 2001 From: login Date: Thu, 7 Oct 2021 12:54:17 +0400 Subject: [PATCH 070/127] 2 1 HW1 --- .../javawebinar/topjava/util/MealsUtil.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c612625af7db..5591fc7307bc 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -15,19 +15,24 @@ import java.util.stream.Collectors; public class MealsUtil { - public static void main(String[] args) { - List meals = Arrays.asList( - new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) - ); - - List mealsTo = filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsTo.forEach(System.out::println); + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + + public static final List meals = Arrays.asList( + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); + + public static List getTos(List meals, int caloriesPerDay) { + return filterByPredicate(meals, caloriesPerDay, meal -> true); + } + + public static List getFilteredTos(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return filterByPredicate(meals, caloriesPerDay, meal -> DateTimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)); } public static List filterByPredicate(List meals, int caloriesPerDay, Predicate filter) { @@ -52,7 +57,7 @@ public static List getFilteredMealsWithExceded(List mealLi Collectors.summingInt(Meal::getCalories))); return mealList.stream() - .filter(m->TimeUtil.isBetweenHalfOpen(m.getDateTime().toLocalTime(), startTime, endTime)) + .filter(m->DateTimeUtil.isBetweenHalfOpen(m.getDateTime().toLocalTime(), startTime, endTime)) .map(m->new MealWithExcess(m.getDateTime(), m.getDescription(), m.getCalories(), caloriesSumByDate.get(m.getDateTime().toLocalDate())>caloriesPerDay)) .collect(Collectors.toList()); From 98794b745fa70ec5298363723552a699fd022da1 Mon Sep 17 00:00:00 2001 From: login Date: Thu, 7 Oct 2021 13:03:18 +0400 Subject: [PATCH 071/127] 2 2 HW1 optional --- .../ru/javawebinar/topjava/model/Meal.java | 29 +++++++++ .../ru/javawebinar/topjava/model/MealTo.java | 12 +++- .../repository/InMemoryMealRepository.java | 45 ++++++++++++++ .../topjava/repository/MealRepository.java | 18 ++++++ .../javawebinar/topjava/util/MealsUtil.java | 9 +-- .../javawebinar/topjava/web/MealServlet.java | 61 ++++++++++++++++++- src/main/webapp/mealForm.jsp | 51 ++++++++++++++++ src/main/webapp/meals.jsp | 6 ++ 8 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/MealRepository.java create mode 100644 src/main/webapp/mealForm.jsp diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 943ff5cd59fa..3abbee42511e 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,6 +5,8 @@ import java.time.LocalTime; public class Meal { + private Integer id; + private final LocalDateTime dateTime; private final String description; @@ -12,11 +14,24 @@ public class Meal { private final int calories; public Meal(LocalDateTime dateTime, String description, int calories) { + this(null, dateTime, description, calories); + } + + public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -36,4 +51,18 @@ public LocalDate getDate() { public LocalTime getTime() { return dateTime.toLocalTime(); } + + public boolean isNew() { + return id == null; + } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java index 800b1fcbb5c3..01b3a5fdaad1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -3,6 +3,8 @@ import java.time.LocalDateTime; public class MealTo { + private final Integer id; + private final LocalDateTime dateTime; private final String description; @@ -11,13 +13,18 @@ public class MealTo { private final boolean excess; - public MealTo(LocalDateTime dateTime, String description, int calories, 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; } @@ -37,7 +44,8 @@ public boolean isExcess() { @Override public String toString() { return "MealTo{" + - "dateTime=" + dateTime + + "id=" + id + + ", dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + ", excess=" + excess + diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java new file mode 100644 index 000000000000..ad6669056a4b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class InMemoryMealRepository implements MealRepository { + private final Map repository = new ConcurrentHashMap<>(); + private final AtomicInteger counter = new AtomicInteger(0); + + { + MealsUtil.meals.forEach(this::save); + } + + @Override + public Meal save(Meal meal) { + if (meal.isNew()) { + meal.setId(counter.incrementAndGet()); + repository.put(meal.getId(), meal); + return meal; + } + // handle case: update, but not present in storage + return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + } + + @Override + public boolean delete(int id) { + return repository.remove(id) != null; + } + + @Override + public Meal get(int id) { + return repository.get(id); + } + + @Override + public Collection getAll() { + return repository.values(); + } +} + 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..2cb2aef8ba8d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -0,0 +1,18 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; + +import java.util.Collection; + +public interface MealRepository { + // null if not found, when updated + Meal save(Meal meal); + + // false if not found + boolean delete(int id); + + // null if not found + Meal get(int id); + + Collection getAll(); +} diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 5591fc7307bc..b070ddfcceb1 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -9,6 +9,7 @@ import java.time.LocalTime; import java.time.Month; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -27,15 +28,15 @@ public class MealsUtil { new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) ); - public static List getTos(List meals, int caloriesPerDay) { + public static List getTos(Collection meals, int caloriesPerDay) { return filterByPredicate(meals, caloriesPerDay, meal -> true); } - public static List getFilteredTos(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + public static List getFilteredTos(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { return filterByPredicate(meals, caloriesPerDay, meal -> DateTimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)); } - public static List filterByPredicate(List meals, int caloriesPerDay, Predicate filter) { + public static List filterByPredicate(Collection meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -49,7 +50,7 @@ public static List filterByPredicate(List meals, int caloriesPerDa } private static MealTo createTo(Meal meal, boolean excess) { - return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); } public static List getFilteredMealsWithExceded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index d7ade99e5c92..d59c63f3064d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,6 +2,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.InMemoryMealRepository; +import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.ServletException; @@ -9,14 +12,66 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; public class MealServlet extends HttpServlet { private static final Logger log = LoggerFactory.getLogger(MealServlet.class); + private MealRepository repository; + + @Override + public void init() { + repository = new InMemoryMealRepository(); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String id = request.getParameter("id"); + + Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); + + log.info(meal.isNew() ? "Create {}" : "Update {}", meal); + repository.save(meal); + response.sendRedirect("meals"); + } + @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.info("getAll"); - request.setAttribute("meals", MealsUtil.getTos(MealsUtil.meals, MealsUtil.DEFAULT_CALORIES_PER_DAY)); - request.getRequestDispatcher("/meals.jsp").forward(request, response); + String action = request.getParameter("action"); + + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); + log.info("Delete {}", id); + repository.delete(id); + response.sendRedirect("meals"); + break; + case "create": + case "update": + final Meal meal = "create".equals(action) ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : + repository.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); + break; + case "all": + default: + log.info("getAll"); + request.setAttribute("meals", + MealsUtil.getTos(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + 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/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp new file mode 100644 index 000000000000..018300082f0e --- /dev/null +++ b/src/main/webapp/mealForm.jsp @@ -0,0 +1,51 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
    +

    Home

    +
    +

    ${param.action == 'create' ? 'Create meal' : 'Edit meal'}

    + +
    + +
    +
    DateTime:
    +
    +
    +
    +
    Description:
    +
    +
    +
    +
    Calories:
    +
    +
    + + +
    +
    + + diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 3ed8945ffc77..b90cadc9f2f2 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -21,12 +21,16 @@

    Home


    Meals

    + Add Meal +

    + + @@ -40,6 +44,8 @@ + +
    Date Description Calories
    ${meal.description} ${meal.calories}UpdateDelete
    From 25075b815cbfa32c552228b23602afa0502a2f43 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 16 Nov 2021 14:26:08 +0400 Subject: [PATCH 072/127] 2 3 app layers --- .../topjava/model/AbstractBaseEntity.java | 26 ++++++ .../topjava/model/AbstractNamedEntity.java | 24 +++++ .../ru/javawebinar/topjava/model/Role.java | 6 ++ .../ru/javawebinar/topjava/model/User.java | 91 +++++++++++++++++++ .../topjava/repository/MealRepository.java | 7 +- .../topjava/repository/UserRepository.java | 21 +++++ .../topjava/service/MealService.java | 9 ++ .../topjava/service/UserService.java | 38 ++++++++ .../topjava/util/ValidationUtil.java | 43 +++++++++ .../util/exception/NotFoundException.java | 7 ++ .../javawebinar/topjava/web/SecurityUtil.java | 14 +++ .../topjava/web/meal/MealRestController.java | 8 ++ .../web/user/AbstractUserController.java | 49 ++++++++++ .../topjava/web/user/AdminRestController.java | 38 ++++++++ .../web/user/ProfileRestController.java | 20 ++++ 15 files changed, 398 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/Role.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/User.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserService.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java 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..8f27c902e047 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractBaseEntity { + protected Integer id; + + protected AbstractBaseEntity(Integer id) { + this.id = id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public boolean isNew() { + return this.id == null; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":" + 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..2054a3d3cf9b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractNamedEntity extends AbstractBaseEntity { + + protected String name; + + 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/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..d88e381945d8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,91 @@ +package ru.javawebinar.topjava.model; + +import java.util.Date; +import java.util.EnumSet; +import java.util.Set; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class User extends AbstractNamedEntity { + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + } + + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + super(id, name); + this.email = email; + this.password = password; + this.caloriesPerDay = caloriesPerDay; + this.enabled = enabled; + this.roles = 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; + } + + @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 index 2cb2aef8ba8d..14b937560b55 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -5,14 +5,15 @@ import java.util.Collection; public interface MealRepository { - // null if not found, when updated + // null if updated meal do not belong to userId Meal save(Meal meal); - // false if not found + // false if meal do not belong to userId boolean delete(int id); - // null if not found + // null if meal do not belong to userId Meal get(int id); + // ORDERED dateTime desc Collection getAll(); } 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/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 000000000000..0dc4a43c394f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.repository.MealRepository; + +public class MealService { + + private MealRepository repository; + +} \ 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..b02082e352c4 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.service; + +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; + +public class UserService { + + private UserRepository repository; + + public User create(User user) { + 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) { + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + + public List getAll() { + return repository.getAll(); + } + + public void update(User user) { + checkNotFoundWithId(repository.save(user), user.getId()); + } +} \ 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..971eb9c0c72b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.util; + + +import ru.javawebinar.topjava.model.AbstractBaseEntity; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +public class 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.getId() != 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/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java new file mode 100644 index 000000000000..e78a4b284a9a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -0,0 +1,14 @@ +package ru.javawebinar.topjava.web; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class SecurityUtil { + + public static int authUserId() { + return 1; + } + + 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/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java new file mode 100644 index 000000000000..ab4e8ea8bb8e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.web.meal; + +import ru.javawebinar.topjava.service.MealService; + +public class MealRestController { + private MealService service; + +} \ 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..16a68259ec20 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,49 @@ +package ru.javawebinar.topjava.web.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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()); + + 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..ae3374468e41 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +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..b5062e20bc90 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; + +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 From d3f8cd3cc9c7dac3082cd69734b4bf6d966d3449 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 16 Nov 2021 14:26:37 +0400 Subject: [PATCH 073/127] 2 4 add spring --- pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 2378d6d7000c..bd339c3c9c50 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 5.3.8 + 1.2.3 1.7.30 @@ -53,6 +55,13 @@ runtime + + + org.springframework + spring-context + ${spring.version} + + javax.servlet From 0c24e469659dae50a19628b9cf04b5ab51a1aa1d Mon Sep 17 00:00:00 2001 From: login Date: Tue, 16 Nov 2021 14:26:55 +0400 Subject: [PATCH 074/127] 2 6 dependency injection --- .../java/ru/javawebinar/topjava/service/UserService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index b02082e352c4..13d052611980 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -10,7 +10,11 @@ public class UserService { - private UserRepository repository; + private final UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } public User create(User user) { return repository.save(user); From 8927d28c1ea51b384829a077655b040ad551983c Mon Sep 17 00:00:00 2001 From: login Date: Tue, 16 Nov 2021 14:27:10 +0400 Subject: [PATCH 075/127] 2 7 annotation processing --- src/main/java/ru/javawebinar/topjava/service/UserService.java | 2 ++ .../ru/javawebinar/topjava/web/user/AbstractUserController.java | 2 ++ .../ru/javawebinar/topjava/web/user/AdminRestController.java | 2 ++ .../ru/javawebinar/topjava/web/user/ProfileRestController.java | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 13d052611980..8fbe8dc0601f 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.service; +import org.springframework.stereotype.Service; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; @@ -8,6 +9,7 @@ import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; +@Service public class UserService { private final UserRepository repository; diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index 16a68259ec20..0000f1c1e02f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -2,6 +2,7 @@ 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; @@ -13,6 +14,7 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); + @Autowired private UserService service; public List getAll() { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index ae3374468e41..b37a8ed6c8a5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,9 +1,11 @@ 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 diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index b5062e20bc90..7d3702c31c46 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,9 +1,11 @@ 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() { From 0f6dd52d1bc7e90ae3334a5e507a0198adcd190e Mon Sep 17 00:00:00 2001 From: login Date: Sat, 20 Nov 2021 21:22:57 +0400 Subject: [PATCH 076/127] 21HW1 rollback --- .../ru/javawebinar/topjava/model/User.java | 6 ++ .../topjava/repository/MealRepository.java | 2 + .../topjava/repository/UserRepository.java | 1 + .../inmemory/InMemoryMealRepository.java | 93 +++++++++++++++++++ .../topjava/service/UserService.java | 3 +- .../topjava/{model => to}/MealTo.java | 2 +- .../ru/javawebinar/topjava/to/UserTo.java | 76 +++++++++++++++ .../topjava/util/DateTimeUtil.java | 9 ++ .../javawebinar/topjava/util/MealsUtil.java | 26 +++--- .../javawebinar/topjava/util/UsersUtil.java | 39 ++++++++ .../web/user/AbstractUserController.java | 3 +- src/main/webapp/meals.jsp | 2 +- 12 files changed, 246 insertions(+), 16 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java rename src/main/java/ru/javawebinar/topjava/{model => to}/MealTo.java (96%) create mode 100644 src/main/java/ru/javawebinar/topjava/to/UserTo.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/UsersUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index d88e381945d8..f949ed39efeb 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -8,6 +8,8 @@ public class User extends AbstractNamedEntity { + private Integer id; + private String email; private String password; @@ -77,6 +79,10 @@ public String getPassword() { return password; } + public boolean isNew() { + return id == null; + } + @Override public String toString() { return "User (" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index 14b937560b55..236636ae8fc8 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -5,6 +5,8 @@ import java.util.Collection; public interface MealRepository { + + // null if updated meal do not belong to userId Meal save(Meal meal); diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java index 138369789175..5a7fa946d5b0 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -2,6 +2,7 @@ import ru.javawebinar.topjava.model.User; +import java.util.Collection; import java.util.List; public interface UserRepository { diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java new file mode 100644 index 000000000000..65e1385ad620 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java @@ -0,0 +1,93 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.springframework.stereotype.Repository; +import org.springframework.util.CollectionUtils; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.Util; + +import java.util.Collection; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.ADMIN_ID; +import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.USER_ID; + +@Repository +public class InMemoryMealRepository implements MealRepository { + private final Map repository = new ConcurrentHashMap<>(); + + // Map userId -> (mealId-> meal) + private final Map> usersMealsMap = new ConcurrentHashMap<>(); + private final AtomicInteger counter = new AtomicInteger(0); + + { +// MealsUtil.meals.forEach(this::save); + MealsUtil.meals.forEach(meal -> save(meal, USER_ID)); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); + } + + + @Override + public Meal save(Meal meal, int userId) { + Map meals = usersMealsMap.computeIfAbsent(userId, ConcurrentHashMap::new); + if (meal.isNew()) { + meal.setId(counter.incrementAndGet()); + repository.put(meal.getId(), meal); + meals.put(meal.getId(), meal); + return meal; + } + // handle case: update, but not present in storage + return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + } + + @Override + public boolean delete(int id, int userId) { + Map meals = usersMealsMap.get(userId); + return meals != null && meals.remove(id) != null; + } + + @Override + public Meal get(int id, int userId) { + Map meals = usersMealsMap.get(userId); + return meals == null ? null : meals.get(id); + } + + @Override + public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return filterByPredicate(userId, meal -> Util.isBetweenHalfOpen(meal.getDateTime(), startDateTime, endDateTime)); + } + +// @Override +// public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { +// return filterByPredicate(userId, meal -> Util.isBetweenHalfOpen(meal.getDateTime(), startDateTime, endDateTime)); +// } + + @Override + + public List getAll(int userId) { + return filterByPredicate(userId, meal -> true); + } + + private List filterByPredicate(int userId, Predicate filter) { + Map meals = usersMealsMap.get(userId); + return CollectionUtils.isEmpty(meals) ? Collections.emptyList() : + meals.values().stream() + .filter(filter) + .sorted(Comparator.comparing(Meal::getDateTime).reversed()) + .collect(Collectors.toList()); + } + +} + + diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 8fbe8dc0601f..78ad36680ca0 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -4,6 +4,7 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; +import java.util.Collection; import java.util.List; import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; @@ -34,7 +35,7 @@ public User getByEmail(String email) { return checkNotFound(repository.getByEmail(email), "email=" + email); } - public List getAll() { + public Collection getAll() { return repository.getAll(); } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java similarity index 96% rename from src/main/java/ru/javawebinar/topjava/model/MealTo.java rename to src/main/java/ru/javawebinar/topjava/to/MealTo.java index 01b3a5fdaad1..d14feae792b7 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -1,4 +1,4 @@ -package ru.javawebinar.topjava.model; +package ru.javawebinar.topjava.to; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java new file mode 100644 index 000000000000..bcb8f19d49a7 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -0,0 +1,76 @@ +package ru.javawebinar.topjava.to; + +import ru.javawebinar.topjava.model.Role; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.Set; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class UserTo { + private Integer id; + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public UserTo(Integer id, String email, String password, boolean enabled, Date registered, Set roles, int caloriesPerDay) { + this.id = id; + this.email = email; + this.password = password; + this.enabled = enabled; + this.registered = registered; + this.roles = roles; + this.caloriesPerDay = caloriesPerDay; + } + + public Integer getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public boolean isEnabled() { + return enabled; + } + + public Date getRegistered() { + return registered; + } + + public Set getRoles() { + return roles; + } + + public int getCaloriesPerDay() { + return caloriesPerDay; + } + + @Override + public String toString() { + return "UserTo{" + + "id=" + id + + ", email=" + email + + ", enabled='" + enabled + '\'' + + ", registered=" + registered + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + + '}'; + } + +} diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 3f23f83fde65..b476c2842654 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -14,5 +14,14 @@ public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, Local public static String toString(LocalDateTime ldt) { return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); } + + public static LocalDateTime getStartInclusive(LocalDate localDate) { + return localDate != null ? localDate.atStartOfDay() : MIN_DATE; + } + + public static LocalDateTime getEndInclusive(LocalDate localDate) { + return localDate != null ? localDate.plus(1, ChronoUnit.DAYS).atStartOfDay() : MAX_DATE; + } + } diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index b070ddfcceb1..4b61d8ad87ea 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -1,8 +1,9 @@ package ru.javawebinar.topjava.util; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.model.MealTo; +import ru.javawebinar.topjava.to.MealTo; import ru.javawebinar.topjava.model.MealWithExcess; +import ru.javawebinar.topjava.web.meal.MealRestController; import java.time.LocalDate; import java.time.LocalDateTime; @@ -53,17 +54,18 @@ private static MealTo createTo(Meal meal, boolean excess) { return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); } - public static List getFilteredMealsWithExceded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - final Map caloriesSumByDate = mealList.stream().collect(Collectors.groupingBy(m -> m.getDateTime().toLocalDate(), - Collectors.summingInt(Meal::getCalories))); - - return mealList.stream() - .filter(m->DateTimeUtil.isBetweenHalfOpen(m.getDateTime().toLocalTime(), startTime, endTime)) - .map(m->new MealWithExcess(m.getDateTime(), m.getDescription(), m.getCalories(), - caloriesSumByDate.get(m.getDateTime().toLocalDate())>caloriesPerDay)) - .collect(Collectors.toList()); - - } +// public static List getFilteredMealsWithExceded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +// +// final Map caloriesSumByDate = mealList.stream().collect(Collectors.groupingBy(m -> m.getDateTime().toLocalDate(), +// Collectors.summingInt(Meal::getCalories))); +// +// return mealList.stream() +// .filter(m-> MealRestController.isBetween(m.getDateTime().toLocalTime(), startTime, endTime)) +// .map(m->new MealWithExcess(m.getDateTime(), m.getDescription(), m.getCalories(), +// caloriesSumByDate.get(m.getDateTime().toLocalDate())>caloriesPerDay)) +// .collect(Collectors.toList()); +// +// } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UsersUtil.java b/src/main/java/ru/javawebinar/topjava/util/UsersUtil.java new file mode 100644 index 000000000000..61db1dec031c --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/UsersUtil.java @@ -0,0 +1,39 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.*; +import ru.javawebinar.topjava.to.UserTo; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class UsersUtil { + public static final List users = Arrays.asList( + new User(0, "admin", "admin@anklav.ru", "1", Role.ADMIN), + new User(1, "Ivan", "Ivan@anklav.ru", "1", Role.USER), + new User(2, "Anton", "Anton@anklav.ru", "1", Role.USER), + new User(3, "Sasha", "Sasha@anklav.ru", "1", Role.USER) + ); + + public static List getTos(Collection users) { + return filterByPredicate(users, user -> true); + } + + public static List getFilteredTos(Collection users) { + return filterByPredicate(users, user -> true); + } + + public static List filterByPredicate(Collection users, Predicate filter) { + + return users.stream() + .filter(filter) + .map(user -> createTo(user)) + .collect(Collectors.toList()); + } + + private static UserTo createTo(User user) { + return new UserTo(user.getId(), user.getEmail(), user.getPassword(), user.isEnabled(), user.getRegistered(), user.getRoles(), user.getCaloriesPerDay()); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index 0000f1c1e02f..bbbc00742939 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -7,6 +7,7 @@ import ru.javawebinar.topjava.service.UserService; import java.util.List; +import java.util.stream.Collectors; import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; @@ -19,7 +20,7 @@ public abstract class AbstractUserController { public List getAll() { log.info("getAll"); - return service.getAll(); + return service.getAll().stream().collect(Collectors.toList()); } public User get(int id) { diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index b90cadc9f2f2..6310069cc863 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -34,7 +34,7 @@ - + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> From 05f7dcb733d74aa6d2d20307b81328b1a206e790 Mon Sep 17 00:00:00 2001 From: login Date: Sat, 20 Nov 2021 21:23:09 +0400 Subject: [PATCH 077/127] 2 5 add spring context --- .../ru/javawebinar/topjava/SpringMain.java | 36 ++++++++++++ .../repository/InMemoryMealRepository.java | 45 --------------- .../inmemory/InMemoryUserRepository.java | 57 +++++++++++++++++++ .../javawebinar/topjava/web/MealServlet.java | 2 +- src/main/resources/spring/spring-app.xml | 18 ++++++ 5 files changed, 112 insertions(+), 46 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.java delete mode 100644 src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java create mode 100644 src/main/resources/spring/spring-app.xml diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java new file mode 100644 index 000000000000..b869d1c6e382 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,36 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.web.meal.MealRestController; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +public class SpringMain { + public static void main(String[] args) { + // java 7 automatic resource management (ARM) + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ADMIN)); + System.out.println(); + + MealRestController mealController = appCtx.getBean(MealRestController.class); + List filteredMealsWithExcess = + mealController.getBetween( + LocalDate.of(2020, Month.JANUARY, 30), LocalTime.of(7, 0), + LocalDate.of(2020, Month.JANUARY, 31), LocalTime.of(11, 0)); + filteredMealsWithExcess.forEach(System.out::println); + System.out.println(); + System.out.println(mealController.getBetween(null, null, null, null)); + } + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java deleted file mode 100644 index ad6669056a4b..000000000000 --- a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.javawebinar.topjava.repository; - -import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.util.MealsUtil; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -public class InMemoryMealRepository implements MealRepository { - private final Map repository = new ConcurrentHashMap<>(); - private final AtomicInteger counter = new AtomicInteger(0); - - { - MealsUtil.meals.forEach(this::save); - } - - @Override - public Meal save(Meal meal) { - if (meal.isNew()) { - meal.setId(counter.incrementAndGet()); - repository.put(meal.getId(), meal); - return meal; - } - // handle case: update, but not present in storage - return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); - } - - @Override - public boolean delete(int id) { - return repository.remove(id) != null; - } - - @Override - public Meal get(int id) { - return repository.get(id); - } - - @Override - public Collection getAll() { - return repository.values(); - } -} - diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java new file mode 100644 index 000000000000..3565868cc632 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java @@ -0,0 +1,57 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Repository +public class InMemoryUserRepository implements UserRepository { + + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + private final Map usersMap = new ConcurrentHashMap<>(); + private final AtomicInteger counter = new AtomicInteger(0); + + @Override + public User save(User user) { + if (user.isNew()) { + user.setId(counter.incrementAndGet()); + usersMap.put(user.getId(), user); + return user; + } + return usersMap.computeIfPresent(user.getId(), (id, oldUser) -> user); + } + + @Override + public boolean delete(int id) { + return usersMap.remove(id) != null; + } + + @Override + public User get(int id) { + return usersMap.get(id); + } + + @Override + public List getAll() { + return usersMap.values().stream() + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { + return usersMap.values().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() + .orElse(null); + } +} \ 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 index d59c63f3064d..f3299023724d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -3,8 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.InMemoryMealRepository; import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.inmemory.InMemoryMealRepository; import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.ServletException; diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..cac42ba13e3d --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file From e7d9b9d69e1018b9b68cd01f56828b718a31a374 Mon Sep 17 00:00:00 2001 From: login Date: Sat, 20 Nov 2021 21:23:22 +0400 Subject: [PATCH 078/127] 3 0 fix --- src/main/webapp/mealForm.jsp | 2 +- src/main/webapp/meals.jsp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp index 018300082f0e..28f140b65fce 100644 --- a/src/main/webapp/mealForm.jsp +++ b/src/main/webapp/mealForm.jsp @@ -1,4 +1,4 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page contentType="text/html;charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 6310069cc863..5f239ba98163 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -1,4 +1,4 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page contentType="text/html;charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> From 98354baef87bd52ef1c7219a12b512483950d474 Mon Sep 17 00:00:00 2001 From: login Date: Sat, 20 Nov 2021 21:23:34 +0400 Subject: [PATCH 079/127] 3 01 HW2 repositories --- .../topjava/repository/MealRepository.java | 14 +++++++++----- .../ru/javawebinar/topjava/web/MealServlet.java | 8 ++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index 236636ae8fc8..61aeebc9868a 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -2,20 +2,24 @@ import ru.javawebinar.topjava.model.Meal; -import java.util.Collection; +import java.time.LocalDateTime; +import java.util.List; public interface MealRepository { // null if updated meal do not belong to userId - Meal save(Meal meal); + Meal save(Meal meal, int userId); // false if meal do not belong to userId - boolean delete(int id); + boolean delete(int id, int userId); // null if meal do not belong to userId - Meal get(int id); + Meal get(int id, int userId); // ORDERED dateTime desc - Collection getAll(); + List getAll(int userId); + + // ORDERED dateTime desc + List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index f3299023724d..6f6ac9985b96 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -37,7 +37,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Integer.parseInt(request.getParameter("calories"))); log.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal); + repository.save(meal, SecurityUtil.authUserId()); response.sendRedirect("meals"); } @@ -49,14 +49,14 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t case "delete": int id = getId(request); log.info("Delete {}", id); - repository.delete(id); + repository.delete(id, SecurityUtil.authUserId()); response.sendRedirect("meals"); break; case "create": case "update": final Meal meal = "create".equals(action) ? new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : - repository.get(getId(request)); + repository.get(getId(request), SecurityUtil.authUserId()); request.setAttribute("meal", meal); request.getRequestDispatcher("/mealForm.jsp").forward(request, response); break; @@ -64,7 +64,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t default: log.info("getAll"); request.setAttribute("meals", - MealsUtil.getTos(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + MealsUtil.getTos(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } From 3987e87dd609adcc7d9e4df5b20d990802dcbe1a Mon Sep 17 00:00:00 2001 From: login Date: Sat, 20 Nov 2021 21:23:47 +0400 Subject: [PATCH 080/127] 3 02 HW2 repo filters --- .../java/ru/javawebinar/topjava/util/DateTimeUtil.java | 6 ------ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java | 2 +- src/main/java/ru/javawebinar/topjava/util/Util.java | 9 +++++++++ 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/Util.java diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index b476c2842654..b17fa0be98c0 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,16 +1,11 @@ package ru.javawebinar.topjava.util; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class DateTimeUtil { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; - } - public static String toString(LocalDateTime ldt) { return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); } @@ -24,4 +19,3 @@ public static LocalDateTime getEndInclusive(LocalDate localDate) { } } - diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 4b61d8ad87ea..3f61944a0ec2 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -34,7 +34,7 @@ public static List getTos(Collection meals, int caloriesPerDay) { } public static List getFilteredTos(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { - return filterByPredicate(meals, caloriesPerDay, meal -> DateTimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)); + return filterByPredicate(meals, caloriesPerDay, meal -> Util.isBetweenHalfOpen(meal.getTime(), startTime, endTime)); } public static List filterByPredicate(Collection meals, int caloriesPerDay, Predicate filter) { 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..a17a6927f975 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.util; + +import org.springframework.lang.Nullable; + +public class 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 From f60af60b5b58bf4bdbeebf3df5607ed5f9cf131a Mon Sep 17 00:00:00 2001 From: login Date: Sat, 20 Nov 2021 21:24:01 +0400 Subject: [PATCH 081/127] 3 03 HW2 meal layers --- .../ru/javawebinar/topjava/model/Meal.java | 18 +---- .../topjava/service/MealService.java | 40 ++++++++++- .../topjava/util/DateTimeUtil.java | 14 ++++ .../topjava/web/meal/MealRestController.java | 70 ++++++++++++++++++- 4 files changed, 124 insertions(+), 18 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 3abbee42511e..9eed15f706be 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -4,9 +4,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; -public class Meal { - private Integer id; - +public class Meal extends AbstractBaseEntity { private final LocalDateTime dateTime; private final String description; @@ -18,20 +16,12 @@ public Meal(LocalDateTime dateTime, String description, int calories) { } public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { - this.id = id; + super(id); this.dateTime = dateTime; this.description = description; this.calories = calories; } - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - public LocalDateTime getDateTime() { return dateTime; } @@ -52,10 +42,6 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } - public boolean isNew() { - return id == null; - } - @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index 0dc4a43c394f..7957a0895d47 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -1,9 +1,47 @@ package ru.javawebinar.topjava.service; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Service; +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 MealRepository repository; + 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) { + checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + public Meal create(Meal meal, int userId) { + return repository.save(meal, userId); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index b17fa0be98c0..40ab4cbaeb57 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,11 +1,25 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDate; import java.time.LocalDateTime; 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); + + 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); } diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index ab4e8ea8bb8e..2f26c81fafeb 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,8 +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 MealService service; + 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(); + log.info("create {} for user {}", meal, userId); + checkNew(meal); + return service.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = SecurityUtil.authUserId(); + log.info("update {} for user {}", meal, userId); + assureIdConsistent(meal, id); + service.update(meal, userId); + } + + /** + *
      Filter separately + *
    1. by date
    2. + *
    3. by time for every date
    4. + *
    + */ + 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 From 3e6c6336c7f1fe200a194ddc33391c7a6d1f46f4 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:04:58 +0400 Subject: [PATCH 082/127] 1 0 rename --- .../model/{UserMeal.java => Meal.java} | 9 +- .../javawebinar/topjava/util/MealsUtil.java | 378 ++++++++++++++++++ .../topjava/util/UserMealsUtil.java | 39 -- 3 files changed, 385 insertions(+), 41 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{UserMeal.java => Meal.java} (63%) create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java similarity index 63% rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java rename to src/main/java/ru/javawebinar/topjava/model/Meal.java index d8f91b127f6a..b15415aa11b3 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -2,14 +2,19 @@ import java.time.LocalDateTime; -public class UserMeal { +public class Meal extends AbstractBaseEntity { private final LocalDateTime dateTime; private final String description; private final int calories; - public UserMeal(LocalDateTime dateTime, String description, int calories) { + 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; 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..c04a2eca35eb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,378 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.to.MealTo; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.function.Predicate; +import java.util.Collection; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; +import static ru.javawebinar.topjava.util.TimeUtil.isBetweenHalfOpen; + +public class MealsUtil { + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + + public static final List meals = Arrays.asList( + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); + + public static void main(String[] args) throws InterruptedException { + + final LocalTime startTime = LocalTime.of(7, 0); + final LocalTime endTime = LocalTime.of(12, 0); + + List mealsTo = filteredByStreams(meals, startTime, endTime, 2000); + mealsTo.forEach(System.out::println); + + System.out.println(filteredByCycles(meals, startTime, endTime, 2000)); + +// Optional2 recursion + System.out.println(filteredByRecursion(meals, startTime, endTime, 2000)); + System.out.println(filteredBySetterRecursion(meals, startTime, endTime, 2000)); + +// Optional2 reference type +// System.out.println(filteredByAtomic(meals, startTime, endTime, 2000)); // or boolean[1] +// System.out.println(filteredByReflection(meals, startTime, endTime, 2000)); + +// Optional2 delayed execution +// System.out.println(filteredByClosure(meals, startTime, endTime, 2000)); + System.out.println(filteredByExecutor(meals, startTime, endTime, 2000)); + System.out.println(filteredByLock(meals, startTime, endTime, 2000)); + System.out.println(filteredByCountDownLatch(meals, startTime, endTime, 2000)); + System.out.println(filteredByPredicate(meals, startTime, endTime, 2000)); + System.out.println(filteredByConsumerChain(meals, startTime, endTime, 2000)); + +// Optional2 streams + System.out.println(filteredByFlatMap(meals, startTime, endTime, 2000)); + System.out.println(filteredByCollector(meals, startTime, endTime, 2000)); + } + + public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + 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(meal -> isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } + + public static List filterByPredicate(Collection meals, int caloriesPerDay, Predicate filter) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) + + ); + + return meals.stream() + .filter(filter) + .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } + + 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 filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + + final Map caloriesSumByDate = new HashMap<>(); + meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); + + final List mealsTo = new ArrayList<>(); + meals.forEach(meal -> { + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + }); + return mealsTo; + } + + private static List filteredByRecursion(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + ArrayList result = new ArrayList<>(); + filterWithRecursion(new LinkedList<>(meals), startTime, endTime, caloriesPerDay, new HashMap<>(), result); + return result; + } + + private static void filterWithRecursion(LinkedList meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay, + Map dailyCaloriesMap, List result) { + if (meals.isEmpty()) return; + + Meal meal = meals.pop(); + dailyCaloriesMap.merge(meal.getDate(), meal.getCalories(), Integer::sum); + filterWithRecursion(meals, startTime, endTime, caloriesPerDay, dailyCaloriesMap, result); + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + result.add(createTo(meal, dailyCaloriesMap.get(meal.getDate()) > caloriesPerDay)); + } + } + + private static List filteredBySetterRecursion(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + class MealNode { + private final MealNode prev; + private final MealTo mealTo; + + public MealNode(MealTo mealTo, MealNode prev) { + this.mealTo = mealTo; + this.prev = prev; + } + + public void setExcess() { + mealTo.setExcess(true); + if (prev != null) { + prev.setExcess(); + } + } + } + + Map caloriesSumByDate = new HashMap<>(); + Map mealNodeByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + meals.forEach(meal -> { + LocalDate localDate = meal.getDate(); + boolean excess = caloriesSumByDate.merge(localDate, meal.getCalories(), Integer::sum) > caloriesPerDay; + if (TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + MealTo mealTo = createTo(meal, excess); + mealsTo.add(mealTo); + if (!excess) { + MealNode prevNode = mealNodeByDate.get(localDate); + mealNodeByDate.put(localDate, new MealNode(mealTo, prevNode)); + } + } + if (excess) { + MealNode mealNode = mealNodeByDate.remove(localDate); + if (mealNode != null) { + // recursive set for all interval day meals + mealNode.setExcess(); + } + } + }); + return mealsTo; + } + + /* + private static List filteredByAtomic(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = new HashMap<>(); + Map exceededMap = new HashMap<>(); + + List mealsTo = new ArrayList<>(); + meals.forEach(meal -> { + AtomicBoolean wrapBoolean = exceededMap.computeIfAbsent(meal.getDate(), date -> new AtomicBoolean()); + Integer dailyCalories = caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (dailyCalories > caloriesPerDay) { + wrapBoolean.set(true); + } + if (isBetween(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, wrapBoolean)); // also change createTo and MealTo.excess + } + }); + return mealsTo; + } + + private static List filteredByReflection(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws NoSuchFieldException, IllegalAccessException { + Map caloriesSumByDate = new HashMap<>(); + Map exceededMap = new HashMap<>(); + Field field = Boolean.class.getDeclaredField("value"); + field.setAccessible(true); + + List mealsTo = new ArrayList<>(); + for (Meal meal : meals) { + Boolean mutableBoolean = exceededMap.computeIfAbsent(meal.getDate(), date -> new Boolean(false)); + Integer dailyCalories = caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (dailyCalories > caloriesPerDay) { + field.setBoolean(mutableBoolean, true); + } + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, mutableBoolean)); // also change createTo and MealTo.excess + } + } + return mealsTo; + } + + private static List filteredByClosure(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final Map caloriesSumByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + mealList.forEach(meal -> { + caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (isBetween(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, () -> (caloriesSumByDate.get(meal.getDate()) > caloriesPerDay))); // also change createTo and MealTo.excess + } + } + ); + return mealsTo; + } + */ + + private static List filteredByExecutor(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws InterruptedException { + Map caloriesSumByDate = new HashMap<>(); + List> tasks = new ArrayList<>(); + final List mealsTo = Collections.synchronizedList(new ArrayList<>()); + + meals.forEach(meal -> { + caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + tasks.add(() -> { + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + return null; + }); + } + }); + ExecutorService executorService = Executors.newCachedThreadPool(); + executorService.invokeAll(tasks); + executorService.shutdown(); + return mealsTo; + } + + public static List filteredByLock(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws InterruptedException { + Map caloriesSumByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + ExecutorService executor = Executors.newCachedThreadPool(); + ReentrantLock lock = new ReentrantLock(); + lock.lock(); + for (Meal meal : meals) { + caloriesSumByDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); + if (isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) + executor.submit(() -> { + lock.lock(); + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDateTime().toLocalDate()) > caloriesPerDay)); + lock.unlock(); + }); + } + lock.unlock(); + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + return mealsTo; + } + + private static List filteredByCountDownLatch(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws InterruptedException { + Map caloriesSumByDate = new HashMap<>(); + List mealsTo = Collections.synchronizedList(new ArrayList<>()); + CountDownLatch latchCycles = new CountDownLatch(meals.size()); + CountDownLatch latchTasks = new CountDownLatch(meals.size()); + for (Meal meal : meals) { + caloriesSumByDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); + if (isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) { + new Thread(() -> { + try { + latchCycles.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDateTime().toLocalDate()) > caloriesPerDay)); + latchTasks.countDown(); + }).start(); + } else { + latchTasks.countDown(); + } + latchCycles.countDown(); + } + latchTasks.await(); + return mealsTo; + } + + public static List filteredByPredicate(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + + Predicate predicate = b -> true; + for (Meal meal : meals) { + caloriesSumByDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); + if (TimeUtil.isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) { + predicate = predicate.and(b -> mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDateTime().toLocalDate()) > caloriesPerDay))); + } + } + predicate.test(true); + return mealsTo; + } + + public static List filteredByConsumerChain(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesPerDays = new HashMap<>(); + List result = new ArrayList<>(); + Consumer consumer = dummy -> {}; + + for (Meal meal : meals) { + caloriesPerDays.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + consumer = consumer.andThen(dummy -> result.add(createTo(meal, caloriesPerDays.get(meal.getDateTime().toLocalDate()) > caloriesPerDay))); + } + } + consumer.accept(null); + return result; + } + + private static List filteredByFlatMap(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Collection> list = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate)).values(); + + return list.stream() + .flatMap(dayMeals -> { + boolean excess = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; + return dayMeals.stream().filter(meal -> + isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .map(meal -> createTo(meal, excess)); + }).collect(toList()); + } + + private static List filteredByCollector(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final class Aggregate { + private final List dailyMeals = new ArrayList<>(); + private int dailySumOfCalories; + + private void accumulate(Meal meal) { + dailySumOfCalories += meal.getCalories(); + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + dailyMeals.add(meal); + } + } + + // never invoked if the upstream is sequential + private Aggregate combine(Aggregate that) { + this.dailySumOfCalories += that.dailySumOfCalories; + this.dailyMeals.addAll(that.dailyMeals); + return this; + } + + private Stream finisher() { + final boolean excess = dailySumOfCalories > caloriesPerDay; + return dailyMeals.stream().map(meal -> createTo(meal, excess)); + } + } + + Collection> values = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate, + Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) + ).values(); + + return values.stream().flatMap(identity()).collect(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/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java deleted file mode 100644 index 3c171b4a5972..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.javawebinar.topjava.util; - -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.model.UserMealWithExcess; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Month; -import java.util.Arrays; -import java.util.List; - -public class UserMealsUtil { - public static void main(String[] args) { - List meals = Arrays.asList( - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) - ); - - List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsTo.forEach(System.out::println); - -// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - } - - public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with excess. Implement by cycles - return null; - } - - public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO Implement by streams - return null; - } -} From bc03d4b330d13f078526069d4a1492110ec1eda3 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:05:13 +0400 Subject: [PATCH 083/127] 1 1 HW0 streams --- src/main/java/ru/javawebinar/topjava/model/Meal.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index b15415aa11b3..961ffff87ac7 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; public class Meal extends AbstractBaseEntity { private final LocalDateTime dateTime; @@ -31,4 +33,12 @@ public String getDescription() { public int getCalories() { return calories; } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } } From 6d6c84a1ced777c0b76679d9898ee7612db609fc Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:05:21 +0400 Subject: [PATCH 084/127] 1 2 switch to war --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 29 +++++++++++++++++++++++++++++ src/main/webapp/index.html | 18 ++++++++++++++++++ src/main/webapp/users.jsp | 11 +++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index 0b1c2896da5b..b4426c1aa2d6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT 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/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..1e2ac3b901d9 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,18 @@ + + + + Java Enterprise (Topjava) + + +

    Проект Java Enterprise (Topjava)

    +
    +
    + Meals of  + + +
    + + diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp new file mode 100644 index 000000000000..650c8dda479c --- /dev/null +++ b/src/main/webapp/users.jsp @@ -0,0 +1,11 @@ +<%@ page contentType="text/html;charset=UTF-8" %> + + + Users + + +

    Home

    +
    +

    Users

    + + \ No newline at end of file From d817293bd689411e142994649f3c15a89466dc10 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:05:27 +0400 Subject: [PATCH 085/127] 1 3 add servlet api --- pom.xml | 74 +++++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 28 +++++++ 2 files changed, 102 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/pom.xml b/pom.xml index b4426c1aa2d6..d4d72867d407 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,80 @@ + + + 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-jdbc + ${spring.version} + + + + + org.postgresql + postgresql + ${postgresql.version} + + + + + 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/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); + } +} From 3d68e8aa72a8f36b46ba5ee643bdb1531a15e22c Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:05:33 +0400 Subject: [PATCH 086/127] 1 4 forward to redirect --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d4d72867d407..022781ee548b 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ topjava - install + package org.apache.maven.plugins From 4476a650da984f1146a07efd19c4a4e1557f99a5 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:05:38 +0400 Subject: [PATCH 087/127] 1 5 logging --- pom.xml | 12 ++++++++++++ src/main/resources/logback.xml | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 022781ee548b..cd0e9e7d7b84 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,18 @@ 1.8 UTF-8 UTF-8 + + 5.3.8 + + + 1.2.3 + 1.7.30 + + + 42.2.21 + + 4.13.2 + 3.19.0 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 + + + + + + + + + + From fc068856f5a5d410412ae8ff72148754359a7370 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:06:15 +0400 Subject: [PATCH 088/127] 21HW1 rollback --- .../java/ru/javawebinar/topjava/Main.java | 11 --- .../ru/javawebinar/topjava/to/MealTo.java | 67 +++++++++++++ .../topjava/util/DateTimeUtil.java | 40 ++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 9 ++ .../javawebinar/topjava/web/MealServlet.java | 94 +++++++++++++++++++ src/main/webapp/WEB-INF/tld/functions.tld | 16 ++++ src/main/webapp/meals.jsp | 66 +++++++++++++ .../user/InMemoryAdminRestControllerTest.java | 52 ++++++++++ .../topjava/web/user/UserTestData.java | 47 ++++++++++ 9 files changed, 391 insertions(+), 11 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java create mode 100644 src/main/java/ru/javawebinar/topjava/to/MealTo.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/meals.jsp create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java deleted file mode 100644 index c2f9cc618f7c..000000000000 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.javawebinar.topjava; - -/** - * @see Demo application - * @see Initial project - */ -public class Main { - public static void main(String[] args) { - System.out.format("Hello TopJava Enterprise!"); - } -} 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..c987ac84d47f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -0,0 +1,67 @@ +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 AtomicBoolean excess; // filteredByAtomic (or any ref type, e.g. boolean[1]) +// private final Boolean excess; // filteredByReflection +// private final Supplier excess; // filteredByClosure + private 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; + } + +// for filteredByClosure +// public Boolean getExcess() { +// return excess.get(); +// } + + // for filteredBySetterRecursion + public void setExcess(boolean excess) { + 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..b63ecf50602f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,40 @@ +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); + + 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/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 0ebfdb5fcdcb..7f93c793ef33 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -1,9 +1,18 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.format.DateTimeFormatter; public class TimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; } + + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } } + 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/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/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 000000000000..b9ac19fdd67c --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,66 @@ +<%@ page contentType="text/html;charset=UTF-8" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> + + + Meals + + + +
    +

    Home

    +
    +

    Meals

    +
    + +
    +
    From Date (inclusive):
    +
    +
    +
    +
    To Date (inclusive):
    +
    +
    +
    +
    From Time (inclusive):
    +
    +
    +
    +
    To Time (exclusive):
    +
    +
    + +
    +
    + Add Meal +

    + + + + + + + + + + + + + + + + + + + + +
    DateDescriptionCalories
    + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}UpdateDelete
    +
    + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java new file mode 100644 index 000000000000..19bd2898548d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -0,0 +1,52 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.Arrays; + +import static ru.javawebinar.topjava.web.user.UserTestData.NOT_FOUND; +import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; + +public class InMemoryAdminRestControllerTest { + private static final Logger log = LoggerFactory.getLogger(InMemoryAdminRestControllerTest.class); + + private static ConfigurableApplicationContext appCtx; + private static AdminRestController controller; + private static InMemoryUserRepository repository; + + @BeforeClass + public static void beforeClass() { + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + log.info("\n{}\n", Arrays.toString(appCtx.getBeanDefinitionNames())); + controller = appCtx.getBean(AdminRestController.class); + repository = appCtx.getBean(InMemoryUserRepository.class); + } + + @AfterClass + public static void afterClass() { + appCtx.close(); + } + + @Before + public void setup() { + // re-initialize + repository.init(); + } + + @Test + public void delete() { + controller.delete(USER_ID); + Assert.assertNull(repository.get(USER_ID)); + } + + @Test + public void deleteNotFound() { + Assert.assertThrows(NotFoundException.class, () -> controller.delete(NOT_FOUND)); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java b/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java new file mode 100644 index 000000000000..54ec6cbe605b --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class UserTestData { + public static final int USER_ID = START_SEQ; + public static final int ADMIN_ID = START_SEQ + 1; + public static final int NOT_FOUND = 10; + + public static final User user = new User(USER_ID, "User", "user@yandex.ru", "password", Role.USER); + public static final User admin = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ADMIN); + + public static User getNew() { + return new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.USER)); + } + + public static User getUpdated() { + User updated = new User(user); + updated.setEmail("update@gmail.com"); + updated.setName("UpdatedName"); + updated.setCaloriesPerDay(330); + updated.setPassword("newPass"); + updated.setEnabled(false); + updated.setRoles(Collections.singletonList(Role.ADMIN)); + return updated; + } + + public static void assertMatch(User actual, User expected) { + assertThat(actual).usingRecursiveComparison().ignoringFields("registered", "roles").isEqualTo(expected); + } + + public static void assertMatch(Iterable actual, User... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); + } +} From 1dc23e913ccebcf37eeb96741f6a69006bfede1c Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:06:29 +0400 Subject: [PATCH 089/127] 2 2 HW1 optional --- .../ru/javawebinar/topjava/model/Meal.java | 10 ++++++ .../topjava/repository/MealRepository.java | 23 +++++++++++++ src/main/webapp/mealForm.jsp | 34 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/MealRepository.java create mode 100644 src/main/webapp/mealForm.jsp diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 961ffff87ac7..9eed15f706be 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -41,4 +41,14 @@ public LocalDate getDate() { public LocalTime getTime() { return dateTime.toLocalTime(); } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } } 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..25aa5d087fce --- /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 do not belong to userId + Meal save(Meal meal, int userId); + + // false if meal do not belong to userId + boolean delete(int id, int userId); + + // null if meal do 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/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp new file mode 100644 index 000000000000..98a6f48735dc --- /dev/null +++ b/src/main/webapp/mealForm.jsp @@ -0,0 +1,34 @@ +<%@ page contentType="text/html;charset=UTF-8" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
    +

    Home

    +
    +

    ${param.action == 'create' ? 'Create meal' : 'Edit meal'}

    + +
    + +
    +
    DateTime:
    +
    +
    +
    +
    Description:
    +
    +
    +
    +
    Calories:
    +
    +
    + + +
    +
    + + From e3f9f139ea19b51dba2c6061059c2bcc9ed1fc95 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:06:35 +0400 Subject: [PATCH 090/127] 2 3 app layers --- .../topjava/model/AbstractBaseEntity.java | 48 ++++++++ .../topjava/model/AbstractNamedEntity.java | 27 +++++ .../ru/javawebinar/topjava/model/Role.java | 6 + .../ru/javawebinar/topjava/model/User.java | 106 ++++++++++++++++++ .../topjava/repository/UserRepository.java | 21 ++++ .../topjava/service/MealService.java | 47 ++++++++ .../topjava/service/UserService.java | 44 ++++++++ .../topjava/util/ValidationUtil.java | 43 +++++++ .../util/exception/NotFoundException.java | 7 ++ .../javawebinar/topjava/web/SecurityUtil.java | 22 ++++ .../topjava/web/meal/MealRestController.java | 76 +++++++++++++ .../web/user/AbstractUserController.java | 51 +++++++++ .../topjava/web/user/AdminRestController.java | 40 +++++++ .../web/user/ProfileRestController.java | 22 ++++ 14 files changed, 560 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/Role.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/User.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserService.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java 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..46e8d6e3038d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -0,0 +1,48 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractBaseEntity { + public static final int START_SEQ = 100000; + + protected Integer id; + + public AbstractBaseEntity() { + } + + protected AbstractBaseEntity(Integer id) { + this.id = id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getId() { + 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..5f0fde2bbfa8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractNamedEntity extends AbstractBaseEntity { + + protected String name; + + public 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/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..446d86ffef6a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,106 @@ +package ru.javawebinar.topjava.model; + +import org.springframework.util.CollectionUtils; + +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; + +public class User extends AbstractNamedEntity { + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + 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/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/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 000000000000..7957a0895d47 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.service; + +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Service; +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) { + checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + + public Meal create(Meal meal, int userId) { + 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..8fbe8dc0601f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,44 @@ +package ru.javawebinar.topjava.service; + +import org.springframework.stereotype.Service; +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) { + 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) { + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + + public List getAll() { + return repository.getAll(); + } + + public void update(User user) { + checkNotFoundWithId(repository.save(user), user.getId()); + } +} \ 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..971eb9c0c72b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.util; + + +import ru.javawebinar.topjava.model.AbstractBaseEntity; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +public class 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.getId() != 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/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java new file mode 100644 index 000000000000..588217547e60 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -0,0 +1,22 @@ +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; + + 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/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java new file mode 100644 index 000000000000..2f26c81fafeb --- /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(); + log.info("create {} for user {}", meal, userId); + checkNew(meal); + return service.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = SecurityUtil.authUserId(); + log.info("update {} for user {}", meal, userId); + assureIdConsistent(meal, id); + service.update(meal, userId); + } + + /** + *
      Filter separately + *
    1. by date
    2. + *
    3. by time for every date
    4. + *
    + */ + 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 From 37665b6db405ee794f6b0893c7b4a9cf864799e5 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:06:40 +0400 Subject: [PATCH 091/127] 2 5 add spring context --- src/main/resources/spring/spring-app.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/resources/spring/spring-app.xml diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..4c05aea4978b --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file From 86d25e85befbea66f7c3b6da8932c27beeee8acf Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:06:47 +0400 Subject: [PATCH 092/127] 3 02 HW2 repo filters --- src/main/java/ru/javawebinar/topjava/util/Util.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/util/Util.java 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..a17a6927f975 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.util; + +import org.springframework.lang.Nullable; + +public class 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 From 5210ba7f9896af600d342f37940c9c3f0264c26d Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:06:52 +0400 Subject: [PATCH 093/127] 3 03 HW2 meal layers --- .../topjava/model/UserMealWithExcess.java | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java deleted file mode 100644 index d0aa431a35d9..000000000000 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.javawebinar.topjava.model; - -import java.time.LocalDateTime; - -public class UserMealWithExcess { - private final LocalDateTime dateTime; - - private final String description; - - private final int calories; - - private final boolean excess; - - public UserMealWithExcess(LocalDateTime dateTime, String description, int calories, boolean excess) { - this.dateTime = dateTime; - this.description = description; - this.calories = calories; - this.excess = excess; - } - - @Override - public String toString() { - return "UserMealWithExcess{" + - "dateTime=" + dateTime + - ", description='" + description + '\'' + - ", calories=" + calories + - ", excess=" + excess + - '}'; - } -} From 0a94b299530cac51f1ef7b337391fdb9cd64c6eb Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:06:57 +0400 Subject: [PATCH 094/127] 3 06 HW2 optional filter --- src/main/webapp/css/style.css | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/webapp/css/style.css 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; +} From 3e0774a7f4e28d2da7de125c7ad8f6839323e573 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:07:02 +0400 Subject: [PATCH 095/127] 3 09 add junit --- pom.xml | 8 ++ .../ru/javawebinar/topjava/SpringMain.java | 36 ++++++++ .../inmemory/InMemoryBaseRepository.java | 38 ++++++++ .../inmemory/InMemoryMealRepository.java | 86 +++++++++++++++++++ .../inmemory/InMemoryUserRepository.java | 40 +++++++++ 5 files changed, 208 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/SpringMain.java create mode 100644 src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java create mode 100644 src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java create mode 100644 src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java diff --git a/pom.xml b/pom.xml index cd0e9e7d7b84..6d64511c38ef 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,14 @@ ${java.version}
    + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + -Dfile.encoding=UTF-8 + +
    diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java new file mode 100644 index 000000000000..b869d1c6e382 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,36 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.web.meal.MealRestController; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +public class SpringMain { + public static void main(String[] args) { + // java 7 automatic resource management (ARM) + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ADMIN)); + System.out.println(); + + MealRestController mealController = appCtx.getBean(MealRestController.class); + List filteredMealsWithExcess = + mealController.getBetween( + LocalDate.of(2020, Month.JANUARY, 30), LocalTime.of(7, 0), + LocalDate.of(2020, Month.JANUARY, 31), LocalTime.of(11, 0)); + filteredMealsWithExcess.forEach(System.out::println); + System.out.println(); + System.out.println(mealController.getBetween(null, null, null, null)); + } + } +} diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java new file mode 100644 index 000000000000..1a076364f38c --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import ru.javawebinar.topjava.model.AbstractBaseEntity; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class InMemoryBaseRepository { + + protected static final AtomicInteger counter = new AtomicInteger(START_SEQ); + + protected final Map map = new ConcurrentHashMap<>(); + + public T save(T entity) { + if (entity.isNew()) { + entity.setId(counter.incrementAndGet()); + map.put(entity.getId(), entity); + return entity; + } + return map.computeIfPresent(entity.getId(), (id, oldT) -> entity); + } + + public boolean delete(int id) { + return map.remove(id) != null; + } + + public T get(int id) { + return map.get(id); + } + + Collection getCollection() { + return map.values(); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java new file mode 100644 index 000000000000..4d1e1bad72c2 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java @@ -0,0 +1,86 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.Util; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.web.user.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; + +@Repository +public class InMemoryMealRepository implements MealRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepository.class); + + // Map userId -> mealRepository + private final Map> usersMealsMap = new ConcurrentHashMap<>(); + + { + MealsUtil.meals.forEach(meal -> save(meal, USER_ID)); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); + } + + + @Override + public Meal save(Meal meal, int userId) { + InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); + return meals.save(meal); + } + + @PostConstruct + public void postConstruct() { + log.info("+++ PostConstruct"); + } + + @PreDestroy + public void preDestroy() { + log.info("+++ PreDestroy"); + } + + @Override + public boolean delete(int id, int userId) { + InMemoryBaseRepository meals = usersMealsMap.get(userId); + return meals != null && meals.delete(id); + } + + @Override + public Meal get(int id, int userId) { + InMemoryBaseRepository meals = usersMealsMap.get(userId); + return meals == null ? null : meals.get(id); + } + + @Override + public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return filterByPredicate(userId, meal -> Util.isBetweenHalfOpen(meal.getDateTime(), startDateTime, endDateTime)); + } + + @Override + public List getAll(int userId) { + return filterByPredicate(userId, meal -> true); + } + + private List filterByPredicate(int userId, Predicate filter) { + InMemoryBaseRepository meals = usersMealsMap.get(userId); + return meals == null ? Collections.emptyList() : + meals.getCollection().stream() + .filter(filter) + .sorted(Comparator.comparing(Meal::getDateTime).reversed()) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java new file mode 100644 index 000000000000..93f1fd20bd35 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java @@ -0,0 +1,40 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.web.user.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.web.user.UserTestData.admin; +import static ru.javawebinar.topjava.web.user.UserTestData.user; + + +@Repository +public class InMemoryUserRepository extends InMemoryBaseRepository implements UserRepository { + + public void init() { + map.clear(); + map.put(UserTestData.USER_ID, user); + map.put(UserTestData.ADMIN_ID, admin); + counter.getAndSet(UserTestData.ADMIN_ID + 1); + } + + @Override + public List getAll() { + return getCollection().stream() + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { + return getCollection().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() + .orElse(null); + } +} \ No newline at end of file From 5cadc3000ea34041040c93a765c112b3fbdbdc16 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 24 Nov 2021 20:07:09 +0400 Subject: [PATCH 096/127] 3 10 add spring test --- ...InMemoryAdminRestControllerSpringTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java new file mode 100644 index 000000000000..6a8d612e6d07 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -0,0 +1,41 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import static ru.javawebinar.topjava.web.user.UserTestData.NOT_FOUND; +import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; + +@ContextConfiguration("classpath:spring/spring-app.xml") +@RunWith(SpringRunner.class) +public class InMemoryAdminRestControllerSpringTest { + + @Autowired + private AdminRestController controller; + + @Autowired + private InMemoryUserRepository repository; + + @Before + public void setup() { + repository.init(); + } + + @Test + public void delete() { + controller.delete(USER_ID); + Assert.assertNull(repository.get(USER_ID)); + } + + @Test + public void deleteNotFound() { + Assert.assertThrows(NotFoundException.class, () -> controller.delete(NOT_FOUND)); + } +} \ No newline at end of file From 0432299f60d7a0b26fe301f75e2cfd7e136cb086 Mon Sep 17 00:00:00 2001 From: login Date: Mon, 13 Dec 2021 17:16:53 +0400 Subject: [PATCH 097/127] 3 11 add postgresql --- src/main/resources/db/postgres.properties | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/resources/db/postgres.properties diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties new file mode 100644 index 000000000000..a3dcb93352b8 --- /dev/null +++ b/src/main/resources/db/postgres.properties @@ -0,0 +1,7 @@ +#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 From f4601826c28c001a9d8ccfda9c36989cea7254a3 Mon Sep 17 00:00:00 2001 From: login Date: Mon, 13 Dec 2021 17:17:01 +0400 Subject: [PATCH 098/127] 3 12 db implementation --- .../repository/jdbc/JdbcUserRepository.java | 81 +++++++++++++++++++ src/main/resources/db/initDB.sql | 35 ++++++++ src/main/resources/db/populateDB.sql | 18 +++++ src/main/resources/spring/spring-db.xml | 25 ++++++ 4 files changed, 159 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java create mode 100644 src/main/resources/db/initDB.sql create mode 100644 src/main/resources/db/populateDB.sql create mode 100644 src/main/resources/spring/spring-db.xml 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..7f6f8626a869 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java @@ -0,0 +1,81 @@ +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.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.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) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", user.getId()) + .addValue("name", user.getName()) + .addValue("email", user.getEmail()) + .addValue("password", user.getPassword()) + .addValue("registered", user.getRegistered()) + .addValue("enabled", user.isEnabled()) + .addValue("caloriesPerDay", user.getCaloriesPerDay()); + + if (user.isNew()) { + Number newKey = insertUser.executeAndReturnKey(map); + 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", map) == 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/resources/db/initDB.sql b/src/main/resources/db/initDB.sql new file mode 100644 index 000000000000..3bcef7a31c20 --- /dev/null +++ b/src/main/resources/db/initDB.sql @@ -0,0 +1,35 @@ +DROP TABLE IF EXISTS meals; +DROP TABLE IF EXISTS user_roles; +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'), + dateTime TIMESTAMP DEFAULT now() NOT NULL, + description VARCHAR NOT NULL, + calories INTEGER NOT NULL, + user_id INTEGER NOT NULL +); +CREATE UNIQUE INDEX users_unique_dateTime_idx ON meals (user_id, dateTime); diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql new file mode 100644 index 000000000000..19ea10b9cab6 --- /dev/null +++ b/src/main/resources/db/populateDB.sql @@ -0,0 +1,18 @@ +DELETE FROM meals; +DELETE FROM user_roles; +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 (dateTime, description, calories, user_id) +VALUES (to_timestamp('14 00 00 1 Jun 2015', 'HH24:MI:SS DD Mon YYYY'), 'Админ ланч', 510, 100001), + (to_timestamp('21 00 00 1 Jun 2015', 'HH24:MI:SS DD Mon YYYY'), 'Админ ужин', 1500, 100001); + diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml new file mode 100644 index 000000000000..8ec56c9e63f0 --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 7715d4b5ebc81cde4978b51ef47a093eb0623c51 Mon Sep 17 00:00:00 2001 From: login Date: Mon, 13 Dec 2021 17:17:09 +0400 Subject: [PATCH 099/127] 3 13 test UserService --- .../repository/jdbc/JdbcMealRepository.java | 91 ++++++++++++++++++ .../topjava/service/UserServiceTest.java | 95 +++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java 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..4ccf2dc116b7 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java @@ -0,0 +1,91 @@ +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.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.MealRepository; + +//import org.springframework.jdbc.core.BeanPropertyRowMapper; +//import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import ru.javawebinar.topjava.util.Util; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class JdbcMealRepository implements MealRepository { + + private static final BeanPropertyRowMapper 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("datetime", meal.getDateTime()) + .addValue("description", meal.getDescription()) + .addValue("calories", meal.getCalories()) + .addValue("user_id", userId); + + if (meal.isNew()) { + Number newKey = insertMeal.executeAndReturnKey(map); + meal.setId(newKey.intValue()); + } else if (namedParameterJdbcTemplate.update( + "UPDATE meals SET datetime=:datetime, description=:description, calories=:calories, " + + "user_id=:user_id 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 id as id, datetime as datetime, description as description, calories as calories FROM meals WHERE id=? and user_id=?", ROW_MAPPER, id, userId); +// List meals = jdbcTemplate.query("SELECT * FROM meals", ROW_MAPPER); + return DataAccessUtils.singleResult(meals); + } + + @Override + public List getAll(int userId) { + return jdbcTemplate.query("SELECT * FROM meals WHERE user_id = ? ORDER BY datetime", ROW_MAPPER, userId); + } + + @Override + public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return jdbcTemplate.query("SELECT * FROM meals " + + " WHERE ?<=datetime and " + + " datetime Util.isBetweenHalfOpen(meal.getDateTime(), startDateTime, endDateTime)); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java new file mode 100644 index 000000000000..d6c56ee1eb3b --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -0,0 +1,95 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.web.user.UserTestData; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +import static org.junit.Assert.assertThrows; +import static ru.javawebinar.topjava.web.user.UserTestData.*; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +public class UserServiceTest { + + static { + // Only for postgres driver logging + // It uses java.util.logging and logged via jul-to-slf4j bridge + SLF4JBridgeHandler.install(); + } + + @Autowired + private UserService service; + + @Test + public void create() { + User created = service.create(getNew()); + Integer newId = created.getId(); + User newUser = getNew(); + newUser.setId(newId); + assertMatch(created, newUser); + assertMatch(service.get(newId), newUser); + } + + @Test + public void duplicateMailCreate() { + assertThrows(DataAccessException.class, () -> + service.create(new User(null, "Duplicate", "user@yandex.ru", "newPass", Role.USER))); + } + + @Test + public void delete() { + service.delete(USER_ID); + assertThrows(NotFoundException.class, () -> service.get(USER_ID)); + } + + @Test + public void deletedNotFound() { + assertThrows(NotFoundException.class, () -> service.delete(NOT_FOUND)); + } + + @Test + public void get() { + User user = service.get(USER_ID); + assertMatch(user, UserTestData.user); + } + + @Test + public void getNotFound() { + assertThrows(NotFoundException.class, () -> service.get(NOT_FOUND)); + } + + @Test + public void getByEmail() { + User user = service.getByEmail("admin@gmail.com"); + assertMatch(user, admin); + } + + @Test + public void update() { + User updated = getUpdated(); + service.update(updated); + assertMatch(service.get(USER_ID), getUpdated()); + } + + @Test + public void getAll() { + List all = service.getAll(); + assertMatch(all, admin, user); + } +} \ No newline at end of file From 5776e390be6fcedcbc360c509f5ee98fe4ecb2f3 Mon Sep 17 00:00:00 2001 From: login Date: Mon, 13 Dec 2021 17:17:33 +0400 Subject: [PATCH 100/127] 3 14 test logging --- src/test/resources/logback-test.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/test/resources/logback-test.xml diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 000000000000..428ceca2ee02 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + true + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + + + + + + + + + + + + \ No newline at end of file From c31a598567b2d34f3350c1a14b7f96f9644d59a7 Mon Sep 17 00:00:00 2001 From: login Date: Mon, 13 Dec 2021 17:42:29 +0400 Subject: [PATCH 101/127] 4 1 HW3 --- .../ru/javawebinar/topjava/model/Meal.java | 21 ++++++++-- .../repository/jdbc/JdbcMealRepository.java | 39 ++++++++++--------- .../repository/jdbc/JdbcUserRepository.java | 15 ++----- src/main/resources/db/initDB.sql | 16 +++++--- src/main/resources/db/populateDB.sql | 15 +++++-- ...InMemoryAdminRestControllerSpringTest.java | 2 + .../user/InMemoryAdminRestControllerTest.java | 2 +- 7 files changed, 68 insertions(+), 42 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 9eed15f706be..1c1e67b15a3b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,11 +5,14 @@ import java.time.LocalTime; public class Meal extends AbstractBaseEntity { - private final LocalDateTime dateTime; + private LocalDateTime dateTime; - private final String description; + private String description; - private final int calories; + private int calories; + + public Meal() { + } public Meal(LocalDateTime dateTime, String description, int calories) { this(null, dateTime, description, calories); @@ -42,6 +45,18 @@ 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; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java index 4ccf2dc116b7..91f4001aa867 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java @@ -4,6 +4,8 @@ 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; @@ -24,7 +26,7 @@ @Repository public class JdbcMealRepository implements MealRepository { - private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); private final JdbcTemplate jdbcTemplate; @@ -46,46 +48,47 @@ public JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) - .addValue("datetime", meal.getDateTime()) .addValue("description", meal.getDescription()) .addValue("calories", meal.getCalories()) + .addValue("date_time", meal.getDateTime()) .addValue("user_id", userId); if (meal.isNew()) { - Number newKey = insertMeal.executeAndReturnKey(map); - meal.setId(newKey.intValue()); - } else if (namedParameterJdbcTemplate.update( - "UPDATE meals SET datetime=:datetime, description=:description, calories=:calories, " + - "user_id=:user_id WHERE id=:id and user_id=:user_id", map) == 0) { - return null; + 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; + 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 id as id, datetime as datetime, description as description, calories as calories FROM meals WHERE id=? and user_id=?", ROW_MAPPER, id, userId); -// List meals = jdbcTemplate.query("SELECT * FROM meals", ROW_MAPPER); + 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 datetime", ROW_MAPPER, 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 ?<=datetime and " + - " datetime Util.isBetweenHalfOpen(meal.getDateTime(), startDateTime, endDateTime)); + 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 index 7f6f8626a869..412bfbebc5df 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java @@ -4,7 +4,7 @@ import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +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; @@ -36,21 +36,14 @@ public JdbcUserRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate @Override public User save(User user) { - MapSqlParameterSource map = new MapSqlParameterSource() - .addValue("id", user.getId()) - .addValue("name", user.getName()) - .addValue("email", user.getEmail()) - .addValue("password", user.getPassword()) - .addValue("registered", user.getRegistered()) - .addValue("enabled", user.isEnabled()) - .addValue("caloriesPerDay", user.getCaloriesPerDay()); + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); if (user.isNew()) { - Number newKey = insertUser.executeAndReturnKey(map); + 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", map) == 0) { + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { return null; } return user; diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index 3bcef7a31c20..b444c29cfe98 100644 --- a/src/main/resources/db/initDB.sql +++ b/src/main/resources/db/initDB.sql @@ -1,5 +1,6 @@ DROP TABLE IF EXISTS meals; DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS meals; DROP TABLE IF EXISTS users; DROP SEQUENCE IF EXISTS global_seq; @@ -24,12 +25,15 @@ CREATE TABLE user_roles 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'), - dateTime TIMESTAMP DEFAULT now() NOT NULL, - description VARCHAR NOT NULL, - calories INTEGER NOT NULL, - user_id INTEGER NOT NULL + 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 users_unique_dateTime_idx ON meals (user_id, dateTime); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals (user_id, date_time); + diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index 19ea10b9cab6..4aa90f0095e2 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -1,5 +1,6 @@ DELETE FROM meals; DELETE FROM user_roles; +DELETE FROM meals; DELETE FROM users; ALTER SEQUENCE global_seq RESTART WITH 100000; @@ -12,7 +13,15 @@ INSERT INTO user_roles (role, user_id) VALUES ('USER', 100000), ('ADMIN', 100001); -INSERT INTO meals (dateTime, description, calories, user_id) -VALUES (to_timestamp('14 00 00 1 Jun 2015', 'HH24:MI:SS DD Mon YYYY'), 'Админ ланч', 510, 100001), - (to_timestamp('21 00 00 1 Jun 2015', 'HH24:MI:SS DD Mon YYYY'), 'Админ ужин', 1500, 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); + diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java index 6a8d612e6d07..5a0810a4674d 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -2,6 +2,7 @@ import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -15,6 +16,7 @@ @ContextConfiguration("classpath:spring/spring-app.xml") @RunWith(SpringRunner.class) +@Ignore public class InMemoryAdminRestControllerSpringTest { @Autowired diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java index 19bd2898548d..6dd6bbd07eec 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -13,6 +13,7 @@ import static ru.javawebinar.topjava.web.user.UserTestData.NOT_FOUND; import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; +@Ignore public class InMemoryAdminRestControllerTest { private static final Logger log = LoggerFactory.getLogger(InMemoryAdminRestControllerTest.class); @@ -39,7 +40,6 @@ public void setup() { repository.init(); } - @Test public void delete() { controller.delete(USER_ID); Assert.assertNull(repository.get(USER_ID)); From f92a1665a2a73bad4c3df04edbd86981fa0fa9ed Mon Sep 17 00:00:00 2001 From: login Date: Fri, 17 Dec 2021 13:40:06 +0400 Subject: [PATCH 102/127] 4 2 HW3 optional --- .../javawebinar/topjava/util/MealsUtil.java | 1 + .../ru/javawebinar/topjava/MealTestData.java | 39 ++++++ .../inmemory/InMemoryMealRepository.java | 16 +-- .../topjava/service/MealServiceTest.java | 112 ++++++++++++++++++ 4 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/MealTestData.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c04a2eca35eb..7320658d6e8d 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -17,6 +17,7 @@ import java.util.stream.Stream; import java.util.function.Predicate; import java.util.Collection; +import java.util.Arrays; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toList; diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java new file mode 100644 index 000000000000..0627d0a43a63 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -0,0 +1,39 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Meal; + +import java.time.Month; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; + +import static java.time.LocalDateTime.of; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class MealTestData { + public static final MatcherFactory MATCHER = MatcherFactory.usingIgnoringFieldsComparator(); + + public static final int NOT_FOUND = 10; + public static final int MEAL1_ID = START_SEQ + 2; + public static final int ADMIN_MEAL_ID = START_SEQ + 9; + + public static final Meal meal1 = new Meal(MEAL1_ID, of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500); + public static final Meal meal2 = new Meal(MEAL1_ID + 1, of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000); + public static final Meal meal3 = new Meal(MEAL1_ID + 2, of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500); + public static final Meal meal4 = new Meal(MEAL1_ID + 3, of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100); + public static final Meal meal5 = new Meal(MEAL1_ID + 4, of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 500); + public static final Meal meal6 = new Meal(MEAL1_ID + 5, of(2020, Month.JANUARY, 31, 13, 0), "Обед", 1000); + public static final Meal meal7 = new Meal(MEAL1_ID + 6, of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 510); + public static final Meal adminMeal1 = new Meal(ADMIN_MEAL_ID, of(2020, Month.JANUARY, 31, 14, 0), "Админ ланч", 510); + public static final Meal adminMeal2 = new Meal(ADMIN_MEAL_ID + 1, of(2020, Month.JANUARY, 31, 21, 0), "Админ ужин", 1500); + + public static final List meals = Arrays.asList(meal7, meal6, meal5, meal4, meal3, meal2, meal1); + + public static Meal getNew() { + return new Meal(null, of(2020, Month.FEBRUARY, 1, 18, 0), "Созданный ужин", 300); + } + + public static Meal getUpdated() { + return new Meal(MEAL1_ID, meal1.getDateTime().plus(2, ChronoUnit.MINUTES), "Обновленный завтрак", 200); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java index 4d1e1bad72c2..d1e38829c7a5 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java @@ -3,19 +3,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.web.user.UserTestData; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.util.MealsUtil; import ru.javawebinar.topjava.util.Util; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDateTime; -import java.time.Month; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -31,12 +28,11 @@ public class InMemoryMealRepository implements MealRepository { private final Map> usersMealsMap = new ConcurrentHashMap<>(); { - MealsUtil.meals.forEach(meal -> save(meal, USER_ID)); - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); + InMemoryBaseRepository userMeals = new InMemoryBaseRepository<>(); + MealTestData.meals.forEach(meal -> userMeals.map.put(meal.getId(), meal)); + usersMealsMap.put(UserTestData.USER_ID, userMeals); } - @Override public Meal save(Meal meal, int userId) { InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java new file mode 100644 index 000000000000..f05b33082d07 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -0,0 +1,112 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.Month; + +import static org.junit.Assert.assertThrows; +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.web.user.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +public class MealServiceTest { + + @Autowired + private MealService service; + + @Test + public void delete() { + service.delete(MEAL1_ID, USER_ID); + assertThrows(NotFoundException.class, () -> service.get(MEAL1_ID, USER_ID)); + } + + @Test + public void deleteNotFound() { + assertThrows(NotFoundException.class, () -> service.delete(NOT_FOUND, USER_ID)); + } + + @Test + public void deleteNotOwn() { + assertThrows(NotFoundException.class, () -> service.delete(MEAL1_ID, ADMIN_ID)); + } + + @Test + public void create() { + Meal created = service.create(getNew(), USER_ID); + int newId = created.id(); + Meal newMeal = getNew(); + newMeal.setId(newId); + MATCHER.assertMatch(created, newMeal); + MATCHER.assertMatch(service.get(newId, USER_ID), newMeal); + } + + @Test + public void duplicateDateTimeCreate() { + assertThrows(DataAccessException.class, () -> + service.create(new Meal(null, meal1.getDateTime(), "duplicate", 100), USER_ID)); + } + + + @Test + public void get() { + Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); + MATCHER.assertMatch(actual, adminMeal1); + } + + @Test + public void getNotFound() { + assertThrows(NotFoundException.class, () -> service.get(NOT_FOUND, USER_ID)); + } + + @Test + public void getNotOwn() { + assertThrows(NotFoundException.class, () -> service.get(MEAL1_ID, ADMIN_ID)); + } + + @Test + public void update() { + Meal updated = getUpdated(); + service.update(updated, USER_ID); + MATCHER.assertMatch(service.get(MEAL1_ID, USER_ID), getUpdated()); + } + + @Test + public void updateNotOwn() { + assertThrows(NotFoundException.class, () -> service.update(meal1, ADMIN_ID)); + MATCHER.assertMatch(service.get(MEAL1_ID, USER_ID), meal1); + } + + @Test + public void getAll() { + MATCHER.assertMatch(service.getAll(USER_ID), meals); + } + + @Test + public void getBetweenInclusive() { + MATCHER.assertMatch(service.getBetweenInclusive( + LocalDate.of(2020, Month.JANUARY, 30), + LocalDate.of(2020, Month.JANUARY, 30), USER_ID), + meal3, meal2, meal1); + } + + @Test + public void getBetweenWithNullDates() { + MATCHER.assertMatch(service.getBetweenInclusive(null, null, USER_ID), meals); + } +} \ No newline at end of file From 34634a1bf0ed242782161ba6dfeecc7717d850ad Mon Sep 17 00:00:00 2001 From: login Date: Fri, 17 Dec 2021 13:40:32 +0400 Subject: [PATCH 103/127] 4 3 tests refactoring --- .../javawebinar/topjava/MatcherFactory.java | 35 +++++++++++++++++++ .../topjava/service/UserServiceTest.java | 12 +++---- .../topjava/web/user/UserTestData.java | 16 ++------- 3 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/MatcherFactory.java diff --git a/src/test/java/ru/javawebinar/topjava/MatcherFactory.java b/src/test/java/ru/javawebinar/topjava/MatcherFactory.java new file mode 100644 index 000000000000..0c5a4883d041 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MatcherFactory.java @@ -0,0 +1,35 @@ +package ru.javawebinar.topjava; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Factory for creating test matchers. + *

    + * Comparing actual and expected objects via AssertJ + */ +public class MatcherFactory { + private final String[] fieldsToIgnore; + + private MatcherFactory(String... fieldsToIgnore) { + this.fieldsToIgnore = fieldsToIgnore; + } + + public static MatcherFactory usingIgnoringFieldsComparator(String... fieldsToIgnore) { + return new MatcherFactory<>(fieldsToIgnore); + } + + public void assertMatch(T actual, T expected) { + assertThat(actual).usingRecursiveComparison().ignoringFields(fieldsToIgnore).isEqualTo(expected); + } + + @SafeVarargs + public final void assertMatch(Iterable actual, T... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields(fieldsToIgnore).isEqualTo(expected); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index d6c56ee1eb3b..f44d0633f2e0 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -42,8 +42,8 @@ public void create() { Integer newId = created.getId(); User newUser = getNew(); newUser.setId(newId); - assertMatch(created, newUser); - assertMatch(service.get(newId), newUser); + MATCHER.assertMatch(created, newUser); + MATCHER.assertMatch(service.get(newId), newUser); } @Test @@ -66,7 +66,7 @@ public void deletedNotFound() { @Test public void get() { User user = service.get(USER_ID); - assertMatch(user, UserTestData.user); + MATCHER.assertMatch(user, UserTestData.user); } @Test @@ -77,19 +77,19 @@ public void getNotFound() { @Test public void getByEmail() { User user = service.getByEmail("admin@gmail.com"); - assertMatch(user, admin); + MATCHER.assertMatch(user, admin); } @Test public void update() { User updated = getUpdated(); service.update(updated); - assertMatch(service.get(USER_ID), getUpdated()); + MATCHER.assertMatch(service.get(USER_ID), getUpdated()); } @Test public void getAll() { List all = service.getAll(); - assertMatch(all, admin, user); + MATCHER.assertMatch(all, admin, user); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java b/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java index 54ec6cbe605b..3c2dfb7091c2 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java @@ -3,14 +3,14 @@ import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import java.util.Arrays; import java.util.Collections; import java.util.Date; -import static org.assertj.core.api.Assertions.assertThat; import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; public class UserTestData { + public static final MatcherFactory MATCHER = MatcherFactory.usingIgnoringFieldsComparator("registered", "roles"); + public static final int USER_ID = START_SEQ; public static final int ADMIN_ID = START_SEQ + 1; public static final int NOT_FOUND = 10; @@ -32,16 +32,4 @@ public static User getUpdated() { updated.setRoles(Collections.singletonList(Role.ADMIN)); return updated; } - - public static void assertMatch(User actual, User expected) { - assertThat(actual).usingRecursiveComparison().ignoringFields("registered", "roles").isEqualTo(expected); - } - - public static void assertMatch(Iterable actual, User... expected) { - assertMatch(actual, Arrays.asList(expected)); - } - - public static void assertMatch(Iterable actual, Iterable expected) { - assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); - } } From 5b62aa25c49dfd98b9d4b03fd5a10f02e565d340 Mon Sep 17 00:00:00 2001 From: login Date: Fri, 17 Dec 2021 13:40:37 +0400 Subject: [PATCH 104/127] 4 4 fix logging --- .../ru/javawebinar/topjava/service/UserServiceTest.java | 7 ------- src/test/resources/logback-test.xml | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index f44d0633f2e0..f0a4525c7b96 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -2,7 +2,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ContextConfiguration; @@ -27,12 +26,6 @@ @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class UserServiceTest { - static { - // Only for postgres driver logging - // It uses java.util.logging and logged via jul-to-slf4j bridge - SLF4JBridgeHandler.install(); - } - @Autowired private UserService service; diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 428ceca2ee02..da4eed7b9468 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -12,7 +12,7 @@ - + From 969ba1f7ab4f58b13fc1835caaf3526936ddd838 Mon Sep 17 00:00:00 2001 From: login Date: Fri, 17 Dec 2021 13:40:54 +0400 Subject: [PATCH 105/127] 4 5 improve code --- .codacy.yml | 5 ++++ .travis.yml | 26 +++++++++++++++++++ .../topjava/model/AbstractBaseEntity.java | 7 +++++ .../topjava/service/MealService.java | 5 +++- .../topjava/service/UserService.java | 6 ++++- .../topjava/util/DateTimeUtil.java | 3 +++ .../javawebinar/topjava/util/MealsUtil.java | 3 +++ .../ru/javawebinar/topjava/util/Util.java | 3 +++ .../topjava/util/ValidationUtil.java | 4 ++- .../javawebinar/topjava/web/SecurityUtil.java | 3 +++ .../inmemory/InMemoryBaseRepository.java | 2 ++ .../inmemory/InMemoryMealRepository.java | 1 + .../inmemory/InMemoryUserRepository.java | 2 ++ .../topjava/service/UserServiceTest.java | 2 +- 14 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 .codacy.yml create mode 100644 .travis.yml 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/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 46e8d6e3038d..a8536054e32d 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import org.springframework.util.Assert; + public abstract class AbstractBaseEntity { public static final int START_SEQ = 100000; @@ -20,6 +22,11 @@ public Integer getId() { return id; } + public int id() { + Assert.notNull(id, "Entity must have id"); + return id; + } + public boolean isNew() { return this.id == null; } diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index 7957a0895d47..df874378eb82 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -2,6 +2,7 @@ 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; @@ -38,10 +39,12 @@ public List getAll(int userId) { } public void update(Meal meal, int userId) { - checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + 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 index 8fbe8dc0601f..09ccee68c43f 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -1,6 +1,7 @@ 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; @@ -19,6 +20,7 @@ public UserService(UserRepository repository) { } public User create(User user) { + Assert.notNull(user, "user must not be null"); return repository.save(user); } @@ -31,6 +33,7 @@ public User get(int id) { } public User getByEmail(String email) { + Assert.notNull(email, "email must not be null"); return checkNotFound(repository.getByEmail(email), "email=" + email); } @@ -39,6 +42,7 @@ public List getAll() { } public void update(User user) { - checkNotFoundWithId(repository.save(user), user.getId()); + 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/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index b63ecf50602f..a4665e2ae0e8 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -16,6 +16,9 @@ public class DateTimeUtil { 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; } diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 7320658d6e8d..74f437ee18d6 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -94,6 +94,9 @@ public static List filterByPredicate(Collection meals, int calorie .collect(Collectors.toList()); } + private MealsUtil() { + } + public static List getTos(Collection meals, int caloriesPerDay) { return filterByPredicate(meals, caloriesPerDay, meal -> true); } diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java index a17a6927f975..0860f5c6bf3c 100644 --- a/src/main/java/ru/javawebinar/topjava/util/Util.java +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -3,6 +3,9 @@ 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); } diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 971eb9c0c72b..0f35f22a05d3 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -5,6 +5,8 @@ import ru.javawebinar.topjava.util.exception.NotFoundException; public class ValidationUtil { + private ValidationUtil() { + } public static T checkNotFoundWithId(T object, int id) { checkNotFoundWithId(object != null, id); @@ -36,7 +38,7 @@ 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.getId() != id) { + } else if (entity.id() != id) { throw new IllegalArgumentException(entity + " must be with id=" + id); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index 588217547e60..4bad5863e3c6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -8,6 +8,9 @@ public class SecurityUtil { private static int id = AbstractBaseEntity.START_SEQ; + private SecurityUtil() { + } + public static int authUserId() { return id; } diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java index 1a076364f38c..40904586ee9d 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -16,6 +17,7 @@ public class InMemoryBaseRepository { protected final Map map = new ConcurrentHashMap<>(); public T save(T entity) { + Objects.requireNonNull(entity, "Entity must not be null"); if (entity.isNew()) { entity.setId(counter.incrementAndGet()); map.put(entity.getId(), entity); diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java index d1e38829c7a5..8e611f37cb7d 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java @@ -35,6 +35,7 @@ public class InMemoryMealRepository implements MealRepository { @Override public Meal save(Meal meal, int userId) { + Objects.requireNonNull(meal, "meal must not be null"); InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); return meals.save(meal); } diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java index 93f1fd20bd35..90526c36cb82 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java @@ -7,6 +7,7 @@ import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import static ru.javawebinar.topjava.web.user.UserTestData.admin; @@ -32,6 +33,7 @@ public List getAll() { @Override public User getByEmail(String email) { + Objects.requireNonNull(email, "email must not be null"); return getCollection().stream() .filter(u -> email.equals(u.getEmail())) .findFirst() diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index f0a4525c7b96..aec1224b44eb 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -32,7 +32,7 @@ public class UserServiceTest { @Test public void create() { User created = service.create(getNew()); - Integer newId = created.getId(); + int newId = created.id(); User newUser = getNew(); newUser.setId(newId); MATCHER.assertMatch(created, newUser); From 9762e939f4cdfc3a48ad3b23c3dc4c0bb9676347 Mon Sep 17 00:00:00 2001 From: login Date: Fri, 17 Dec 2021 13:41:00 +0400 Subject: [PATCH 106/127] 4 6 init and populate db --- src/main/resources/db/postgres.properties | 1 + src/main/resources/spring/spring-db.xml | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index a3dcb93352b8..e5aeb857eb9a 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,3 +5,4 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password +database.init=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 8ec56c9e63f0..8e33d32f5f49 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,11 +1,29 @@ + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> + + + + + + + + + + + + From 2ede992c8639aad571973635d8602d55b4d0f192 Mon Sep 17 00:00:00 2001 From: login Date: Fri, 17 Dec 2021 13:41:15 +0400 Subject: [PATCH 107/127] 4 7 create inmemory test ctx --- src/main/resources/spring/spring-app.xml | 1 - src/test/java/ru/javawebinar/topjava/SpringMain.java | 2 +- .../web/user/InMemoryAdminRestControllerSpringTest.java | 4 +--- .../topjava/web/user/InMemoryAdminRestControllerTest.java | 3 +-- src/test/resources/spring/inmemory.xml | 7 +++++++ 5 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/spring/inmemory.xml diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 4c05aea4978b..4c17228b7a87 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -9,7 +9,6 @@ --> - diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index b869d1c6e382..e41f1ae119be 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -17,7 +17,7 @@ public class SpringMain { public static void main(String[] args) { // java 7 automatic resource management (ARM) - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml")) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ADMIN)); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java index 5a0810a4674d..46ca7a68fdcf 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -2,7 +2,6 @@ import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -14,9 +13,8 @@ import static ru.javawebinar.topjava.web.user.UserTestData.NOT_FOUND; import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; -@ContextConfiguration("classpath:spring/spring-app.xml") +@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/inmemory.xml"}) @RunWith(SpringRunner.class) -@Ignore public class InMemoryAdminRestControllerSpringTest { @Autowired diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java index 6dd6bbd07eec..0fd2d488ef62 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -13,7 +13,6 @@ import static ru.javawebinar.topjava.web.user.UserTestData.NOT_FOUND; import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; -@Ignore public class InMemoryAdminRestControllerTest { private static final Logger log = LoggerFactory.getLogger(InMemoryAdminRestControllerTest.class); @@ -23,7 +22,7 @@ public class InMemoryAdminRestControllerTest { @BeforeClass public static void beforeClass() { - appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml"); log.info("\n{}\n", Arrays.toString(appCtx.getBeanDefinitionNames())); controller = appCtx.getBean(AdminRestController.class); repository = appCtx.getBean(InMemoryUserRepository.class); diff --git a/src/test/resources/spring/inmemory.xml b/src/test/resources/spring/inmemory.xml new file mode 100644 index 000000000000..c6a2710cbd88 --- /dev/null +++ b/src/test/resources/spring/inmemory.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file From b733ac4d1d392cbc70845302f623ee5172146f58 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 17 May 2022 13:06:00 +0400 Subject: [PATCH 108/127] 4 8 add jpa --- pom.xml | 27 ++++++- .../topjava/model/AbstractBaseEntity.java | 10 ++- .../topjava/model/AbstractNamedEntity.java | 12 +++- .../ru/javawebinar/topjava/model/Meal.java | 13 ++++ .../ru/javawebinar/topjava/model/User.java | 34 +++++++++ .../repository/jpa/JpaMealRepository.java | 37 ++++++++++ .../repository/jpa/JpaUserRepository.java | 72 +++++++++++++++++++ src/main/resources/db/postgres.properties | 6 +- src/main/resources/spring/spring-db.xml | 42 +++++++++-- 9 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java diff --git a/pom.xml b/pom.xml index 6d64511c38ef..f0b5c3b32eae 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,11 @@ 4.13.2 3.19.0 + + + 5.5.2.Final + 6.2.0.Final + 3.0.1-b12 @@ -84,7 +89,7 @@ org.springframework - spring-jdbc + spring-orm ${spring.version} @@ -95,6 +100,26 @@ ${postgresql.version} + + + org.hibernate + hibernate-core + ${hibernate.version} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + + + org.glassfish + javax.el + ${javax-el.version} + provided + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index a8536054e32d..d9343a0740a8 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -2,12 +2,20 @@ 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; - public AbstractBaseEntity() { + protected AbstractBaseEntity() { } protected AbstractBaseEntity(Integer id) { diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index 5f0fde2bbfa8..edff2ab1718f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -1,10 +1,20 @@ package ru.javawebinar.topjava.model; +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + + +@MappedSuperclass public abstract class AbstractNamedEntity extends AbstractBaseEntity { + @NotBlank + @Size(min = 2, max = 100) + @Column(name = "name", nullable = false) protected String name; - public AbstractNamedEntity() { + protected AbstractNamedEntity() { } protected AbstractNamedEntity(Integer id, String name) { diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 1c1e67b15a3b..788ae8f6fe5b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,5 +1,7 @@ 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; @@ -11,6 +13,9 @@ public class Meal extends AbstractBaseEntity { private int calories; + @ManyToOne(fetch = FetchType.LAZY) + private User user; + public Meal() { } @@ -57,6 +62,14 @@ 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{" + diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 446d86ffef6a..7f861537e364 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,7 +1,13 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.util.Collection; import java.util.Date; import java.util.EnumSet; @@ -9,18 +15,46 @@ 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() { 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/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index e5aeb857eb9a..040ea60c00ab 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,4 +5,8 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password -database.init=true \ No newline at end of file + +database.init=true +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 8e33d32f5f49..610a6cab8063 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,5 +1,6 @@ - + @@ -33,11 +34,40 @@ - - - + + + + + + + + + + - - + + + + + + + + + + + \ No newline at end of file From 1f9a0e5554169c695e1c2dd86d987c2009f4eff4 Mon Sep 17 00:00:00 2001 From: login Date: Wed, 18 May 2022 20:50:40 +0400 Subject: [PATCH 109/127] 4_10_add_hsqldb --- pom.xml | 6 +++ src/main/resources/db/hsqldb.properties | 12 ++++++ src/main/resources/db/initDB_hsql.sql | 39 +++++++++++++++++++ src/main/resources/db/postgres.properties | 2 + src/main/resources/spring/spring-db.xml | 11 ++++-- .../topjava/web/user/UserTestData.java | 1 + 6 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/db/hsqldb.properties create mode 100644 src/main/resources/db/initDB_hsql.sql diff --git a/pom.xml b/pom.xml index f0b5c3b32eae..230934855b84 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,12 @@ postgresql ${postgresql.version} + + org.hsqldb + hsqldb + 2.3.4 + + 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_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/postgres.properties b/src/main/resources/db/postgres.properties index 040ea60c00ab..5ea981f0a5ca 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,8 +5,10 @@ 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/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 610a6cab8063..970261d06d54 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -3,11 +3,16 @@ xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" + xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd - http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + + + - + diff --git a/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java b/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java index 3c2dfb7091c2..e3a98679ca08 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import ru.javawebinar.topjava.MatcherFactory; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; From b98365faa8445ce472ebdb64d0313332e206438a Mon Sep 17 00:00:00 2001 From: login Date: Thu, 26 May 2022 22:41:40 +0400 Subject: [PATCH 110/127] 4_10_add_hsqldb --- src/main/java/ru/javawebinar/topjava/model/User.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 7f861537e364..da7c839cbea3 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -21,7 +21,7 @@ @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")}) +@Table(name = "users") public class User extends AbstractNamedEntity { public static final String DELETE = "User.delete"; @@ -48,7 +48,7 @@ public class User extends AbstractNamedEntity { @Enumerated(EnumType.STRING) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), - uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "role"}, name = "user_roles_unique_idx")}) + uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "role"}, name = "uk_user_roles")}) @Column(name = "role") @ElementCollection(fetch = FetchType.EAGER) private Set roles; @@ -61,7 +61,7 @@ public User() { } public User(User u) { - this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRegistered(), u.getRoles()); + this(u.id, u.name, u.email, u.password, u.caloriesPerDay, u.enabled, u.registered, u.roles); } public User(Integer id, String name, String email, String password, Role role, Role... roles) { From c829ac52796b7c230d9f2e2d8cb36daf594253b0 Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 16:15:35 +0400 Subject: [PATCH 111/127] 5_1_jdk_16 --- .travis.yml | 2 +- pom.xml | 13 ++++++++++++- .../topjava/model/AbstractBaseEntity.java | 5 ++++- .../repository/jdbc/JdbcUserRepository.java | 7 ++++--- .../javawebinar/topjava/web/MealServlet.java | 18 ++++++++---------- .../ru/javawebinar/topjava/MatcherFactory.java | 4 ++-- .../ru/javawebinar/topjava/MealTestData.java | 3 +-- .../inmemory/InMemoryMealRepository.java | 10 +++++----- 8 files changed, 37 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8f44cef8d05a..c3539a9a5a14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ # https://docs.travis-ci.com/user/languages/java/ language: java -jdk: openjdk8 +jdk: openjdk16 #https://dzone.com/articles/travis-ci-tutorial-java-projects cache: diff --git a/pom.xml b/pom.xml index 230934855b84..536805f13d3d 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ http://topjava.herokuapp.com/ - 1.8 + 17 UTF-8 UTF-8 @@ -47,6 +47,11 @@ ${java.version} + + org.apache.maven.plugins + maven-war-plugin + 3.3.1 + org.apache.maven.plugins maven-surefire-plugin @@ -81,6 +86,12 @@ runtime + + javax.annotation + javax.annotation-api + 1.3.2 + + org.springframework diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index d9343a0740a8..1cd6897add4a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import org.hibernate.Hibernate; import org.springframework.util.Assert; import javax.persistence.*; @@ -13,6 +14,8 @@ public abstract class AbstractBaseEntity { @Id @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") +// See https://hibernate.atlassian.net/browse/HHH-3718 and https://hibernate.atlassian.net/browse/HHH-12034 +// Proxy initialization when accessing its identifier managed now by JPA_PROXY_COMPLIANCE setting protected Integer id; protected AbstractBaseEntity() { @@ -49,7 +52,7 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !getClass().equals(Hibernate.getClass(o))) { return false; } AbstractBaseEntity that = (AbstractBaseEntity) o; diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java index 412bfbebc5df..0d4baa50eef2 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java @@ -41,9 +41,10 @@ public User save(User 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) { + } 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; diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index a8f60993833f..defeb1d7005a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -58,32 +58,30 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t String action = request.getParameter("action"); switch (action == null ? "all" : action) { - case "delete": + case "delete" -> { int id = getId(request); mealController.delete(id); response.sendRedirect("meals"); - break; - case "create": - case "update": + } + case "create", "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": + } + 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: + } + default -> { request.setAttribute("meals", mealController.getAll()); request.getRequestDispatcher("/meals.jsp").forward(request, response); - break; + } } } diff --git a/src/test/java/ru/javawebinar/topjava/MatcherFactory.java b/src/test/java/ru/javawebinar/topjava/MatcherFactory.java index 0c5a4883d041..b33bdd3cdc2c 100644 --- a/src/test/java/ru/javawebinar/topjava/MatcherFactory.java +++ b/src/test/java/ru/javawebinar/topjava/MatcherFactory.java @@ -1,6 +1,6 @@ package ru.javawebinar.topjava; -import java.util.Arrays; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -26,7 +26,7 @@ public void assertMatch(T actual, T expected) { @SafeVarargs public final void assertMatch(Iterable actual, T... expected) { - assertMatch(actual, Arrays.asList(expected)); + assertMatch(actual, List.of(expected)); } public void assertMatch(Iterable actual, Iterable expected) { diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index 0627d0a43a63..d4ebbda4a37c 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -4,7 +4,6 @@ import java.time.Month; import java.time.temporal.ChronoUnit; -import java.util.Arrays; import java.util.List; import static java.time.LocalDateTime.of; @@ -27,7 +26,7 @@ public class MealTestData { public static final Meal adminMeal1 = new Meal(ADMIN_MEAL_ID, of(2020, Month.JANUARY, 31, 14, 0), "Админ ланч", 510); public static final Meal adminMeal2 = new Meal(ADMIN_MEAL_ID + 1, of(2020, Month.JANUARY, 31, 21, 0), "Админ ужин", 1500); - public static final List meals = Arrays.asList(meal7, meal6, meal5, meal4, meal3, meal2, meal1); + public static final List meals = List.of(meal7, meal6, meal5, meal4, meal3, meal2, meal1); public static Meal getNew() { return new Meal(null, of(2020, Month.FEBRUARY, 1, 18, 0), "Созданный ужин", 300); diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java index 8e611f37cb7d..b31c9c9d33e0 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java @@ -28,7 +28,7 @@ public class InMemoryMealRepository implements MealRepository { private final Map> usersMealsMap = new ConcurrentHashMap<>(); { - InMemoryBaseRepository userMeals = new InMemoryBaseRepository<>(); + var userMeals = new InMemoryBaseRepository(); MealTestData.meals.forEach(meal -> userMeals.map.put(meal.getId(), meal)); usersMealsMap.put(UserTestData.USER_ID, userMeals); } @@ -36,7 +36,7 @@ public class InMemoryMealRepository implements MealRepository { @Override public Meal save(Meal meal, int userId) { Objects.requireNonNull(meal, "meal must not be null"); - InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); + var meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); return meals.save(meal); } @@ -52,13 +52,13 @@ public void preDestroy() { @Override public boolean delete(int id, int userId) { - InMemoryBaseRepository meals = usersMealsMap.get(userId); + var meals = usersMealsMap.get(userId); return meals != null && meals.delete(id); } @Override public Meal get(int id, int userId) { - InMemoryBaseRepository meals = usersMealsMap.get(userId); + var meals = usersMealsMap.get(userId); return meals == null ? null : meals.get(id); } @@ -73,7 +73,7 @@ public List getAll(int userId) { } private List filterByPredicate(int userId, Predicate filter) { - InMemoryBaseRepository meals = usersMealsMap.get(userId); + var meals = usersMealsMap.get(userId); return meals == null ? Collections.emptyList() : meals.getCollection().stream() .filter(filter) From c6a2e8c9ffaa2aab94dff2f030e3074e5d307ae5 Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 16:15:58 +0400 Subject: [PATCH 112/127] 5_2_HW4 --- .../ru/javawebinar/topjava/model/Meal.java | 34 ++++++++++++++++- .../repository/jpa/JpaMealRepository.java | 37 ++++++++++++++++--- .../ru/javawebinar/topjava/MealTestData.java | 2 +- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 788ae8f6fe5b..fe2c242ede78 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,19 +1,49 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +@NamedQueries({ + @NamedQuery(name = Meal.ALL_SORTED, query = "SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC"), + @NamedQuery(name = Meal.DELETE, query = "DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId"), + @NamedQuery(name = Meal.GET_BETWEEN, query = """ + SELECT m FROM Meal m + WHERE m.user.id=:userId AND m.dateTime >= :startDateTime AND m.dateTime < :endDateTime ORDER BY m.dateTime DESC + """), +// @NamedQuery(name = Meal.GET_BETWEEN, query = "SELECT m FROM Meal m WHERE m.user.id=:userId AND m.dateTime >= :startDateTime AND m.dateTime < :endDateTime ORDER BY m.dateTime DESC"), +// @NamedQuery(name = Meal.UPDATE, query = "UPDATE Meal m SET m.dateTime = :datetime, m.calories= :calories," + +// "m.description=:desc where m.id=:id and m.user.id=:userId") +}) +@Entity +@Table(name = "meals", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "date_time"}, name = "meals_unique_user_datetime_idx")}) public class Meal extends AbstractBaseEntity { + public static final String ALL_SORTED = "Meal.getAll"; + public static final String DELETE = "Meal.delete"; + public static final String GET_BETWEEN = "Meal.getBetween"; + + @Column(name = "date_time", nullable = false) + @NotNull private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotBlank + @Size(min = 2, max = 120) private String description; + @Column(name = "calories", nullable = false) + @Range(min = 10, max = 5000) private int calories; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + @NotNull private User user; public Meal() { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java index 9cc19a4dc31b..300a920aea2f 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java @@ -1,37 +1,64 @@ package ru.javawebinar.topjava.repository.jpa; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.MealRepository; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @Repository +@Transactional(readOnly = true) public class JpaMealRepository implements MealRepository { + @PersistenceContext + private EntityManager em; + @Override + @Transactional public Meal save(Meal meal, int userId) { - return null; + meal.setUser(em.getReference(User.class, userId)); + if (meal.isNew()) { + em.persist(meal); + return meal; + } else if (get(meal.id(), userId) == null) { + return null; + } + return em.merge(meal); } @Override + @Transactional public boolean delete(int id, int userId) { - return false; + return em.createNamedQuery(Meal.DELETE) + .setParameter("id", id) + .setParameter("userId", userId) + .executeUpdate() != 0; } @Override public Meal get(int id, int userId) { - return null; + Meal meal = em.find(Meal.class, id); + return meal != null && meal.getUser().getId() == userId ? meal : null; } @Override public List getAll(int userId) { - return null; + return em.createNamedQuery(Meal.ALL_SORTED, Meal.class) + .setParameter("userId", userId) + .getResultList(); } @Override public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { - return null; + return em.createNamedQuery(Meal.GET_BETWEEN, Meal.class) + .setParameter("userId", userId) + .setParameter("startDateTime", startDateTime) + .setParameter("endDateTime", endDateTime) + .getResultList(); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index d4ebbda4a37c..1d3087d8e0a0 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -10,7 +10,7 @@ import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; public class MealTestData { - public static final MatcherFactory MATCHER = MatcherFactory.usingIgnoringFieldsComparator(); + public static final MatcherFactory MATCHER = MatcherFactory.usingIgnoringFieldsComparator("user"); public static final int NOT_FOUND = 10; public static final int MEAL1_ID = START_SEQ + 2; From 6ed8932aa737de6a3de6a8f93b51482482cdc98e Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 16:17:03 +0400 Subject: [PATCH 113/127] 5_3_fix_hibernate_issue --- src/main/resources/spring/spring-db.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 970261d06d54..ae30dc794b54 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -48,6 +48,7 @@ + From 93bc0f009714f9ee9e001bb337073e0adf805413 Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 16:17:12 +0400 Subject: [PATCH 114/127] 5_4_HW4_optional --- src/main/resources/logback.xml | 2 +- .../topjava/service/MealServiceTest.java | 34 ++++++++++++++++++- src/test/resources/logback-test.xml | 16 +++++++-- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index c7bffc3a958c..12a4f63c6c15 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -16,7 +16,7 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index f05b33082d07..5fa013195b92 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,7 +1,13 @@ package ru.javawebinar.topjava.service; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; import org.junit.runner.RunWith; +import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ContextConfiguration; @@ -13,8 +19,10 @@ import java.time.LocalDate; import java.time.Month; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertThrows; +import static org.slf4j.LoggerFactory.getLogger; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.web.user.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; @@ -26,10 +34,33 @@ @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { + private static final Logger log = getLogger("result"); + + private static final StringBuilder results = new StringBuilder(); + + @Rule + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public final Stopwatch stopwatch = new Stopwatch() { + @Override + protected void finished(long nanos, Description description) { + String result = String.format("\n%-25s %7d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result); + log.info(result + " ms\n"); + } + }; @Autowired private MealService service; + @AfterClass + public static void printResult() { + log.info("\n---------------------------------" + + "\nTest Duration, ms" + + "\n---------------------------------" + + results + + "\n---------------------------------"); + } + @Test public void delete() { service.delete(MEAL1_ID, USER_ID); @@ -88,7 +119,8 @@ public void update() { @Test public void updateNotOwn() { - assertThrows(NotFoundException.class, () -> service.update(meal1, ADMIN_ID)); + NotFoundException exception = assertThrows(NotFoundException.class, () -> service.update(meal1, ADMIN_ID)); + Assert.assertEquals("Not found entity with id=" + MEAL1_ID, exception.getMessage()); MATCHER.assertMatch(service.get(MEAL1_ID, USER_ID), meal1); } diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index da4eed7b9468..803655475302 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -7,12 +7,22 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n - - + + + UTF-8 + %magenta(%msg%n) + + + + + + + + From 061098933b9a8a8565a745480bd4116e91ca2e02 Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 17:11:15 +0400 Subject: [PATCH 115/127] 5_5_profiles_connection_pool --- pom.xml | 59 +++++++++++------ .../java/ru/javawebinar/topjava/Profiles.java | 28 ++++++++ src/main/resources/db/hsqldb.properties | 1 - src/main/resources/db/postgres.properties | 1 - src/main/resources/spring/spring-db.xml | 65 ++++++++++++------- .../topjava/service/MealServiceTest.java | 2 + .../topjava/service/UserServiceTest.java | 2 + 7 files changed, 113 insertions(+), 45 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/Profiles.java diff --git a/pom.xml b/pom.xml index 536805f13d3d..e4191cdafb8d 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,9 @@ UTF-8 5.3.8 + 2.5.2 + + 9.0.48 1.2.3 @@ -72,13 +75,6 @@ compile - - org.slf4j - jul-to-slf4j - ${slf4j.version} - runtime - - ch.qos.logback logback-classic @@ -104,19 +100,6 @@ ${spring.version} - - - org.postgresql - postgresql - ${postgresql.version} - - - org.hsqldb - hsqldb - 2.3.4 - - - org.hibernate @@ -173,6 +156,42 @@ + + hsqldb + + + org.hsqldb + hsqldb + 2.3.4 + + + + + + postgres + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + provided + + + org.slf4j + jul-to-slf4j + ${slf4j.version} + runtime + + + + true + + diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java new file mode 100644 index 000000000000..f729daaa8441 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -0,0 +1,28 @@ +package ru.javawebinar.topjava; + +import org.springframework.util.ClassUtils; + +public class Profiles { + public static final String + JDBC = "jdbc", + JPA = "jpa", + DATAJPA = "datajpa"; + + public static final String REPOSITORY_IMPLEMENTATION = DATAJPA; + + public static final String + POSTGRES_DB = "postgres", + HSQL_DB = "hsqldb"; + + public static final String ACTIVE_DB = POSTGRES_DB; + // Get DB profile depending of DB driver in classpath + public static String getActiveDbProfile() { + if (ClassUtils.isPresent("org.postgresql.Driver", null)) { + return POSTGRES_DB; + } else if (ClassUtils.isPresent("org.hsqldb.jdbcDriver", null)) { + return HSQL_DB; + } else { + throw new IllegalStateException("Could not find DB driver"); + } + } +} diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties index c7944e25c223..17c03ef4ebda 100644 --- a/src/main/resources/db/hsqldb.properties +++ b/src/main/resources/db/hsqldb.properties @@ -3,7 +3,6 @@ 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 diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 5ea981f0a5ca..63817b2c85bf 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,7 +5,6 @@ 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 diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index ae30dc794b54..10e508a4c75e 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -9,36 +9,16 @@ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> - - - - - - - - - + - + + - - - - - - - - @@ -76,4 +56,43 @@ --> + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 5fa013195b92..acbb25551e79 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -14,6 +14,7 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -33,6 +34,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) public class MealServiceTest { private static final Logger log = getLogger("result"); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index aec1224b44eb..313c8971af78 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -4,6 +4,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; @@ -24,6 +25,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) public class UserServiceTest { @Autowired From f5a43e2c9f655fa419c9e96d5bce52bd7448d00a Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 17:11:40 +0400 Subject: [PATCH 116/127] 5_0_fix --- .../java/ru/javawebinar/topjava/service/UserServiceTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 313c8971af78..9d51ba7f40dc 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -9,11 +9,13 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.web.user.UserTestData; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; + import java.util.List; import static org.junit.Assert.assertThrows; From 1fe24d112bcd088ca75faebe3142ac16a8e6ef16 Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 17:11:56 +0400 Subject: [PATCH 117/127] 5_6_profile_resolver --- .../topjava/ActiveDbProfileResolver.java | 13 +++++++++++++ .../topjava/service/MealServiceTest.java | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java diff --git a/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java new file mode 100644 index 000000000000..2d819f84e5eb --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java @@ -0,0 +1,13 @@ +package ru.javawebinar.topjava; + +import org.springframework.lang.NonNull; +import org.springframework.test.context.ActiveProfilesResolver; + +//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver +public class ActiveDbProfileResolver implements ActiveProfilesResolver { + @Override + public @NonNull + String[] resolve(@NonNull Class aClass) { + return new String[]{Profiles.getActiveDbProfile()}; + } +} diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index acbb25551e79..1dc534525dfa 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; @@ -121,7 +122,7 @@ public void update() { @Test public void updateNotOwn() { - NotFoundException exception = assertThrows(NotFoundException.class, () -> service.update(meal1, ADMIN_ID)); + NotFoundException exception = assertThrows(NotFoundException.class, () -> service.update(getUpdated(), ADMIN_ID)); Assert.assertEquals("Not found entity with id=" + MEAL1_ID, exception.getMessage()); MATCHER.assertMatch(service.get(MEAL1_ID, USER_ID), meal1); } From c4418fddd476fc0725622ad64aa113444f2a8b54 Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 17:12:26 +0400 Subject: [PATCH 118/127] 5_7_spring_data_jpa --- pom.xml | 19 +++++--- .../topjava/model/AbstractBaseEntity.java | 5 ++- .../datajpa/CrudMealRepository.java | 7 +++ .../datajpa/CrudUserRepository.java | 19 ++++++++ .../datajpa/DataJpaMealRepository.java | 43 ++++++++++++++++++ .../datajpa/DataJpaUserRepository.java | 44 +++++++++++++++++++ src/main/resources/spring/spring-db.xml | 4 +- 7 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java diff --git a/pom.xml b/pom.xml index e4191cdafb8d..b7bf73cf48a9 100644 --- a/pom.xml +++ b/pom.xml @@ -91,13 +91,12 @@ org.springframework - spring-context - ${spring.version} + spring-context-support - org.springframework - spring-orm - ${spring.version} + org.springframework.data + spring-data-jpa + ${spring-data-jpa.version} @@ -144,7 +143,6 @@ org.springframework spring-test - ${spring.version} test @@ -195,5 +193,14 @@ + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 1cd6897add4a..2ab30f34f272 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.model; import org.hibernate.Hibernate; +import org.springframework.data.domain.Persistable; import org.springframework.util.Assert; import javax.persistence.*; @@ -8,7 +9,7 @@ @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) -public abstract class AbstractBaseEntity { +public abstract class AbstractBaseEntity implements Persistable { public static final int START_SEQ = 100000; @Id @@ -29,6 +30,7 @@ public void setId(Integer id) { this.id = id; } + @Override public Integer getId() { return id; } @@ -38,6 +40,7 @@ public int id() { return id; } + @Override public boolean isNew() { return this.id == null; } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java new file mode 100644 index 000000000000..a3659675c910 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.javawebinar.topjava.model.Meal; + +public interface CrudMealRepository extends JpaRepository { +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java new file mode 100644 index 000000000000..24c42a814220 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.model.User; + +@Transactional(readOnly = true) +public interface CrudUserRepository extends JpaRepository { + @Transactional + @Modifying +// @Query(name = User.DELETE) + @Query("DELETE FROM User u WHERE u.id=:id") + int delete(@Param("id") int id); + + User getByEmail(String email); +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java new file mode 100644 index 000000000000..d1b4c8efe9cc --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.repository.datajpa; + +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 DataJpaMealRepository implements MealRepository { + + private final CrudMealRepository crudRepository; + + public DataJpaMealRepository(CrudMealRepository crudRepository) { + this.crudRepository = crudRepository; + } + + @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; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java new file mode 100644 index 000000000000..bc240d2d5a90 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java @@ -0,0 +1,44 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.List; + +@Repository +public class DataJpaUserRepository implements UserRepository { + private static final Sort SORT_NAME_EMAIL = Sort.by(Sort.Direction.ASC, "name", "email"); + + private final CrudUserRepository crudRepository; + + public DataJpaUserRepository(CrudUserRepository crudRepository) { + this.crudRepository = crudRepository; + } + + @Override + public User save(User user) { + return crudRepository.save(user); + } + + @Override + public boolean delete(int id) { + return crudRepository.delete(id) != 0; + } + + @Override + public User get(int id) { + return crudRepository.findById(id).orElse(null); + } + + @Override + public User getByEmail(String email) { + return crudRepository.getByEmail(email); + } + + @Override + public List getAll() { + return crudRepository.findAll(SORT_NAME_EMAIL); + } +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 10e508a4c75e..4106dbf1fb86 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -4,10 +4,12 @@ xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd - http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> From 4e1915711b960f320ad4dd356dc57c916ff2a3a9 Mon Sep 17 00:00:00 2001 From: login Date: Sun, 5 Jun 2022 17:12:42 +0400 Subject: [PATCH 119/127] 5_8_spring_cache --- pom.xml | 29 +++++++++++++++++++ .../topjava/service/UserService.java | 6 ++++ src/main/resources/cache/ehcache.xml | 25 ++++++++++++++++ src/main/resources/spring/spring-app.xml | 1 + src/main/resources/spring/spring-cache.xml | 20 +++++++++++++ .../topjava/service/UserServiceTest.java | 10 +++++++ 6 files changed, 91 insertions(+) create mode 100644 src/main/resources/cache/ehcache.xml create mode 100644 src/main/resources/spring/spring-cache.xml diff --git a/pom.xml b/pom.xml index b7bf73cf48a9..b40e1885aedd 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,9 @@ 5.5.2.Final 6.2.0.Final 3.0.1-b12 + + + 3.9.4 @@ -119,6 +122,32 @@ provided + + + javax.cache + cache-api + 1.1.0 + + + org.ehcache + ehcache + runtime + ${ehcache.version} + + + org.glassfish.jaxb + jaxb-runtime + + + + + + + org.glassfish.jaxb + jaxb-runtime + 2.4.0-b180830.0438 + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 09ccee68c43f..e0979b7ee4fd 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.service; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import ru.javawebinar.topjava.model.User; @@ -19,11 +21,13 @@ public UserService(UserRepository repository) { this.repository = repository; } + @CacheEvict(value = "users", allEntries = true) public User create(User user) { Assert.notNull(user, "user must not be null"); return repository.save(user); } + @CacheEvict(value = "users", allEntries = true) public void delete(int id) { checkNotFoundWithId(repository.delete(id), id); } @@ -37,10 +41,12 @@ public User getByEmail(String email) { return checkNotFound(repository.getByEmail(email), "email=" + email); } + @Cacheable("users") public List getAll() { return repository.getAll(); } + @CacheEvict(value = "users", allEntries = true) public void update(User user) { Assert.notNull(user, "user must not be null"); checkNotFoundWithId(repository.save(user), user.id()); diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml new file mode 100644 index 000000000000..05589f71f06e --- /dev/null +++ b/src/main/resources/cache/ehcache.xml @@ -0,0 +1,25 @@ + + + + + + + + + + 5 + + 5000 + + + + + + + 1 + + + + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 4c17228b7a87..d6c643e972e1 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -9,6 +9,7 @@ --> + diff --git a/src/main/resources/spring/spring-cache.xml b/src/main/resources/spring/spring-cache.xml new file mode 100644 index 000000000000..73325fee065f --- /dev/null +++ b/src/main/resources/spring/spring-cache.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 9d51ba7f40dc..e05cf137fb26 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -1,8 +1,10 @@ package ru.javawebinar.topjava.service; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -33,6 +35,14 @@ public class UserServiceTest { @Autowired private UserService service; + @Autowired + private CacheManager cacheManager; + + @Before + public void setup() { + cacheManager.getCache("users").clear(); + } + @Test public void create() { User created = service.create(getNew()); From 977d51a7da20d3e289ec361fd21f115624193a1b Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:17:46 +0400 Subject: [PATCH 120/127] homework --- src/main/resources/spring/spring-db.xml | 18 ++++++++++++------ ...eTest.java => AbstractUserServiceTest.java} | 2 +- .../user/InMemoryAdminRestControllerTest.java | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) rename src/test/java/ru/javawebinar/topjava/service/{UserServiceTest.java => AbstractUserServiceTest.java} (99%) diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 4106dbf1fb86..e165ce8a2512 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -83,12 +83,18 @@ - + + + + + + + + + + + + diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java similarity index 99% rename from src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index e05cf137fb26..c34258f7cc4f 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -99,4 +99,4 @@ public void getAll() { List all = service.getAll(); MATCHER.assertMatch(all, admin, user); } -} \ No newline at end of file +} diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java index 0fd2d488ef62..ddb2b75938a3 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -39,6 +39,7 @@ public void setup() { repository.init(); } + @Test public void delete() { controller.delete(USER_ID); Assert.assertNull(repository.get(USER_ID)); From 558add002d937fe416769e33c2ab6015be31d8f4 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:20:46 +0400 Subject: [PATCH 121/127] homework --- .../ru/javawebinar/topjava/{web/user => }/UserTestData.java | 4 ++-- .../topjava/repository/inmemory/InMemoryMealRepository.java | 5 +---- .../topjava/repository/inmemory/InMemoryUserRepository.java | 6 +++--- .../{MealServiceTest.java => AbstractMealServiceTest.java} | 4 ++-- .../topjava/service/AbstractUserServiceTest.java | 4 ++-- .../web/user/InMemoryAdminRestControllerSpringTest.java | 4 ++-- .../topjava/web/user/InMemoryAdminRestControllerTest.java | 4 ++-- 7 files changed, 14 insertions(+), 17 deletions(-) rename src/test/java/ru/javawebinar/topjava/{web/user => }/UserTestData.java (92%) rename src/test/java/ru/javawebinar/topjava/service/{MealServiceTest.java => AbstractMealServiceTest.java} (97%) diff --git a/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java similarity index 92% rename from src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java rename to src/test/java/ru/javawebinar/topjava/UserTestData.java index e3a98679ca08..774ee514ccb3 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -1,4 +1,4 @@ -package ru.javawebinar.topjava.web.user; +package ru.javawebinar.topjava; import ru.javawebinar.topjava.MatcherFactory; import ru.javawebinar.topjava.model.Role; @@ -10,7 +10,7 @@ import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; public class UserTestData { - public static final MatcherFactory MATCHER = MatcherFactory.usingIgnoringFieldsComparator("registered", "roles"); + public static final MatcherFactory MATCHER = MatcherFactory.usingIgnoringFieldsComparator("registered", "roles", "meals"); public static final int USER_ID = START_SEQ; public static final int ADMIN_ID = START_SEQ + 1; diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java index b31c9c9d33e0..6ed5ac3749e8 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java @@ -4,7 +4,7 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.MealTestData; -import ru.javawebinar.topjava.web.user.UserTestData; +import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.Util; @@ -17,9 +17,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import static ru.javawebinar.topjava.web.user.UserTestData.ADMIN_ID; -import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; - @Repository public class InMemoryMealRepository implements MealRepository { private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepository.class); diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java index 90526c36cb82..70dfd0a63ba4 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.repository.inmemory; import org.springframework.stereotype.Repository; -import ru.javawebinar.topjava.web.user.UserTestData; +import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; @@ -10,8 +10,8 @@ import java.util.Objects; import java.util.stream.Collectors; -import static ru.javawebinar.topjava.web.user.UserTestData.admin; -import static ru.javawebinar.topjava.web.user.UserTestData.user; +import static ru.javawebinar.topjava.UserTestData.admin; +import static ru.javawebinar.topjava.UserTestData.user; @Repository diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java similarity index 97% rename from src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 1dc534525dfa..c7fc0421f9b8 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -26,8 +26,8 @@ import static org.junit.Assert.assertThrows; import static org.slf4j.LoggerFactory.getLogger; import static ru.javawebinar.topjava.MealTestData.*; -import static ru.javawebinar.topjava.web.user.UserTestData.ADMIN_ID; -import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.USER_ID; @ContextConfiguration({ "classpath:spring/spring-app.xml", diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index c34258f7cc4f..ce33bd8084b9 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -12,7 +12,7 @@ import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; import ru.javawebinar.topjava.ActiveDbProfileResolver; -import ru.javawebinar.topjava.web.user.UserTestData; +import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -21,7 +21,7 @@ import java.util.List; import static org.junit.Assert.assertThrows; -import static ru.javawebinar.topjava.web.user.UserTestData.*; +import static ru.javawebinar.topjava.UserTestData.*; @ContextConfiguration({ "classpath:spring/spring-app.xml", diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java index 46ca7a68fdcf..9102d2104634 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -10,8 +10,8 @@ import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository; import ru.javawebinar.topjava.util.exception.NotFoundException; -import static ru.javawebinar.topjava.web.user.UserTestData.NOT_FOUND; -import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; +import static ru.javawebinar.topjava.UserTestData.NOT_FOUND; +import static ru.javawebinar.topjava.UserTestData.USER_ID; @ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/inmemory.xml"}) @RunWith(SpringRunner.class) diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java index ddb2b75938a3..14c60d960a09 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -10,8 +10,8 @@ import java.util.Arrays; -import static ru.javawebinar.topjava.web.user.UserTestData.NOT_FOUND; -import static ru.javawebinar.topjava.web.user.UserTestData.USER_ID; +import static ru.javawebinar.topjava.UserTestData.NOT_FOUND; +import static ru.javawebinar.topjava.UserTestData.USER_ID; public class InMemoryAdminRestControllerTest { private static final Logger log = LoggerFactory.getLogger(InMemoryAdminRestControllerTest.class); From 2a79fa70488336bdb363ff29c594357b7b61c000 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:21:03 +0400 Subject: [PATCH 122/127] 6_01_HW5_data_jpa --- .../datajpa/CrudMealRepository.java | 10 ++++++- .../datajpa/DataJpaMealRepository.java | 26 +++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java index a3659675c910..d23dd9056eef 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -1,7 +1,15 @@ package ru.javawebinar.topjava.repository.datajpa; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; +import java.time.LocalDateTime; +import java.util.List; + +@Transactional(readOnly = true) public interface CrudMealRepository extends JpaRepository { -} +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java index d1b4c8efe9cc..a57c9742acc6 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.repository.datajpa; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -10,34 +11,43 @@ @Repository public class DataJpaMealRepository implements MealRepository { - private final CrudMealRepository crudRepository; + private final CrudMealRepository crudMealRepository; + private final CrudUserRepository crudUserRepository; - public DataJpaMealRepository(CrudMealRepository crudRepository) { - this.crudRepository = crudRepository; + public DataJpaMealRepository(CrudMealRepository crudMealRepository, CrudUserRepository crudUserRepository) { + this.crudMealRepository = crudMealRepository; + this.crudUserRepository = crudUserRepository; } @Override + @Transactional public Meal save(Meal meal, int userId) { - return null; + if (!meal.isNew() && get(meal.getId(), userId) == null) { + return null; + } + meal.setUser(crudUserRepository.getOne(userId)); + return crudMealRepository.save(meal); } @Override public boolean delete(int id, int userId) { - return false; + return crudMealRepository.delete(id, userId) != 0; } @Override public Meal get(int id, int userId) { - return null; + return crudMealRepository.findById(id) + .filter(meal -> meal.getUser().getId() == userId) + .orElse(null); } @Override public List getAll(int userId) { - return null; + return crudMealRepository.getAll(userId); } @Override public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { - return null; + return crudMealRepository.getBetweenHalfOpen(startDateTime, endDateTime, userId); } } From eefc9ea3823018908d75c26688c04d64e101e1c7 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:21:13 +0400 Subject: [PATCH 123/127] 6_02_HW5_profile_test --- src/main/resources/spring/spring-db.xml | 84 +++++++++---------- .../service/AbstractMealServiceTest.java | 46 +--------- .../topjava/service/AbstractServiceTest.java | 29 +++++++ .../service/AbstractUserServiceTest.java | 2 +- .../datajpa/DataJpaMealServiceTest.java | 30 +++++++ .../datajpa/DataJpaUserServiceTest.java | 30 +++++++ .../service/jdbc/JdbcMealServiceTest.java | 10 +++ .../service/jdbc/JdbcUserServiceTest.java | 10 +++ .../service/jpa/JpaMealServiceTest.java | 10 +++ .../service/jpa/JpaUserServiceTest.java | 10 +++ 10 files changed, 173 insertions(+), 88 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index e165ce8a2512..47502d38674f 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -11,53 +11,12 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -98,9 +57,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index c7fc0421f9b8..dc160b905d20 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,69 +1,25 @@ package ru.javawebinar.topjava.service; -import org.junit.AfterClass; import org.junit.Assert; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.Stopwatch; -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.time.LocalDate; import java.time.Month; -import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertThrows; -import static org.slf4j.LoggerFactory.getLogger; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; -@ContextConfiguration({ - "classpath:spring/spring-app.xml", - "classpath:spring/spring-db.xml" -}) -@RunWith(SpringRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(resolver = ActiveDbProfileResolver.class) -public class MealServiceTest { - private static final Logger log = getLogger("result"); - - private static final StringBuilder results = new StringBuilder(); - - @Rule - // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev - public final Stopwatch stopwatch = new Stopwatch() { - @Override - protected void finished(long nanos, Description description) { - String result = String.format("\n%-25s %7d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); - results.append(result); - log.info(result + " ms\n"); - } - }; +public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired private MealService service; - @AfterClass - public static void printResult() { - log.info("\n---------------------------------" + - "\nTest Duration, ms" + - "\n---------------------------------" + - results + - "\n---------------------------------"); - } - @Test public void delete() { service.delete(MEAL1_ID, USER_ID); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java new file mode 100644 index 000000000000..036d28c82434 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava.service; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.rules.ExternalResource; +import org.junit.rules.Stopwatch; +import org.junit.runner.RunWith; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.ActiveDbProfileResolver; +import ru.javawebinar.topjava.TimingRules; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) +public abstract class AbstractServiceTest { + @ClassRule + public static ExternalResource summary = TimingRules.SUMMARY; + + @Rule + public Stopwatch stopwatch = TimingRules.STOPWATCH; +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index ce33bd8084b9..fc19c7db9aa0 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -30,7 +30,7 @@ @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) @ActiveProfiles(resolver = ActiveDbProfileResolver.class) -public class UserServiceTest { +public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private UserService service; diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java new file mode 100644 index 000000000000..c7a2d9c9137b --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -0,0 +1,30 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.admin; + +@ActiveProfiles(DATAJPA) +public class DataJpaMealServiceTest extends AbstractMealServiceTest { + @Test + public void getWithUser() { + Meal adminMeal = service.getWithUser(ADMIN_MEAL_ID, ADMIN_ID); + MATCHER.assertMatch(adminMeal, adminMeal1); + UserTestData.MATCHER.assertMatch(adminMeal.getUser(), admin); + } + + @Test + public void getWithUserNotFound() { + Assert.assertThrows(NotFoundException.class, + () -> service.getWithUser(1, ADMIN_ID)); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java new file mode 100644 index 000000000000..f773e3dc7e21 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -0,0 +1,30 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.UserTestData.MATCHER; +import static ru.javawebinar.topjava.UserTestData.USER_ID; + +@ActiveProfiles(DATAJPA) +public class DataJpaUserServiceTest extends AbstractUserServiceTest { + @Test + public void getWithMeals() { + User user = service.getWithMeals(USER_ID); + MATCHER.assertMatch(user, UserTestData.user); + MealTestData.MATCHER.assertMatch(user.getMeals(), MealTestData.meals); + } + + @Test + public void getWithMealsNotFound() { + Assert.assertThrows(NotFoundException.class, + () -> service.getWithMeals(1)); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java new file mode 100644 index 000000000000..9ff4ae615b2b --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jdbc; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JDBC; + +@ActiveProfiles(JDBC) +public class JdbcMealServiceTest extends AbstractMealServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java new file mode 100644 index 000000000000..419f68ed1098 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jdbc; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.JDBC; + +@ActiveProfiles(JDBC) +public class JdbcUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java new file mode 100644 index 000000000000..70e7bf865421 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JPA; + +@ActiveProfiles(JPA) +public class JpaMealServiceTest extends AbstractMealServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java new file mode 100644 index 000000000000..d1b3e4699785 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.JPA; + +@ActiveProfiles(JPA) +public class JpaUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file From a4875e14b77f19650b576b8166ade4cfcc1d210f Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:21:23 +0400 Subject: [PATCH 124/127] 6_03_extract_rules --- .../ru/javawebinar/topjava/TimingRules.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/TimingRules.java diff --git a/src/test/java/ru/javawebinar/topjava/TimingRules.java b/src/test/java/ru/javawebinar/topjava/TimingRules.java new file mode 100644 index 000000000000..fdd3d8779dbc --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TimingRules.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava; + +import org.junit.rules.ExternalResource; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public class TimingRules { + private static final Logger log = LoggerFactory.getLogger("result"); + + private static final StringBuilder results = new StringBuilder(); + + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public static final Stopwatch STOPWATCH = new Stopwatch() { + @Override + protected void finished(long nanos, Description description) { + String result = String.format("%-95s %7d", description.getDisplayName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result).append('\n'); + log.info(result + " ms\n"); + } + }; + + // https://dzone.com/articles/applying-new-jdk-11-string-methods + private static final String DELIM = "-".repeat(103); + + public static final ExternalResource SUMMARY = new ExternalResource() { + @Override + protected void before() throws Throwable { + results.setLength(0); + } + + @Override + protected void after() { + log.info("\n" + DELIM + + "\nTest Duration, ms" + + "\n" + DELIM + "\n" + results + DELIM + "\n"); + } + }; +} From ae8fcc0b5927b168d9ca684abc25acadb4852d8b Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:21:32 +0400 Subject: [PATCH 125/127] 6_05_update_hsqldb --- pom.xml | 2 +- .../topjava/repository/jdbc/JdbcMealRepository.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b40e1885aedd..1a039ceac371 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ org.hsqldb hsqldb - 2.3.4 + 2.6.0 diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java index 91f4001aa867..09beed68cd2d 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java @@ -9,6 +9,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.MealRepository; @@ -23,6 +24,7 @@ import java.time.LocalDateTime; import java.util.List; + @Repository public class JdbcMealRepository implements MealRepository { @@ -34,7 +36,7 @@ public class JdbcMealRepository implements MealRepository { private final SimpleJdbcInsert insertMeal; - @Autowired + public JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) .withTableName("meals") @@ -44,6 +46,8 @@ public JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; } + + @Override public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() From 33d93cc5c0e6d451dbb36fefd52884e60236ac54 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:21:46 +0400 Subject: [PATCH 126/127] 6_06_HW5_optional_fetch_join --- .../java/ru/javawebinar/topjava/model/User.java | 13 +++++++++---- .../topjava/repository/MealRepository.java | 4 ++++ .../topjava/repository/UserRepository.java | 4 ++++ .../repository/datajpa/CrudMealRepository.java | 14 ++++++++++++++ .../repository/datajpa/DataJpaMealRepository.java | 5 +++++ .../repository/datajpa/DataJpaUserRepository.java | 5 +++++ .../javawebinar/topjava/service/MealService.java | 4 ++++ .../javawebinar/topjava/service/UserService.java | 4 ++++ .../topjava/service/AbstractMealServiceTest.java | 2 +- .../topjava/service/AbstractUserServiceTest.java | 2 +- 10 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index da7c839cbea3..78f2ea69d01f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -8,10 +8,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -import java.util.Collection; -import java.util.Date; -import java.util.EnumSet; -import java.util.Set; +import java.util.*; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; @@ -57,6 +54,10 @@ public class User extends AbstractNamedEntity { @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") + @OrderBy("dateTime DESC") + private List meals; + public User() { } @@ -126,6 +127,10 @@ public void setRoles(Collection roles) { this.roles = CollectionUtils.isEmpty(roles) ? EnumSet.noneOf(Role.class) : EnumSet.copyOf(roles); } + public List getMeals() { + return meals; + } + @Override public String toString() { return "User{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index 25aa5d087fce..86beb3fb6ac3 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -20,4 +20,8 @@ public interface MealRepository { // ORDERED dateTime desc List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId); + + default Meal getWithUser(int id, int userId) { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java index 138369789175..9fecbddaaa19 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -18,4 +18,8 @@ public interface UserRepository { User getByEmail(String email); List getAll(); + + default User getWithMeals(int id) { + throw new UnsupportedOperationException(); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java index d23dd9056eef..9aeef134f236 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -12,4 +12,18 @@ @Transactional(readOnly = true) public interface CrudMealRepository extends JpaRepository { + + @Modifying + @Transactional + @Query("DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId") + int delete(@Param("id") int id, @Param("userId") int userId); + + @Query("SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC") + List getAll(@Param("userId") int userId); + + @Query("SELECT m from Meal m WHERE m.user.id=:userId AND m.dateTime >= :startDate AND m.dateTime < :endDate ORDER BY m.dateTime DESC") + List getBetweenHalfOpen(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate, @Param("userId") int userId); + + @Query("SELECT m FROM Meal m JOIN FETCH m.user WHERE m.id = ?1 and m.user.id = ?2") + Meal getWithUser(int id, int userId); } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java index a57c9742acc6..dc93b47e5c7b 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java @@ -50,4 +50,9 @@ public List getAll(int userId) { public List getBetweenHalfOpen(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { return crudMealRepository.getBetweenHalfOpen(startDateTime, endDateTime, userId); } + + @Override + public Meal getWithUser(int id, int userId) { + return crudMealRepository.getWithUser(id, userId); + } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java index bc240d2d5a90..608c855e0a05 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java @@ -41,4 +41,9 @@ public User getByEmail(String email) { public List getAll() { return crudRepository.findAll(SORT_NAME_EMAIL); } + + @Override + public User getWithMeals(int id) { + return crudRepository.getWithMeals(id); + } } diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index df874378eb82..5e08c9e5a3dc 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -47,4 +47,8 @@ public Meal create(Meal meal, int userId) { Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } + + public Meal getWithUser(int id, int userId) { + return checkNotFoundWithId(repository.getWithUser(id, userId), id); + } } \ 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 index e0979b7ee4fd..2fa248762d62 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -51,4 +51,8 @@ public void update(User user) { Assert.notNull(user, "user must not be null"); checkNotFoundWithId(repository.save(user), user.id()); } + + public User getWithMeals(int id) { + return checkNotFoundWithId(repository.getWithMeals(id), id); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index dc160b905d20..c78f74fa61e4 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -18,7 +18,7 @@ public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired - private MealService service; + protected MealService service; @Test public void delete() { diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index fc19c7db9aa0..83e78ff11580 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -33,7 +33,7 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired - private UserService service; + protected UserService service; @Autowired private CacheManager cacheManager; From 219439764db7326861ee50298bc2c56919ac39c3 Mon Sep 17 00:00:00 2001 From: login Date: Tue, 14 Jun 2022 12:21:56 +0400 Subject: [PATCH 127/127] 6_07_HW5_graph_batch_size --- src/main/java/ru/javawebinar/topjava/model/User.java | 5 ++++- .../topjava/repository/datajpa/CrudUserRepository.java | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 78f2ea69d01f..faded2d499de 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import org.hibernate.annotations.BatchSize; import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; @@ -15,7 +16,7 @@ @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"), + @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u ORDER BY u.name, u.email"), }) @Entity @Table(name = "users") @@ -48,6 +49,8 @@ public class User extends AbstractNamedEntity { uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "role"}, name = "uk_user_roles")}) @Column(name = "role") @ElementCollection(fetch = FetchType.EAGER) +// @Fetch(FetchMode.SUBSELECT) + @BatchSize(size = 200) private Set roles; @Column(name = "calories_per_day", nullable = false, columnDefinition = "int default 2000") diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java index 24c42a814220..806884a9c90b 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.repository.datajpa; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -11,9 +12,12 @@ public interface CrudUserRepository extends JpaRepository { @Transactional @Modifying -// @Query(name = User.DELETE) @Query("DELETE FROM User u WHERE u.id=:id") int delete(@Param("id") int id); User getByEmail(String email); + + @EntityGraph(attributePaths = {"meals", "roles"}) + @Query("SELECT u FROM User u WHERE u.id=?1") + User getWithMeals(int id); }