From aa6c1516297fccba768a22b0a029c28ef26a24d5 Mon Sep 17 00:00:00 2001 From: Grigory Kislin Date: Sat, 13 Feb 2016 17:02:55 +0300 Subject: [PATCH 001/166] Add description --- README.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ cv.md | 55 +++++++++++++++++++++++++ description.md | 67 +++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 README.md create mode 100644 cv.md create mode 100644 description.md diff --git a/README.md b/README.md new file mode 100644 index 000000000000..786ec1b557bb --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +Java Enterprise Online Project +=============================== +Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: +Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. + + Когда вы слышите что-то, вы забываете это. + Когда вы видите что-то, вы запоминаете это. + Но только когда вы начинаете делать это, + вы начинаете понимать это + + Старинная китайская поговорка + +## Описание и план проекта +### Демо разрабатываемого приложения +### Требования к участникам, Wiki + +Вводное занятие +=============== +## ![video](http://s.ytimg.com/yts/img/favicon-vflz7uhzw.ico) Системы управления версиями. Git. + +- Система управления версиями. VCS/DVSC. +- Ресурсы: + - Интерактивная Git обучалка + - Основы Git + - Working with remote repositories + - Видео по обучению Git + - Как начать работать с GitHub: быстрый старт + - Справочник в графическом виде + - Git Overview + - Видеокурс по Git + +## ![video](http://s.ytimg.com/yts/img/favicon-vflz7uhzw.ico) Работа с проектом (выполнять инструкции) +- **Prepare_ to_ HW0.patch (скачать и положить в каталог вашего проекта)** +- Доступно о Java 8 Lambda + +## Инструкция по шагам (из видео): +- Установить ПО (git, JDK8, IntelliJ IDEA, Maven) +- Создать аккаунт на GitHub +- Сделать Fork ЭТОГО проекта (https://github.com/JavaOPs/topjava) +- Сделать локальный репозиторий проекта: +
git clone https://github.com/[Ваш аккаунт]/topjava.git
+- Открыть и настроить проект в IDEA +- По ходу видео сделать Apply Patch... скаченного патча Prepare_ to_ HW0.patch +- Закоммитить и запушить изменения (commit + push) +- Сделать ветку домашнего задания +- Выполнить задание и залить на GitHub (commit + push) +- Переключиться в основную ветку проекта master. + +## Домашнее задание HW0 + +- Java 8 Date and Time API +- Time complexity +- Временная сложность алгоритма +- Вычислительная сложность +``` +Реализовать UserMealsUtil.getFilteredMealsWithExceeded: +- должны возвращаться только записи между startTime и endTime +- поле UserMealWithExceed.exceed должно показывать, + превышает ли сумма калорий за весь день параметра метода caloriesPerDay + +Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. + +- Проверте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) +- Оцените Time complexity вашего алгоритма (если он O(N*N)- попробуйте сделать O(N). +``` +Optional + +- Java 8: Lambda выражения +- Java 8: Потоки +- Pуководство по Java 8 Stream +- Лямбда-выражения в Java 8 +- Шпаргалка Java Stream API +- stream.map vs forEach + +``` +Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) +``` +### Составление резюме, подготовка к интервью, поиск работы +### Ресурсы для подготовки к проекту +#### (желательно иметь представление для лучшего усвоения материала) + +#### Java Web (Servlets, JSP) +- Введение в сетевое программирование +- Основы Java на реальном примере (Servlets, JSP) +- Как создать Servlet? Полное руководство. +- Технологии Java для разработки веб-приложений + +#### HTML, JavaScript, CSS +- Основы работы с HTML/CSS/JavaScript +- jQuery для начинающих +- Уроки Bootstrap 3 + +#### Java Core (Reflection API, JUnit) +- Введение в Java Reflection API +- Java JUnit + +#### JDBC, SQL +- Уроки по JDBC +- Learn SQL +- Try SQL + +#### Разное +- Интуит. Программирование на Java +- Oracle Java tutorial на русском. +- Эффективная работа с кодом в IntelliJ IDEA diff --git a/cv.md b/cv.md new file mode 100644 index 000000000000..95a53c28f61a --- /dev/null +++ b/cv.md @@ -0,0 +1,55 @@ +## Составление резюме, подготовка к интервью, поиск работы + +![cv](https://cloud.githubusercontent.com/assets/13649199/10877471/93ea86b8-8157-11e5-9bfa-95e3fba75c58.jpg) + +- Научиться программировать сложнее, чем кажется + +### Составление резюме: +- Идеальное резюме Junior’а +- Как продать свое резюме в 2 раза дороже +- Как правильно составить резюме +- Резюме программистов. Часть 1 (плохие) +- Резюме программистов. Часть 2 (хорошие) +- Как составить резюме на английском + +### Интервью: +- Михаил Портнов. Собеседование на работу: как продать себя грамотно +- Михаил Портнов. Какие вопросы мы задаем на собеседовании? +- Канал: Резюме, поиск работы, интервью +- Яков Файн: Как стать профессиональным Java разработчиком +- Quizful- тесты онлайн +- Сборка по вопросам на интервью +- Тест на знание SQL +- Сборка вопросов от JavaRush +> про clone и finalize объязательно прочтите Джошуа Блох: Java. Эффективное программирование (второе издание) + +- Cracking the Coding Interview +> Особенно обратите внимание на раздел: Часть VIII. Вопросы собеседования + +### От себя: +- email, skype - очень желательно, чтобы по ним вы были узнаваемы. Заведите рабочие, если не так. +- написать ВЕСЬ IT опыт (исключая опыт пользователя: Windows, MS Word, Photophop, Yandex disk, Google docs, ..): технологии, какие задачи решали (конкретные), какие инструменты использовали, VCS, DB, инструменты сборки, ... включая опыт в ВУЗе. +- на English иметь желательно. Если вакансия опублинована на Englsih - шлите на нем. Часто могут на нем попросить, если работодатель иностранный. +- удобно иметь резюме где то в инете (hh, linkedin, google doc, чтобы им было удобно делиться). + +### Позиционирование проекта Topjava: +- После завершения проекта вы освоите все заявленные в нем технологии - вставьте в квалификацию (включая java 8 Stream and Time API) +- Делайте упор не на обучение, а на *участие в проекте*. Выполнение домашних заданий - это полноценное участие с написанием функционала по всем этим технологиям. Например: + + Участие в разработке Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей + на стеке Maven/ Spring MVC/ Security/ REST(Jackson)/ Java 8 Stream API: + - реализация сохранения в базы Postgres и HSQLDB на основе Spring JBDC, JPA(Hibernate) и Spring-Data-JPA + - реализация и тестирование REST и AJAX контроллеров + - реализация клиента на Bootstrap (css/js), datatables, jQuery + plugins. + - отлично будет, если вы его еще как то сами доработаете + +- По поводу обучения на курсах- можно упомянуть в разделе образования. Но на собеседовании меньше смотрят на то, что вы заканчивали, больше - на ваш опыт и то что вы знаете. + +### В течении проекта заполните форму соискателя "Разработчик Java" +Я ее рассылаю всем знакомым HR, уже есть пинги и трудоустройство. + +### Основные сайты поиска работы: +- HH +- LinkedIn +- djinni.co (более актуально для Украины) +- moikrug diff --git a/description.md b/description.md new file mode 100644 index 000000000000..0133bf3c83e8 --- /dev/null +++ b/description.md @@ -0,0 +1,67 @@ +#### Разработка полнофункционального 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 +- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения. +- Каждая итерация проекта в закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий. +- Большое внимание уделяется тестированию кода: в проекте 88 JUnit тестов. +- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например мы используем кэш 2-го уровня Hibernate, настраиваем Jenkins для работы с ленивой загрузкой +Hibernate, делаем конверторы для типов LocalDateTime (Java 8 time API), которые еще не поддерживаются ни JPA/Hibernate, ни Jackson/json). + Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применят 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. +- Поддержка 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. Datatables. +- AJAX. jQuery. Notifications. +- Spring Security +- Spring Binding/Validation +- Работа с Datatables через Ajax. +- Spring Security Test +- Encoding password +- CSRF (добавление в Spring проект защиты от межсайтовой подделки запроса) +- form-login. Spring Security Taglib +- Handler interceptor +- Spring Exception Handling +- Деплой в Heroku From 67eb1c848fc88ad95568e1ef772fd7ff7cdb8cce Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 17 Feb 2016 01:24:38 +0300 Subject: [PATCH 002/166] Update description.md --- description.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/description.md b/description.md index 0133bf3c83e8..f12cc888bf75 100644 --- a/description.md +++ b/description.md @@ -17,8 +17,8 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja ## План проекта (ссылки на некоторые темы открыты для просмотра) ### Архитектура проекта. Персистентность. -- Системы управления версиями -- Java 8: Lambda, Stream API +- Системы управления версиями +- Java 8: Lambda, Stream API - Обзор используемых в проекте технологий и инструментов. - Инструмент сборки Maven. - WAR. Веб-контейнер Tomcat. Сервлеты. From 53793a82849a3b864f863aa4244beff038f5030e Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 20 Feb 2016 00:39:41 +0300 Subject: [PATCH 003/166] Update description.md --- description.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/description.md b/description.md index f12cc888bf75..cbb102fa72aa 100644 --- a/description.md +++ b/description.md @@ -1,10 +1,10 @@ -#### Разработка полнофункционального 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 +#### Разработка полнофункционального 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 - Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения. - Каждая итерация проекта в закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий. - Большое внимание уделяется тестированию кода: в проекте 88 JUnit тестов. - Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например мы используем кэш 2-го уровня Hibernate, настраиваем Jenkins для работы с ленивой загрузкой Hibernate, делаем конверторы для типов LocalDateTime (Java 8 time API), которые еще не поддерживаются ни JPA/Hibernate, ни Jackson/json). - Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применят Data Transfer Object. + Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object. Т.е на выходе получается не учебный проект, а хорошо маштабируемый шаблон для большого проекта на всех пройденных технологиях. - Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST контроллеров, насторойка EntityManagerFactory, @@ -60,7 +60,7 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - Работа с Datatables через Ajax. - Spring Security Test - Encoding password -- CSRF (добавление в Spring проект защиты от межсайтовой подделки запроса) +- CSRF (добавление в проект защиты от межсайтовой подделки запроса) - form-login. Spring Security Taglib - Handler interceptor - Spring Exception Handling From acc204b48f4cd1d7666c3dc4aceccf5c960466be Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 25 Feb 2016 23:02:15 +0300 Subject: [PATCH 004/166] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 786ec1b557bb..61037dac999a 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,13 @@ Optional ``` Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) ``` + +### Ваши вопросы +> Как брать диапазон дат? Какие брать дефолтные значения для null? + +Для диапазона нужно использовать уже готовый `TimeUtil.isBetween`. Как приложение должно себя вести помно проверить на demo. + + ### Составление резюме, подготовка к интервью, поиск работы ### Ресурсы для подготовки к проекту #### (желательно иметь представление для лучшего усвоения материала) From dd170042264af360946506340b768aa427f31938 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 25 Feb 2016 23:03:02 +0300 Subject: [PATCH 005/166] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61037dac999a..613028cc00aa 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Optional ### Ваши вопросы > Как брать диапазон дат? Какие брать дефолтные значения для null? -Для диапазона нужно использовать уже готовый `TimeUtil.isBetween`. Как приложение должно себя вести помно проверить на demo. +Для диапазона нужно использовать уже готовый `TimeUtil.isBetween`. Как приложение должно себя вести можно проверить на demo. ### Составление резюме, подготовка к интервью, поиск работы From 077eed89ebba29a7c94e13a3be00478eec0e1228 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 27 Feb 2016 21:52:40 +0500 Subject: [PATCH 006/166] prepare to HW0 --- .../javawebinar/topjava/model/UserMeal.java | 33 +++++++++++++++++ .../topjava/model/UserMealWithExceed.java | 24 +++++++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 13 +++++++ .../topjava/util/UserMealsUtil.java | 35 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMeal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.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..fde5448b93d8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -0,0 +1,33 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +/** + * GKislin + * 11.01.2015. + */ +public class UserMeal { + protected final LocalDateTime dateTime; + + protected final String description; + + protected 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/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java new file mode 100644 index 000000000000..dab6c9468c5e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +/** + * GKislin + * 11.01.2015. + */ +public class UserMealWithExceed { + protected final LocalDateTime dateTime; + + protected final String description; + + protected final int calories; + + protected final boolean exceed; + + public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.exceed = exceed; + } +} 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..02399b7288cf --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -0,0 +1,13 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalTime; + +/** + * GKislin + * 07.01.2015. + */ +public class TimeUtil { + public static boolean isBetween(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..2cb426400fa5 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -0,0 +1,35 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.model.UserMealWithExceed; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +/** + * GKislin + * 31.05.2015. + */ +public class UserMealsUtil { + public static void main(String[] args) { + List mealList = Arrays.asList( + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) + ); + getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +// .toLocalDate(); +// .toLocalTime(); + } + + public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO return filtered list with correctly exceeded field + return null; + } +} From 5b38bbfeb578184d54dd460743ab05d01c20cb19 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 27 Feb 2016 23:31:32 +0500 Subject: [PATCH 007/166] HW0 with fori --- .gitignore | 2 + .../topjava/util/UserMealsUtil.java | 49 ++++++++++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 6ca05c4a5184..9629b3660e44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.gitignore .idea out target @@ -6,3 +7,4 @@ log +/Prepare_to_HW0.patch diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 2cb426400fa5..6364c2df2bea 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -6,6 +6,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -16,20 +17,52 @@ public class UserMealsUtil { public static void main(String[] args) { List mealList = Arrays.asList( + new UserMeal(LocalDateTime.of(2015, Month.MAY, 29,10,0), "Завтрак", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 29,13,0), "Обед ", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 29,20,0), "Ужин ", 501), new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед ", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин ", 500), new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед ", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин ", 510) ); getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); -// .toLocalDate(); -// .toLocalTime(); } public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with correctly exceeded field - return null; + List resulList = new ArrayList<>(); + UserMeal userMeal; + LocalDateTime localDateTime; + int dayOfMonth=mealList.get(0).getDateTime().getDayOfMonth(); + boolean exceed; + int sumOfCaloriesDay = 0; + int countOfDays=0; + for (int i = 0; i < mealList.size(); i++) { + userMeal = mealList.get(i); + localDateTime = userMeal.getDateTime(); + if (dayOfMonth==localDateTime.getDayOfMonth()){ + sumOfCaloriesDay = sumOfCaloriesDay +userMeal.getCalories(); + countOfDays++; + } + if (dayOfMonth != localDateTime.getDayOfMonth() || i == (mealList.size()-1)){ + if (sumOfCaloriesDay >caloriesPerDay){ + exceed=true; + } else { + exceed=false; + } + for (int j = (i-countOfDays); j < i; j++) { + System.out.println(" "+mealList.get(j).getDateTime().toLocalTime()+" "+startTime+" "+endTime); + if (TimeUtil.isBetween(mealList.get(j).getDateTime().toLocalTime(),startTime,endTime)){ + resulList.add(new UserMealWithExceed(mealList.get(j).getDateTime(), mealList.get(j). + getDescription(), mealList.get(j).getCalories(), exceed)); + } + } + dayOfMonth = mealList.get(i).getDateTime().getDayOfMonth(); + sumOfCaloriesDay = userMeal.getCalories(); + countOfDays = 1; + } + } + return resulList; } } From 9842b2af1b74fb59c7a05084cbfebd417fe47f19 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 27 Feb 2016 23:33:25 +0500 Subject: [PATCH 008/166] HW0 with fori update#1 --- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 6364c2df2bea..9e279dcdc3b8 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -52,7 +52,6 @@ public static List getFilteredMealsWithExceeded(List Date: Sun, 6 Mar 2016 08:39:56 +0300 Subject: [PATCH 009/166] update usermealutil - --- .../java/ru/javawebinar/topjava/util/UserMealsUtil.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 9e279dcdc3b8..b1c32b21dfa8 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -17,9 +17,6 @@ public class UserMealsUtil { public static void main(String[] args) { List mealList = Arrays.asList( - new UserMeal(LocalDateTime.of(2015, Month.MAY, 29,10,0), "Завтрак", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 29,13,0), "Обед ", 1000), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 29,20,0), "Ужин ", 501), new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед ", 1000), new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин ", 500), @@ -46,11 +43,7 @@ public static List getFilteredMealsWithExceeded(ListcaloriesPerDay){ - exceed=true; - } else { - exceed=false; - } + exceed = sumOfCaloriesDay > caloriesPerDay; for (int j = (i-countOfDays); j < i; j++) { if (TimeUtil.isBetween(mealList.get(j).getDateTime().toLocalTime(),startTime,endTime)){ resulList.add(new UserMealWithExceed(mealList.get(j).getDateTime(), mealList.get(j). From 384609baa8fd45715e6374ef366e3ed209a6a73f Mon Sep 17 00:00:00 2001 From: ilnur Date: Sun, 6 Mar 2016 11:09:23 +0300 Subject: [PATCH 010/166] update usermealutil - --- .../topjava/model/UserMealWithExceed.java | 10 +++++++++ .../topjava/util/UserMealsUtil.java | 22 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java index dab6c9468c5e..66c356922f6b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java @@ -21,4 +21,14 @@ public UserMealWithExceed(LocalDateTime dateTime, String description, int calori this.calories = calories; this.exceed = exceed; } + + @Override + public String toString() { + return "UserMealWithExceed{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index b1c32b21dfa8..949adb575759 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -3,12 +3,15 @@ import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.model.UserMealWithExceed; +import java.time.LocalDate; 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; /** * GKislin @@ -24,10 +27,12 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед ", 500), new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин ", 510) ); - getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); + List filteredMealsWithExceeded = getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); + filteredMealsWithExceeded.forEach(System.out::println); } - public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +/* + public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { List resulList = new ArrayList<>(); UserMeal userMeal; LocalDateTime localDateTime; @@ -57,4 +62,17 @@ public static List getFilteredMealsWithExceeded(List getFilteredMealsWithExceeded (List mealList, LocalTime startTime, LocalTime endTime, + int caloriesPerDay){ + Map caloriesSumByDate = mealList.stream() + .collect(Collectors.groupingBy(userMeal -> userMeal.getDateTime().toLocalDate(), + Collectors.summingInt(UserMeal::getCalories))); + return mealList.stream() + .filter(um -> TimeUtil.isBetween(um.getDateTime().toLocalTime(), startTime, endTime)) + .map(um->new UserMealWithExceed(um.getDateTime(), um.getDescription(),um.getCalories(), + caloriesSumByDate.get(um.getDateTime().toLocalDate())>caloriesPerDay)) + .collect(Collectors.toList()); + } } + From 6fe64c91538da03f87657d1aa2b3fda861c7e62e Mon Sep 17 00:00:00 2001 From: ilnur Date: Sun, 6 Mar 2016 11:10:59 +0300 Subject: [PATCH 011/166] update usermealutil - --- .../topjava/model/UserMealWithExceed.java | 10 ++++++ .../topjava/util/UserMealsUtil.java | 36 ++++++++++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java index dab6c9468c5e..66c356922f6b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java @@ -21,4 +21,14 @@ public UserMealWithExceed(LocalDateTime dateTime, String description, int calori this.calories = calories; this.exceed = exceed; } + + @Override + public String toString() { + return "UserMealWithExceed{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 2cb426400fa5..8252485855f3 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -3,11 +3,14 @@ import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.model.UserMealWithExceed; +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; /** * GKislin @@ -16,20 +19,29 @@ public class UserMealsUtil { public static void main(String[] args) { List mealList = Arrays.asList( - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) ); - getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); -// .toLocalDate(); -// .toLocalTime(); + List filteredMealsWithExceeded = getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + filteredMealsWithExceeded.forEach(System.out::println); } - public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with correctly exceeded field - return null; + public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = mealList.stream() + .collect( + Collectors.groupingBy(um -> um.getDateTime().toLocalDate(), + Collectors.summingInt(UserMeal::getCalories)) + ); + + return mealList.stream() + .filter(um -> TimeUtil.isBetween(um.getDateTime().toLocalTime(), startTime, endTime)) + .map(um -> + new UserMealWithExceed(um.getDateTime(), um.getDescription(), um.getCalories(), + caloriesSumByDate.get(um.getDateTime().toLocalDate()) > caloriesPerDay)) + .collect(Collectors.toList()); } } From d382a27115e31d7ae27bb6c8b64d3c82cd881e3d Mon Sep 17 00:00:00 2001 From: ilnur Date: Sun, 6 Mar 2016 11:32:57 +0300 Subject: [PATCH 012/166] update gitignore, add 1-2 patch --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9629b3660e44..0d7c9a27a7a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.gitignore .idea +1-1-HW0-stream.patch out target *.iml From da4722fb431a5d7badeb56236b4d65be885f008d Mon Sep 17 00:00:00 2001 From: ilnur Date: Sun, 6 Mar 2016 11:33:50 +0300 Subject: [PATCH 013/166] update gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0d7c9a27a7a6..ec40fcac71ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ /.gitignore .idea -1-1-HW0-stream.patch out target *.iml log +*.patch From 0a40da9fb0f38df39986f1dad3385a322da73060 Mon Sep 17 00:00:00 2001 From: ilnur Date: Sun, 6 Mar 2016 11:34:19 +0300 Subject: [PATCH 014/166] update util --- .../topjava/util/UserMealsUtil.java | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 8252485855f3..ed7fef004529 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -7,9 +7,7 @@ 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.*; import java.util.stream.Collectors; /** @@ -28,6 +26,8 @@ public static void main(String[] args) { ); List filteredMealsWithExceeded = getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); filteredMealsWithExceeded.forEach(System.out::println); + + System.out.println(getFilteredMealsWithExceededByCycle(mealList, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -39,9 +39,26 @@ public static List getFilteredMealsWithExceeded(List TimeUtil.isBetween(um.getDateTime().toLocalTime(), startTime, endTime)) - .map(um -> - new UserMealWithExceed(um.getDateTime(), um.getDescription(), um.getCalories(), - caloriesSumByDate.get(um.getDateTime().toLocalDate()) > caloriesPerDay)) + .map(um -> createWithExceed(um, caloriesSumByDate.get(um.getDateTime().toLocalDate()) > caloriesPerDay)) .collect(Collectors.toList()); } -} + + public static List getFilteredMealsWithExceededByCycle(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + + final Map caloriesSumPerDate = new HashMap<>(); + mealList.forEach(meal -> caloriesSumPerDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum)); + + final List mealExceeded = new ArrayList<>(); + mealList.forEach(meal -> { + final LocalDateTime dateTime = meal.getDateTime(); + if (TimeUtil.isBetween(dateTime.toLocalTime(), startTime, endTime)) { + mealExceeded.add(createWithExceed(meal, caloriesSumPerDate.get(dateTime.toLocalDate()) > caloriesPerDay)); + } + }); + return mealExceeded; + } + + public static UserMealWithExceed createWithExceed(UserMeal um, boolean exceeded) { + return new UserMealWithExceed(um.getDateTime(), um.getDescription(), um.getCalories(), exceeded); + } +} \ No newline at end of file From 01345bfc09ab43fbed5eefd84a1d20926c333898 Mon Sep 17 00:00:00 2001 From: ilnur Date: Sun, 6 Mar 2016 11:56:31 +0300 Subject: [PATCH 015/166] apply HW1-3 --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 18 ++++++++++++++++++ src/main/webapp/index.html | 10 ++++++++++ src/main/webapp/userList.jsp | 9 +++++++++ 4 files changed, 38 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/userList.jsp diff --git a/pom.xml b/pom.xml index c8a1c78f3b29..6f0e7fd7cf47 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..edca9575b44b --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,18 @@ + + + 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..1937333f0dbe --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,10 @@ + + + + + Top Java. + + +

Приложение вебинара "Top Java."

+ + \ No newline at end of file diff --git a/src/main/webapp/userList.jsp b/src/main/webapp/userList.jsp new file mode 100644 index 000000000000..c6b12052a263 --- /dev/null +++ b/src/main/webapp/userList.jsp @@ -0,0 +1,9 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + User list + + +

User list

+ + From 38b7ca31fe3369d201d03c32eebd0a20a50c27f5 Mon Sep 17 00:00:00 2001 From: ilnur Date: Sun, 6 Mar 2016 15:46:33 +0300 Subject: [PATCH 016/166] apply patch 1-4, 1-5 --- pom.xml | 10 +++++++++- .../javawebinar/topjava/web/UserServlet.java | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/pom.xml b/pom.xml index 6f0e7fd7cf47..70d34d406c59 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ topjava - install + package org.apache.maven.plugins @@ -34,6 +34,14 @@ + + + + javax.servlet + servlet-api + 2.5 + 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..70ce24c6c494 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava.web; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class UserServlet extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { +// request.getRequestDispatcher("/userList.jsp").forward(request, response); + response.sendRedirect("userList.jsp"); + } +} From 8104a56de9fabe754fb1c1c597c3b8378c9a4d8f Mon Sep 17 00:00:00 2001 From: ilnur Date: Mon, 7 Mar 2016 15:03:57 +0300 Subject: [PATCH 017/166] apply patch 1-6, 1-7 change path to file into logback.xml --- .gitignore | 1 - config/setenv.bat | 4 +++ pom.xml | 32 ++++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 8 +++++ src/main/resources/logback.xml | 33 +++++++++++++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 config/setenv.bat create mode 100644 src/main/resources/logback.xml diff --git a/.gitignore b/.gitignore index ec40fcac71ef..c82fcde2c451 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,4 @@ log *.patch - /Prepare_to_HW0.patch diff --git a/config/setenv.bat b/config/setenv.bat new file mode 100644 index 000000000000..ebbd659386f6 --- /dev/null +++ b/config/setenv.bat @@ -0,0 +1,4 @@ +rem run tomcat with JMX ability +rem Run Tomcat as admin +rem for remote connection add -Djava.rmi.server.hostname=TomcatServer_IP +set CATALINA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false diff --git a/pom.xml b/pom.xml index 70d34d406c59..c56f716d4cbf 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ 1.8 UTF-8 UTF-8 + + + 1.1.6 + 1.7.18 @@ -34,6 +38,34 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + org.slf4j + jul-to-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 70ce24c6c494..4a4706396789 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,18 +1,26 @@ package ru.javawebinar.topjava.web; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + 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; + /** * User: gkislin * Date: 19.08.2014 */ public class UserServlet extends HttpServlet { + private static final Logger LOG = getLogger(UserServlet.class); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + LOG.debug("redirect to userList"); +// request.getRequestDispatcher("/userList.jsp").forward(request, response); // request.getRequestDispatcher("/userList.jsp").forward(request, response); response.sendRedirect("userList.jsp"); } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..7f75cdc87d09 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,33 @@ + + + + + true + + + + + + + /home/ilnur/docs/repo/repoGithub/topjava/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From 7140ed6a1c131bf22d4f7adb1d870180ac74a6cc Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 17 Mar 2016 23:43:40 +0500 Subject: [PATCH 018/166] HW2 --- pom.xml | 5 +++ .../topjava/model/UserMealWithExceed.java | 24 ++++++++-- .../ru/javawebinar/topjava/util/TimeUtil.java | 8 ++++ .../topjava/util/UserMealsUtil.java | 31 ++++++++----- .../javawebinar/topjava/web/MealServlet.java | 25 +++++++++++ src/main/webapp/WEB-INF/web.xml | 10 +++++ src/main/webapp/index.html | 9 +++- src/main/webapp/mealList.jsp | 45 +++++++++++++++++++ src/main/webapp/userList.jsp | 3 +- 9 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/mealList.jsp diff --git a/pom.xml b/pom.xml index c56f716d4cbf..af41b1894a11 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,11 @@ runtime + + javax.servlet + jstl + 1.2 + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java index 66c356922f6b..935b943afeb6 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java @@ -7,13 +7,13 @@ * 11.01.2015. */ public class UserMealWithExceed { - protected final LocalDateTime dateTime; + private final LocalDateTime dateTime; - protected final String description; + private final String description; - protected final int calories; + private final int calories; - protected final boolean exceed; + private final boolean exceed; public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { this.dateTime = dateTime; @@ -22,6 +22,22 @@ public UserMealWithExceed(LocalDateTime dateTime, String description, int calori this.exceed = exceed; } + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExceed() { + return exceed; + } + @Override public String toString() { return "UserMealWithExceed{" + diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 02399b7288cf..115311fad6b7 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -1,13 +1,21 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.format.DateTimeFormatter; /** * GKislin * 07.01.2015. */ public class TimeUtil { + public static final DateTimeFormatter DATE_TME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static boolean isBetween(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_TME_FORMATTER); + } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index ed7fef004529..6ea1f206d9e7 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -15,22 +15,29 @@ * 31.05.2015. */ public class UserMealsUtil { + public static final List MEAL_LIST = Arrays.asList( + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) + ); + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + public static void main(String[] args) { - List mealList = Arrays.asList( - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), - new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) - ); - List filteredMealsWithExceeded = getFilteredMealsWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + List filteredMealsWithExceeded = getFilteredWithExceeded(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY); filteredMealsWithExceeded.forEach(System.out::println); - System.out.println(getFilteredMealsWithExceededByCycle(mealList, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededByCycle(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY)); + } + + public static List getWithExceeded(List mealList, int caloriesPerDay) { + return getFilteredWithExceeded(mealList, LocalTime.MIN, LocalTime.MAX, caloriesPerDay); } - public static List getFilteredMealsWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { Map caloriesSumByDate = mealList.stream() .collect( Collectors.groupingBy(um -> um.getDateTime().toLocalDate(), @@ -43,7 +50,7 @@ public static List getFilteredMealsWithExceeded(List getFilteredMealsWithExceededByCycle(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + public static List getFilteredWithExceededByCycle(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { final Map caloriesSumPerDate = new HashMap<>(); mealList.forEach(meal -> caloriesSumPerDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum)); 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..9e6ae04d69b2 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.util.UserMealsUtil; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class MealServlet extends HttpServlet { + private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + LOG.info("getAll"); + request.setAttribute("mealList", UserMealsUtil.getWithExceeded(UserMealsUtil.MEAL_LIST, UserMealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/mealList.jsp").forward(request, response); + } +} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index edca9575b44b..6306688d0998 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -15,4 +15,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 1937333f0dbe..e85ec56ddf0d 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -5,6 +5,11 @@ Top Java. -

Приложение вебинара "Top Java."

+

Проект "Top Java"

+
+ - \ No newline at end of file + diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/mealList.jsp new file mode 100644 index 000000000000..1d1f66d87f7f --- /dev/null +++ b/src/main/webapp/mealList.jsp @@ -0,0 +1,45 @@ +<%@ page import="ru.javawebinar.topjava.util.TimeUtil" %> +<%@ 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" %> + + + Meal list + + + +
+

Home

+

Meal list

+
+ + + + + + + + + + + + + + + + +
DateDescriptionCalories
+ ${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()} + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + ${meal.description}${meal.calories}
+
+ + \ No newline at end of file diff --git a/src/main/webapp/userList.jsp b/src/main/webapp/userList.jsp index c6b12052a263..eae4726d4400 100644 --- a/src/main/webapp/userList.jsp +++ b/src/main/webapp/userList.jsp @@ -4,6 +4,7 @@ User list -

User list

+

Home

+

User list

From a5346174c3ba935020f3a42c0f5ade34eca5248a Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Fri, 18 Mar 2016 00:00:13 +0500 Subject: [PATCH 019/166] HW2 optional --- .../javawebinar/topjava/model/UserMeal.java | 35 ++++++++++-- .../topjava/model/UserMealWithExceed.java | 14 ++++- .../InMemoryUserMealRepositoryImpl.java | 47 ++++++++++++++++ .../repository/UserMealRepository.java | 19 +++++++ .../topjava/util/UserMealsUtil.java | 8 +-- .../javawebinar/topjava/web/MealServlet.java | 53 +++++++++++++++++-- src/main/webapp/mealEdit.jsp | 51 ++++++++++++++++++ src/main/webapp/mealList.jsp | 5 ++ 8 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java create mode 100644 src/main/webapp/mealEdit.jsp diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index fde5448b93d8..fc43ba8b8b32 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -7,18 +7,29 @@ * 11.01.2015. */ public class UserMeal { - protected final LocalDateTime dateTime; + private Integer id; - protected final String description; + private final LocalDateTime dateTime; - protected final int calories; + private final String description; + + private final int calories; public UserMeal(LocalDateTime dateTime, String description, int calories) { + this(null, dateTime, description, calories); + } + + public UserMeal(Integer id, LocalDateTime dateTime, String description, int calories) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; } + public void setId(int id) { + this.id = id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -30,4 +41,22 @@ public String getDescription() { public int getCalories() { return calories; } + + public Integer getId() { + return id; + } + + public boolean isNew() { + return id == null; + } + + @Override + public String toString() { + return "UserMeal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java index 935b943afeb6..4f1fe2f6ae0f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java @@ -7,6 +7,8 @@ * 11.01.2015. */ public class UserMealWithExceed { + protected final Integer id; + private final LocalDateTime dateTime; private final String description; @@ -16,12 +18,21 @@ public class UserMealWithExceed { private final boolean exceed; public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + this(null, dateTime, description, calories, exceed); + } + + public UserMealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; this.exceed = exceed; } + public Integer getId() { + return id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -41,7 +52,8 @@ public boolean isExceed() { @Override public String toString() { return "UserMealWithExceed{" + - "dateTime=" + dateTime + + "id=" + id + + ", dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + ", exceed=" + exceed + diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepositoryImpl.java new file mode 100644 index 000000000000..4da0792bb19d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepositoryImpl.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.util.UserMealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * GKislin + * 15.09.2015. + */ +public class InMemoryUserMealRepositoryImpl implements UserMealRepository { + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + { + UserMealsUtil.MEAL_LIST.forEach(this::save); + } + + @Override + public UserMeal save(UserMeal userMeal) { + if (userMeal.isNew()) { + userMeal.setId(counter.incrementAndGet()); + } + repository.put(userMeal.getId(), userMeal); + return userMeal; + } + + @Override + public void delete(int id) { + repository.remove(id); + } + + @Override + public UserMeal get(int id) { + return repository.get(id); + } + + @Override + public Collection getAll() { + return repository.values(); + } +} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java new file mode 100644 index 000000000000..ad656ea6cd19 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.UserMeal; + +import java.util.Collection; + +/** + * GKislin + * 06.03.2015. + */ +public interface UserMealRepository { + UserMeal save(UserMeal userMeal); + + void delete(int id); + + UserMeal get(int id); + + Collection getAll(); +} diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 6ea1f206d9e7..6f22256c7807 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -33,11 +33,11 @@ public static void main(String[] args) { System.out.println(getFilteredWithExceededByCycle(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY)); } - public static List getWithExceeded(List mealList, int caloriesPerDay) { + public static List getWithExceeded(Collection mealList, int caloriesPerDay) { return getFilteredWithExceeded(mealList, LocalTime.MIN, LocalTime.MAX, caloriesPerDay); } - public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + public static List getFilteredWithExceeded(Collection mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { Map caloriesSumByDate = mealList.stream() .collect( Collectors.groupingBy(um -> um.getDateTime().toLocalDate(), @@ -66,6 +66,6 @@ public static List getFilteredWithExceededByCycle(List +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
+

Home

+

Edit meal

+
+ +
+ +
+
DateTime:
+
+
+
+
Description:
+
+
+
+
Calories:
+
+
+ + +
+
+ + diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/mealList.jsp index 1d1f66d87f7f..5d6a5dbfc9ef 100644 --- a/src/main/webapp/mealList.jsp +++ b/src/main/webapp/mealList.jsp @@ -19,6 +19,7 @@

Home

Meal list

+ Add Meal
@@ -26,6 +27,8 @@ + + @@ -37,6 +40,8 @@ + +
Date Description Calories
${meal.description} ${meal.calories}UpdateDelete
From 0308dabb414c16dbb430f39b7da531303590312d Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Fri, 18 Mar 2016 00:16:09 +0500 Subject: [PATCH 020/166] HW2 patch 2-3 --- .../ru/javawebinar/topjava/LoggedUser.java | 18 ++++ .../javawebinar/topjava/model/BaseEntity.java | 28 ++++++ .../topjava/model/NamedEntity.java | 31 ++++++ .../ru/javawebinar/topjava/model/Role.java | 10 ++ .../ru/javawebinar/topjava/model/User.java | 98 +++++++++++++++++++ .../topjava/repository/UserRepository.java | 24 +++++ .../topjava/service/UserMealService.java | 8 ++ .../topjava/service/UserMealServiceImpl.java | 13 +++ .../topjava/service/UserService.java | 26 +++++ .../topjava/service/UserServiceImpl.java | 41 ++++++++ .../topjava/util/exception/ExceptionUtil.java | 25 +++++ .../util/exception/NotFoundException.java | 11 +++ .../web/meal/UserMealRestController.java | 12 +++ .../web/user/AbstractUserController.java | 49 ++++++++++ .../topjava/web/user/AdminRestController.java | 36 +++++++ .../web/user/ProfileRestController.java | 23 +++++ 16 files changed, 453 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/LoggedUser.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/BaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/NamedEntity.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/UserMealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.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/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java new file mode 100644 index 000000000000..eb68d99a0d6e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -0,0 +1,18 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.util.UserMealsUtil; + +/** + * GKislin + * 06.03.2015. + */ +public class LoggedUser { + + public static int id() { + return 1; + } + + public static int getCaloriesPerDay() { + return UserMealsUtil.DEFAULT_CALORIES_PER_DAY; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java new file mode 100644 index 000000000000..60f4b46f1d8e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -0,0 +1,28 @@ +package ru.javawebinar.topjava.model; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public class BaseEntity { + protected Integer id; + + public BaseEntity() { + } + + protected BaseEntity(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); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java new file mode 100644 index 000000000000..ca92e394dc98 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -0,0 +1,31 @@ +package ru.javawebinar.topjava.model; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public class NamedEntity extends BaseEntity { + + protected String name; + + public NamedEntity() { + } + + protected NamedEntity(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 name; + } +} 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..f0de2b4c4a81 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.model; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public enum Role { + ROLE_USER, + ROLE_ADMIN +} 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..34b5977a5917 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,98 @@ +package ru.javawebinar.topjava.model; + +import ru.javawebinar.topjava.util.UserMealsUtil; + +import java.util.Date; +import java.util.EnumSet; +import java.util.Set; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public class User extends NamedEntity { + + protected String email; + + protected String password; + + protected boolean enabled = true; + + protected Date registered = new Date(); + + protected Set roles; + + protected int caloriesPerDay = UserMealsUtil.DEFAULT_CALORIES_PER_DAY; + + public User() { + } + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, UserMealsUtil.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 + + ')'; + } +} 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..24c1c45beca6 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public interface UserRepository { + 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(); +} diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java new file mode 100644 index 000000000000..b17fec811065 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.service; + +/** + * GKislin + * 15.06.2015. + */ +public interface UserMealService { +} diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java new file mode 100644 index 000000000000..5730b8bfb130 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java @@ -0,0 +1,13 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.repository.UserMealRepository; + +/** + * GKislin + * 06.03.2015. + */ +public class UserMealServiceImpl implements UserMealService { + + private UserMealRepository repository; + +} 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..85c15e48f951 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.service; + + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public interface UserService { + + User save(User user); + + void delete(int id) throws NotFoundException; + + User get(int id) throws NotFoundException; + + User getByEmail(String email) throws NotFoundException; + + List getAll(); + + void update(User user); +} diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java new file mode 100644 index 000000000000..27a4b171c406 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -0,0 +1,41 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.util.exception.ExceptionUtil; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +/** + * GKislin + * 06.03.2015. + */ +public class UserServiceImpl implements UserService { + + private UserRepository repository; + + public User save(User user) { + return repository.save(user); + } + + public void delete(int id) { + ExceptionUtil.check(repository.delete(id), id); + } + + public User get(int id) throws NotFoundException { + return ExceptionUtil.check(repository.get(id), id); + } + + public User getByEmail(String email) throws NotFoundException { + return ExceptionUtil.check(repository.getByEmail(email), "email=" + email); + } + + public List getAll() { + return repository.getAll(); + } + + public void update(User user) { + repository.save(user); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java new file mode 100644 index 000000000000..de97182f9d87 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.util.exception; + + +/** + * User: gkislin + * Date: 14.05.2014 + */ +public class ExceptionUtil { + public static void check(boolean found, int id) { + check(found, "id=" + id); + } + + public static T check(T object, int id) { + return check(object, "id=" + id); + } + + public static T check(T object, String msg) { + check(object != null, msg); + return object; + } + + public static void check(boolean found, String msg) { + if (!found) throw new NotFoundException("Not found entity with " + msg); + } +} 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..7a770f0db834 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -0,0 +1,11 @@ +package ru.javawebinar.topjava.util.exception; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java new file mode 100644 index 000000000000..b3c1008f1d00 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava.web.meal; + +import ru.javawebinar.topjava.service.UserMealService; + +/** + * GKislin + * 06.03.2015. + */ +public class UserMealRestController { + private UserMealService service; + +} 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..aac911f5864d --- /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; + +/** + * User: gkislin + */ +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) { + user.setId(null); + LOG.info("create " + user); + return service.save(user); + } + + public void delete(int id) { + LOG.info("delete " + id); + service.delete(id); + } + + public void update(User user, int id) { + user.setId(id); + LOG.info("update " + user); + service.update(user); + } + + public User getByMail(String email) { + LOG.info("getByEmail " + email); + return service.getByEmail(email); + } +} 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..cdd1e6bf11f8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,36 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +/** + * GKislin + * 06.03.2015. + */ +public class AdminRestController extends AbstractUserController { + + public List getAll() { + return super.getAll(); + } + + public User get(int id) { + return super.get(id); + } + + public User create(User user) { + return super.create(user); + } + + public void delete(int id) { + super.delete(id); + } + + public void update(User user, int id) { + super.update(user, id); + } + + public User getByMail(String email) { + return super.getByMail(email); + } +} 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..6f0c970152af --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -0,0 +1,23 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.model.User; + +/** + * GKislin + * 06.03.2015. + */ +public class ProfileRestController extends AbstractUserController { + + public User get() { + return super.get(LoggedUser.id()); + } + + public void delete() { + super.delete(LoggedUser.id()); + } + + public void update(User user) { + super.update(user, LoggedUser.id()); + } +} \ No newline at end of file From c35d19a00839b2d6f86bfc5d4e260d2282fe9622 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 15:22:37 +0500 Subject: [PATCH 021/166] HW2 patch 2-4 --- pom.xml | 14 ++++++ .../java/ru/javawebinar/topjava/Main.java | 2 +- .../ru/javawebinar/topjava/SpringMain.java | 23 +++++++++ .../InMemoryUserMealRepositoryImpl.java | 3 +- .../mock/MockUserRepositoryImpl.java | 47 +++++++++++++++++++ .../javawebinar/topjava/web/MealServlet.java | 2 +- src/main/resources/spring/spring-app.xml | 8 ++++ 7 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.java rename src/main/java/ru/javawebinar/topjava/repository/{ => mock}/InMemoryUserMealRepositoryImpl.java (90%) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java create mode 100644 src/main/resources/spring/spring-app.xml diff --git a/pom.xml b/pom.xml index af41b1894a11..ec49eea1e524 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 4.2.5.RELEASE + 1.1.6 1.7.18 @@ -67,6 +69,18 @@ runtime + + + org.springframework + spring-context + ${spring.version} + + + commons-logging + commons-logging + + + javax.servlet jstl diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java index b23a2f0961fc..e78cff96c229 100644 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ b/src/main/java/ru/javawebinar/topjava/Main.java @@ -9,6 +9,6 @@ */ public class Main { public static void main(String[] args) { - System.out.format("Hello Topjava Enterprise!"); + System.out.println("Hello Topjava Enterprise!"); } } 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..6699bb27ca33 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,23 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Arrays; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public class SpringMain { + public static void main(String[] args) { + ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); + +// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); + UserRepository userRepository = appCtx.getBean(UserRepository.class); + userRepository.getAll(); + appCtx.close(); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java similarity index 90% rename from src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepositoryImpl.java rename to src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java index 4da0792bb19d..ab4e99c5993c 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java @@ -1,6 +1,7 @@ -package ru.javawebinar.topjava.repository; +package ru.javawebinar.topjava.repository.mock; import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.repository.UserMealRepository; import ru.javawebinar.topjava.util.UserMealsUtil; import java.util.Collection; diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java new file mode 100644 index 000000000000..8a014233b855 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Collections; +import java.util.List; + +/** + * GKislin + * 15.06.2015. + */ +public class MockUserRepositoryImpl implements UserRepository { + private static final Logger LOG = LoggerFactory.getLogger(MockUserRepositoryImpl.class); + + @Override + public boolean delete(int id) { + LOG.info("delete " + id); + return true; + } + + @Override + public User save(User user) { + LOG.info("save " + user); + return user; + } + + @Override + public User get(int id) { + LOG.info("get " + id); + return null; + } + + @Override + public List getAll() { + LOG.info("getAll"); + return Collections.emptyList(); + } + + @Override + public User getByEmail(String email) { + LOG.info("getByEmail " + email); + return null; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 614c2a6d44bd..d66f4c4301df 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -3,7 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.repository.InMemoryUserMealRepositoryImpl; +import ru.javawebinar.topjava.repository.mock.InMemoryUserMealRepositoryImpl; import ru.javawebinar.topjava.repository.UserMealRepository; import ru.javawebinar.topjava.util.UserMealsUtil; diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..19632d5645ac --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file From 39e6ad2d54782cb41b89813236d0c5058dba4e5e Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 15:23:09 +0500 Subject: [PATCH 022/166] HW2 patch 2-5 --- src/main/java/ru/javawebinar/topjava/SpringMain.java | 7 +++++++ .../ru/javawebinar/topjava/service/UserServiceImpl.java | 4 ++++ src/main/resources/spring/spring-app.xml | 6 ++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 6699bb27ca33..fcf9372812b9 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -2,7 +2,10 @@ 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.repository.UserRepository; +import ru.javawebinar.topjava.service.UserService; import java.util.Arrays; @@ -18,6 +21,10 @@ public static void main(String[] args) { // UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); UserRepository userRepository = appCtx.getBean(UserRepository.class); userRepository.getAll(); + + UserService userService = appCtx.getBean(UserService.class); + System.out.println(userService.save(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); + appCtx.close(); } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 27a4b171c406..066ae95037be 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -15,6 +15,10 @@ public class UserServiceImpl implements UserService { private UserRepository repository; + public void setRepository(UserRepository repository) { + this.repository = repository; + } + public User save(User user) { return repository.save(user); } diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 19632d5645ac..7b4fa1689425 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -1,8 +1,10 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> + + + \ No newline at end of file From 11e102d1d49036790cbcbe25927b49c3adc35c65 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 15:24:52 +0500 Subject: [PATCH 023/166] HW2 patch 2-6 --- .../ru/javawebinar/topjava/SpringMain.java | 20 ++++++---------- .../mock/MockUserRepositoryImpl.java | 2 ++ .../topjava/service/UserServiceImpl.java | 8 +++---- .../web/user/AbstractUserController.java | 2 ++ .../topjava/web/user/AdminRestController.java | 2 ++ .../web/user/ProfileRestController.java | 2 ++ src/main/resources/spring/spring-app.xml | 23 +++++++++++++++---- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index fcf9372812b9..9b14ec645a08 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -4,8 +4,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.UserRepository; -import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.web.user.AdminRestController; import java.util.Arrays; @@ -15,16 +14,11 @@ */ public class SpringMain { public static void main(String[] args) { - ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); - System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); - -// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); - UserRepository userRepository = appCtx.getBean(UserRepository.class); - userRepository.getAll(); - - UserService userService = appCtx.getBean(UserService.class); - System.out.println(userService.save(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); - - appCtx.close(); + // java 7 Automatic resource management + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + System.out.println(adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); + } } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java index 8a014233b855..6231e4c74521 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.repository.mock; +import org.springframework.stereotype.Repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.User; @@ -12,6 +13,7 @@ * GKislin * 15.06.2015. */ +@Repository public class MockUserRepositoryImpl implements UserRepository { private static final Logger LOG = LoggerFactory.getLogger(MockUserRepositoryImpl.class); diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 066ae95037be..f83d039b7fbc 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.ExceptionUtil; @@ -11,14 +13,12 @@ * GKislin * 06.03.2015. */ +@Service public class UserServiceImpl implements UserService { + @Autowired private UserRepository repository; - public void setRepository(UserRepository repository) { - this.repository = repository; - } - public User save(User user) { return repository.save(user); } 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 aac911f5864d..e2b847678af2 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.beans.factory.annotation.Autowired; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.User; @@ -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 cdd1e6bf11f8..d6b8bb420803 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.model.User; import java.util.List; @@ -8,6 +9,7 @@ * GKislin * 06.03.2015. */ +@Controller public class AdminRestController extends AbstractUserController { public List getAll() { 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 6f0c970152af..1114e57eeb1e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.model.User; @@ -7,6 +8,7 @@ * GKislin * 06.03.2015. */ +@Controller public class ProfileRestController extends AbstractUserController { public User get() { diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 7b4fa1689425..96e40e902b66 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -1,10 +1,23 @@ + xmlns:context="http://www.springframework.org/schema/context" + 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"> - + + + + + + + + + - - - \ No newline at end of file From aa88c4ba1845cb99ab2d4f7f84b4592c585a0700 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 16:08:05 +0500 Subject: [PATCH 024/166] HW3 patch 3-01 --- .../repository/UserMealRepository.java | 16 +++-- .../mock/InMemoryUserMealRepositoryImpl.java | 64 +++++++++++++++---- .../mock/InMemoryUserRepositoryImpl.java | 59 +++++++++++++++++ .../mock/MockUserRepositoryImpl.java | 49 -------------- .../ru/javawebinar/topjava/util/TimeUtil.java | 5 +- .../javawebinar/topjava/web/MealServlet.java | 11 ++-- 6 files changed, 129 insertions(+), 75 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java delete mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java index ad656ea6cd19..8a7687fb85cb 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java @@ -2,6 +2,7 @@ import ru.javawebinar.topjava.model.UserMeal; +import java.time.LocalDateTime; import java.util.Collection; /** @@ -9,11 +10,18 @@ * 06.03.2015. */ public interface UserMealRepository { - UserMeal save(UserMeal userMeal); + // null if updated meal do not belong to userId + UserMeal save(UserMeal userMeal, int userId); - void delete(int id); + // false if meal do not belong to userId + boolean delete(int id, int userId); - UserMeal get(int id); + // null if meal do not belong to userId + UserMeal get(int id, int userId); - Collection getAll(); + // ORDERED dateTime + Collection getAll(int userId); + + // ORDERED dateTime + Collection getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java index ab4e99c5993c..8e5df520c7b7 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java @@ -1,48 +1,84 @@ package ru.javawebinar.topjava.repository.mock; +import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; +import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.util.UserMealsUtil; -import java.util.Collection; -import java.util.Map; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.ADMIN_ID; +import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; /** * GKislin * 15.09.2015. */ +@Repository public class InMemoryUserMealRepositoryImpl implements UserMealRepository { - private Map repository = new ConcurrentHashMap<>(); + + public static final Comparator USER_MEAL_COMPARATOR = Comparator.comparing(UserMeal::getDateTime).reversed(); + + // Map userId -> (mealId-> meal) + private Map> repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); { - UserMealsUtil.MEAL_LIST.forEach(this::save); + UserMealsUtil.MEAL_LIST.forEach(um -> save(um, USER_ID)); + + save(new UserMeal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); + save(new UserMeal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); } @Override - public UserMeal save(UserMeal userMeal) { + public UserMeal save(UserMeal userMeal, int userId) { + Objects.requireNonNull(userMeal); + + Integer mealId = userMeal.getId(); + if (userMeal.isNew()) { - userMeal.setId(counter.incrementAndGet()); + mealId = counter.incrementAndGet(); + userMeal.setId(mealId); + } else if (get(mealId, userId) == null) { + return null; } - repository.put(userMeal.getId(), userMeal); + Map userMeals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); + userMeals.put(mealId, userMeal); return userMeal; } @Override - public void delete(int id) { - repository.remove(id); + public boolean delete(int id, int userId) { + Map userMeals = repository.get(userId); + return userMeals != null && userMeals.remove(id) != null; } @Override - public UserMeal get(int id) { - return repository.get(id); + public UserMeal get(int id, int userId) { + Map userMeals = repository.get(userId); + return userMeals == null ? null : userMeals.get(id); } @Override - public Collection getAll() { - return repository.values(); + public Collection getAll(int userId) { + Map userMeals = repository.get(userId); + return userMeals == null ? + Collections.emptyList() : + userMeals.values().stream().sorted(USER_MEAL_COMPARATOR).collect(Collectors.toList()); } -} + @Override + public Collection getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Objects.requireNonNull(startDateTime); + Objects.requireNonNull(endDateTime); + return getAll(userId).stream() + .filter(um -> TimeUtil.isBetween(um.getDateTime(), startDateTime, endDateTime)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java new file mode 100644 index 000000000000..fe8b12bdc3ff --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -0,0 +1,59 @@ +package ru.javawebinar.topjava.repository.mock; + +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.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +/** + * GKislin + * 15.06.2015. + */ +@Repository +public class InMemoryUserRepositoryImpl implements UserRepository { + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + public static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName); + + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + @Override + public User save(User user) { + Objects.requireNonNull(user); + if (user.isNew()) { + user.setId(counter.incrementAndGet()); + } + repository.put(user.getId(), user); + return user; + } + + @Override + public boolean delete(int id) { + return repository.remove(id) != null; + } + + @Override + public User get(int id) { + return repository.get(id); + } + + @Override + public List getAll() { + return repository.values().stream().sorted(USER_COMPARATOR).collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { + Objects.requireNonNull(email); + return repository.values().stream().filter(u -> email.equals(u.getEmail())).findFirst().orElse(null); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java deleted file mode 100644 index 6231e4c74521..000000000000 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.javawebinar.topjava.repository.mock; - -import org.springframework.stereotype.Repository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.UserRepository; - -import java.util.Collections; -import java.util.List; - -/** - * GKislin - * 15.06.2015. - */ -@Repository -public class MockUserRepositoryImpl implements UserRepository { - private static final Logger LOG = LoggerFactory.getLogger(MockUserRepositoryImpl.class); - - @Override - public boolean delete(int id) { - LOG.info("delete " + id); - return true; - } - - @Override - public User save(User user) { - LOG.info("save " + user); - return user; - } - - @Override - public User get(int id) { - LOG.info("get " + id); - return null; - } - - @Override - public List getAll() { - LOG.info("getAll"); - return Collections.emptyList(); - } - - @Override - public User getByEmail(String email) { - LOG.info("getByEmail " + email); - return null; - } -} diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 115311fad6b7..74df07dec309 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -1,7 +1,6 @@ package ru.javawebinar.topjava.util; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.format.DateTimeFormatter; /** @@ -11,8 +10,8 @@ public class TimeUtil { public static final DateTimeFormatter DATE_TME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; + public static > boolean isBetween(T value, T start, T end) { + return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; } public static String toString(LocalDateTime ldt) { diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index d66f4c4301df..db61311a371d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,10 +1,11 @@ package ru.javawebinar.topjava.web; +import ru.javawebinar.topjava.LoggedUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.repository.mock.InMemoryUserMealRepositoryImpl; import ru.javawebinar.topjava.repository.UserMealRepository; +import ru.javawebinar.topjava.repository.mock.InMemoryUserMealRepositoryImpl; import ru.javawebinar.topjava.util.UserMealsUtil; import javax.servlet.ServletConfig; @@ -39,7 +40,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) request.getParameter("description"), Integer.valueOf(request.getParameter("calories"))); LOG.info(userMeal.isNew() ? "Create {}" : "Update {}", userMeal); - repository.save(userMeal); + repository.save(userMeal, LoggedUser.id()); response.sendRedirect("meals"); } @@ -49,17 +50,17 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t if (action == null) { LOG.info("getAll"); request.setAttribute("mealList", - UserMealsUtil.getWithExceeded(repository.getAll(), UserMealsUtil.DEFAULT_CALORIES_PER_DAY)); + UserMealsUtil.getWithExceeded(repository.getAll(LoggedUser.id()), UserMealsUtil.DEFAULT_CALORIES_PER_DAY)); request.getRequestDispatcher("/mealList.jsp").forward(request, response); } else if (action.equals("delete")) { int id = getId(request); LOG.info("Delete {}", id); - repository.delete(id); + repository.delete(id, LoggedUser.id()); response.sendRedirect("meals"); } else { final UserMeal meal = action.equals("create") ? new UserMeal(LocalDateTime.now(), "", 1000) : - repository.get(getId(request)); + repository.get(getId(request), LoggedUser.id()); request.setAttribute("meal", meal); request.getRequestDispatcher("mealEdit.jsp").forward(request, response); } From e3a110968ecbd9c3dad65ccd3abd9bfc168c8a04 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 16:13:09 +0500 Subject: [PATCH 025/166] HW3 patch 3-02 --- .../ru/javawebinar/topjava/SpringMain.java | 14 +++++ .../javawebinar/topjava/model/UserMeal.java | 17 +----- .../topjava/service/UserMealService.java | 23 +++++++ .../topjava/service/UserMealServiceImpl.java | 38 ++++++++++++ .../{model => to}/UserMealWithExceed.java | 2 +- .../ru/javawebinar/topjava/util/TimeUtil.java | 3 + .../topjava/util/UserMealsUtil.java | 2 +- .../web/meal/UserMealRestController.java | 61 ++++++++++++++++++- src/main/webapp/mealList.jsp | 6 +- 9 files changed, 145 insertions(+), 21 deletions(-) rename src/main/java/ru/javawebinar/topjava/{model => to}/UserMealWithExceed.java (97%) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 9b14ec645a08..a50dc23ecee7 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -4,9 +4,15 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.UserMealWithExceed; +import ru.javawebinar.topjava.web.meal.UserMealRestController; 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; /** * User: gkislin @@ -19,6 +25,14 @@ public static void main(String[] args) { System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); System.out.println(adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); + System.out.println(); + + UserMealRestController mealController = appCtx.getBean(UserMealRestController.class); + List filteredMealsWithExceeded = + mealController.getBetween( + LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), + LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); + filteredMealsWithExceeded.forEach(System.out::println); } } } diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index fc43ba8b8b32..5fc41074a979 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -6,8 +6,7 @@ * GKislin * 11.01.2015. */ -public class UserMeal { - private Integer id; +public class UserMeal extends BaseEntity { private final LocalDateTime dateTime; @@ -20,16 +19,12 @@ public UserMeal(LocalDateTime dateTime, String description, int calories) { } public UserMeal(Integer id, LocalDateTime dateTime, String description, int calories) { - this.id = id; + super(id); this.dateTime = dateTime; this.description = description; this.calories = calories; } - public void setId(int id) { - this.id = id; - } - public LocalDateTime getDateTime() { return dateTime; } @@ -42,14 +37,6 @@ public int getCalories() { return calories; } - public Integer getId() { - return id; - } - - public boolean isNew() { - return id == null; - } - @Override public String toString() { return "UserMeal{" + diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java index b17fec811065..9e4ea6413504 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java @@ -1,8 +1,31 @@ package ru.javawebinar.topjava.service; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Collection; + /** * GKislin * 15.06.2015. */ public interface UserMealService { + UserMeal get(int id, int userId) throws NotFoundException; + + void delete(int id, int userId) throws NotFoundException; + + default Collection getBetweenDates(LocalDate startDate, LocalDate endDate, int userId) { + return getBetweenDateTimes(LocalDateTime.of(startDate, LocalTime.MIN), LocalDateTime.of(endDate, LocalTime.MAX), userId); + } + + Collection getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId); + + Collection getAll(int userId); + + UserMeal update(UserMeal meal, int userId) throws NotFoundException; + + UserMeal save(UserMeal meal, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java index 5730b8bfb130..fe2aa873de5c 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java @@ -1,13 +1,51 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; +import ru.javawebinar.topjava.util.exception.ExceptionUtil; + +import java.time.LocalDateTime; +import java.util.Collection; /** * GKislin * 06.03.2015. */ +@Service public class UserMealServiceImpl implements UserMealService { + @Autowired private UserMealRepository repository; + @Override + public UserMeal get(int id, int userId) { + return ExceptionUtil.check(repository.get(id, userId), id); + } + + @Override + public void delete(int id, int userId) { + ExceptionUtil.check(repository.delete(id, userId), id); + } + + @Override + public Collection getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return repository.getBetween(startDateTime, endDateTime, userId); + } + + @Override + public Collection getAll(int userId) { + return repository.getAll(userId); + } + + @Override + public UserMeal update(UserMeal meal, int userId) { + return ExceptionUtil.check(repository.save(meal, userId), meal.getId()); + } + + @Override + public UserMeal save(UserMeal meal, int userId) { + return repository.save(meal, userId); + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java similarity index 97% rename from src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java rename to src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java index 4f1fe2f6ae0f..3dcc17d30b2f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.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/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 74df07dec309..0e9b13eab977 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -9,6 +10,8 @@ */ public class TimeUtil { public static final DateTimeFormatter DATE_TME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static final LocalDate MIN_DATE = LocalDate.of(0, 1, 1); + public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); public static > boolean isBetween(T value, T start, T end) { return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 6f22256c7807..dea298e4cf4c 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.util; import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.model.UserMealWithExceed; +import ru.javawebinar.topjava.to.UserMealWithExceed; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index b3c1008f1d00..ac5e90232670 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -1,12 +1,71 @@ package ru.javawebinar.topjava.web.meal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.service.UserMealService; +import ru.javawebinar.topjava.to.UserMealWithExceed; +import ru.javawebinar.topjava.util.TimeUtil; +import ru.javawebinar.topjava.util.UserMealsUtil; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; /** * GKislin * 06.03.2015. */ +@Controller public class UserMealRestController { + private static final Logger LOG = LoggerFactory.getLogger(UserMealRestController.class); + + @Autowired private UserMealService service; -} + public UserMeal get(int id) { + int userId = LoggedUser.id(); + LOG.info("get meal {} for User {}", id, userId); + return service.get(id, userId); + } + + public void delete(int id) { + int userId = LoggedUser.id(); + LOG.info("delete meal {} for User {}", id, userId); + service.delete(id, userId); + } + + public List getAll() { + int userId = LoggedUser.id(); + LOG.info("getAll for User {}", userId); + return UserMealsUtil.getWithExceeded(service.getAll(userId), LoggedUser.getCaloriesPerDay()); + } + + public void update(UserMeal meal, int id) { + meal.setId(id); + int userId = LoggedUser.id(); + LOG.info("update {} for User {}", meal, userId); + service.update(meal, userId); + } + + public UserMeal create(UserMeal meal) { + meal.setId(null); + int userId = LoggedUser.id(); + LOG.info("create {} for User {}", meal, userId); + return service.save(meal, userId); + } + + public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { + int userId = LoggedUser.id(); + LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId); + + return UserMealsUtil.getFilteredWithExceeded( + service.getBetweenDates( + startDate != null ? startDate : TimeUtil.MIN_DATE, endDate != null ? endDate : TimeUtil.MAX_DATE, userId), + startTime != null ? startTime : LocalTime.MIN, endTime != null ? endTime : LocalTime.MAX, LoggedUser.getCaloriesPerDay() + ); + } +} \ No newline at end of file diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/mealList.jsp index 5d6a5dbfc9ef..cacd403218e9 100644 --- a/src/main/webapp/mealList.jsp +++ b/src/main/webapp/mealList.jsp @@ -32,11 +32,11 @@ - + - ${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()} - <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%=TimeUtil.toString(meal.getDateTime())%> ${meal.description} ${meal.calories} From d3f0bf2d4cf9608ef95ba4d3adde6479910dbe69 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 16:15:42 +0500 Subject: [PATCH 026/166] HW3 patch 3-03 --- .../javawebinar/topjava/web/MealServlet.java | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index db61311a371d..80cd67d1d781 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,12 +1,11 @@ package ru.javawebinar.topjava.web; -import ru.javawebinar.topjava.LoggedUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.repository.UserMealRepository; -import ru.javawebinar.topjava.repository.mock.InMemoryUserMealRepositoryImpl; -import ru.javawebinar.topjava.util.UserMealsUtil; +import ru.javawebinar.topjava.web.meal.UserMealRestController; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -24,23 +23,37 @@ public class MealServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - private UserMealRepository repository; + private ConfigurableApplicationContext springContext; + private UserMealRestController mealController; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - repository = new InMemoryUserMealRepositoryImpl(); + springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + mealController = springContext.getBean(UserMealRestController.class); + } + + @Override + public void destroy() { + springContext.close(); + super.destroy(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { request.setCharacterEncoding("UTF-8"); - String id = request.getParameter("id"); - UserMeal userMeal = new UserMeal(id.isEmpty() ? null : Integer.valueOf(id), + + final UserMeal userMeal = new UserMeal( LocalDateTime.parse(request.getParameter("dateTime")), request.getParameter("description"), Integer.valueOf(request.getParameter("calories"))); - LOG.info(userMeal.isNew() ? "Create {}" : "Update {}", userMeal); - repository.save(userMeal, LoggedUser.id()); + + if (request.getParameter("id").isEmpty()) { + LOG.info("Create {}", userMeal); + mealController.create(userMeal); + } else { + LOG.info("Update {}", userMeal); + mealController.update(userMeal, getId(request)); + } response.sendRedirect("meals"); } @@ -49,18 +62,17 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t if (action == null) { LOG.info("getAll"); - request.setAttribute("mealList", - UserMealsUtil.getWithExceeded(repository.getAll(LoggedUser.id()), UserMealsUtil.DEFAULT_CALORIES_PER_DAY)); - request.getRequestDispatcher("/mealList.jsp").forward(request, response); + request.setAttribute("mealList", mealController.getAll()); + request.getRequestDispatcher("mealList.jsp").forward(request, response); } else if (action.equals("delete")) { int id = getId(request); LOG.info("Delete {}", id); - repository.delete(id, LoggedUser.id()); + mealController.delete(id); response.sendRedirect("meals"); } else { final UserMeal meal = action.equals("create") ? - new UserMeal(LocalDateTime.now(), "", 1000) : - repository.get(getId(request), LoggedUser.id()); + new UserMeal(LocalDateTime.now(), "", 1000) : // create + mealController.get(getId(request)); // update request.setAttribute("meal", meal); request.getRequestDispatcher("mealEdit.jsp").forward(request, response); } From 04cb31d329c3c0aa6f2cd4f50006d7e96e2ec4c2 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 16:18:01 +0500 Subject: [PATCH 027/166] HW3 patch 3-04 --- .../ru/javawebinar/topjava/util/TimeUtil.java | 11 +++++ .../javawebinar/topjava/web/MealServlet.java | 41 ++++++++++++++----- .../web/meal/UserMealRestController.java | 1 - src/main/webapp/css/style.css | 24 +++++++++++ src/main/webapp/mealEdit.jsp | 19 +-------- src/main/webapp/mealList.jsp | 30 ++++++++++---- 6 files changed, 87 insertions(+), 39 deletions(-) create mode 100644 src/main/webapp/css/style.css diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 0e9b13eab977..872dbd411e06 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -1,7 +1,10 @@ package ru.javawebinar.topjava.util; +import org.springframework.util.StringUtils; + import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; /** @@ -20,4 +23,12 @@ public static > boolean isBetween(T value, T sta public static String toString(LocalDateTime ldt) { return ldt == null ? "" : ldt.format(DATE_TME_FORMATTER); } + + public static LocalDate parseLocalDate(String str) { + return StringUtils.isEmpty(str) ? null : LocalDate.parse(str); + } + + public static LocalTime parseLocalTime(String str) { + return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 80cd67d1d781..025642a40de5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -5,6 +5,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.web.meal.UserMealRestController; import javax.servlet.ServletConfig; @@ -13,7 +14,9 @@ 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.util.Objects; /** @@ -41,20 +44,30 @@ public void destroy() { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { request.setCharacterEncoding("UTF-8"); + String action = request.getParameter("action"); + if (action == null) { + final UserMeal userMeal = new UserMeal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); - final UserMeal userMeal = new UserMeal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.valueOf(request.getParameter("calories"))); + if (request.getParameter("id").isEmpty()) { + LOG.info("Create {}", userMeal); + mealController.create(userMeal); + } else { + LOG.info("Update {}", userMeal); + mealController.update(userMeal, getId(request)); + } + response.sendRedirect("meals"); - if (request.getParameter("id").isEmpty()) { - LOG.info("Create {}", userMeal); - mealController.create(userMeal); - } else { - LOG.info("Update {}", userMeal); - mealController.update(userMeal, getId(request)); + } else if (action.equals("filter")) { + LocalDate startDate = TimeUtil.parseLocalDate(resetParam("startDate", request)); + LocalDate endDate = TimeUtil.parseLocalDate(resetParam("endDate", request)); + LocalTime startTime = TimeUtil.parseLocalTime(resetParam("startTime", request)); + LocalTime endTime = TimeUtil.parseLocalTime(resetParam("endTime", request)); + request.setAttribute("mealList", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/mealList.jsp").forward(request, response); } - response.sendRedirect("meals"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -78,6 +91,12 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t } } + private String resetParam(String param, HttpServletRequest request) { + String value = request.getParameter(param); + request.setAttribute(param, value); + return value; + } + private int getId(HttpServletRequest request) { String paramId = Objects.requireNonNull(request.getParameter("id")); return Integer.valueOf(paramId); diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index ac5e90232670..305604f46144 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -61,7 +61,6 @@ public UserMeal create(UserMeal meal) { public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { int userId = LoggedUser.id(); LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId); - return UserMealsUtil.getFilteredWithExceeded( service.getBetweenDates( startDate != null ? startDate : TimeUtil.MIN_DATE, endDate != null ? endDate : TimeUtil.MAX_DATE, userId), diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css new file mode 100644 index 000000000000..0c9fc667371c --- /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; +} + +.normal { + color: green; +} + +.exceeded { + color: red; +} diff --git a/src/main/webapp/mealEdit.jsp b/src/main/webapp/mealEdit.jsp index 443933549130..d71aeb3a6bbd 100644 --- a/src/main/webapp/mealEdit.jsp +++ b/src/main/webapp/mealEdit.jsp @@ -4,24 +4,7 @@ Meal - +
diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/mealList.jsp index cacd403218e9..0f198569b67f 100644 --- a/src/main/webapp/mealList.jsp +++ b/src/main/webapp/mealList.jsp @@ -5,20 +5,32 @@ Meal list - +

Home

Meal list

+
+
+
From Date:
+
+
+
+
To Date:
+
+
+
+
From Time:
+
+
+
+
To Time:
+
+
+ +
+
Add Meal
From 73364d827d0d672c7c4d6bff7ff2bd7e5086848d Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 20 Mar 2016 16:23:31 +0500 Subject: [PATCH 028/166] HW3 patch 3-05 --- src/main/java/ru/javawebinar/topjava/LoggedUser.java | 7 ++++++- .../java/ru/javawebinar/topjava/web/UserServlet.java | 8 +++++++- src/main/webapp/index.html | 11 +++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java index eb68d99a0d6e..115866e29f99 100644 --- a/src/main/java/ru/javawebinar/topjava/LoggedUser.java +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -7,9 +7,14 @@ * 06.03.2015. */ public class LoggedUser { + public static int id = 1; public static int id() { - return 1; + return id; + } + + public static void setId(int id) { + LoggedUser.id = id; } public static int getCaloriesPerDay() { diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 4a4706396789..b76bd0771733 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.web; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.LoggedUser; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -18,6 +18,12 @@ public class UserServlet extends HttpServlet { private static final Logger LOG = getLogger(UserServlet.class); + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + int userId = Integer.valueOf(request.getParameter("userId")); + LoggedUser.setId(userId); + response.sendRedirect("meals"); + } + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOG.debug("redirect to userList"); // request.getRequestDispatcher("/userList.jsp").forward(request, response); diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index e85ec56ddf0d..b178fa0eeba4 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -7,9 +7,16 @@

Проект "Top Java"


+
+ Login as: + + From d1f2c53e24bead5e85e00c5230ef177fb2fa74c1 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 23 Mar 2016 23:03:03 +0500 Subject: [PATCH 029/166] HW3 patch 3-06 --- .../mock/InMemoryUserRepositoryImpl.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java index fe8b12bdc3ff..dbbef77704d1 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -1,9 +1,13 @@ package ru.javawebinar.topjava.repository.mock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -18,6 +22,8 @@ */ @Repository public class InMemoryUserRepositoryImpl implements UserRepository { + private static final Logger LOG = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); + private Map repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); @@ -36,6 +42,16 @@ public User save(User user) { return user; } + @PostConstruct + public void postConstruct() { + LOG.info("+++ PostConstruct"); + } + + @PreDestroy + public void preDestroy() { + LOG.info("+++ PreDestroy"); + } + @Override public boolean delete(int id) { return repository.remove(id) != null; From 27d8810435e1a22af4e5b8e2fa05cb81677043a8 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 23 Mar 2016 23:09:01 +0500 Subject: [PATCH 030/166] HW3 patch 3-07 --- pom.xml | 11 ++++ .../ru/javawebinar/topjava/SpringMain.java | 4 +- .../ru/javawebinar/topjava/UserTestData.java | 16 ++++++ .../mock/InMemoryUserMealRepositoryImpl.java | 4 +- .../mock/InMemoryUserRepositoryImpl.java | 3 - .../web/InMemoryAdminRestControllerTest.java | 55 +++++++++++++++++++ 6 files changed, 85 insertions(+), 8 deletions(-) rename src/{main => test}/java/ru/javawebinar/topjava/SpringMain.java (87%) create mode 100644 src/test/java/ru/javawebinar/topjava/UserTestData.java rename src/{main => test}/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java (94%) rename src/{main => test}/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java (95%) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index ec49eea1e524..533516297105 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ 1.1.6 1.7.18 + + + 4.12 @@ -81,6 +84,14 @@ + + + + junit + junit + ${junit.version} + test + javax.servlet jstl diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java similarity index 87% rename from src/main/java/ru/javawebinar/topjava/SpringMain.java rename to src/test/java/ru/javawebinar/topjava/SpringMain.java index a50dc23ecee7..1e61564819e0 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -2,8 +2,6 @@ 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.UserMealWithExceed; import ru.javawebinar.topjava.web.meal.UserMealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; @@ -24,7 +22,7 @@ public static void main(String[] args) { try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); - System.out.println(adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); + System.out.println(adminUserController.create(UserTestData.USER)); System.out.println(); UserMealRestController mealController = appCtx.getBean(UserMealRestController.class); diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java new file mode 100644 index 000000000000..bb8a9fb19788 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -0,0 +1,16 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +/** + * GKislin + * 24.09.2015. + */ +public class UserTestData { + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); + public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java similarity index 94% rename from src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java index 8e5df520c7b7..d68c3e3555b7 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java @@ -13,8 +13,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.ADMIN_ID; -import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.USER_ID; /** * GKislin diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java similarity index 95% rename from src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java index dbbef77704d1..a76cfe226c06 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -29,9 +29,6 @@ public class InMemoryUserRepositoryImpl implements UserRepository { public static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName); - public static final int USER_ID = 1; - public static final int ADMIN_ID = 2; - @Override public User save(User user) { Objects.requireNonNull(user); diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java new file mode 100644 index 000000000000..fe08ae623eec --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -0,0 +1,55 @@ +package ru.javawebinar.topjava.web; + +import org.junit.*; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Arrays; +import java.util.Collection; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; +import static ru.javawebinar.topjava.UserTestData.USER; + +public class InMemoryAdminRestControllerTest { + private static ConfigurableApplicationContext appCtx; + private static AdminRestController controller; + + @BeforeClass + public static void beforeClass() { + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); + controller = appCtx.getBean(AdminRestController.class); + } + + @Before + public void setUp() throws Exception { + // Re-initialize + UserRepository repository = appCtx.getBean(UserRepository.class); + repository.getAll().forEach(u -> repository.delete(u.getId())); + repository.save(USER); + repository.save(ADMIN); + } + + @AfterClass + public static void afterClass() { + appCtx.close(); + } + + @Test + public void testDelete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { + controller.delete(10); + } +} \ No newline at end of file From d3ec02d3e22c0b456876223eb43277e224953552 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 23 Mar 2016 23:33:07 +0500 Subject: [PATCH 031/166] HW3 patch 3-08 --- pom.xml | 7 +++ ...InMemoryAdminRestControllerSpringTest.java | 54 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java diff --git a/pom.xml b/pom.xml index 533516297105..63e4368b02bd 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,13 @@ 2.5 provided + + org.springframework + spring-test + ${spring.version} + test + + diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java new file mode 100644 index 000000000000..9396810afe48 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -0,0 +1,54 @@ +package ru.javawebinar.topjava.web; + +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.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Collection; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; +import static ru.javawebinar.topjava.UserTestData.USER; + +/** + * GKislin + * 13.03.2015. + */ +@ContextConfiguration("classpath:spring/spring-app.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class InMemoryAdminRestControllerSpringTest { + + @Autowired + private AdminRestController controller; + + @Autowired + private UserRepository repository; + + @Before + public void setUp() throws Exception { + repository.getAll().forEach(u -> repository.delete(u.getId())); + repository.save(USER); + repository.save(ADMIN); + } + + @Test + public void testDelete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { + controller.delete(10); + } +} From 259288850566108c5ef3a6dfecd8bc5aed8529b1 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 24 Mar 2016 23:12:37 +0500 Subject: [PATCH 032/166] HW3 patch 3-09 --- pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 63e4368b02bd..07cfa1c089ba 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,8 @@ 1.1.6 1.7.18 + + 9.4.1208 4.12 @@ -85,6 +87,13 @@ + + + org.postgresql + postgresql + ${postgresql.version} + + junit From 27482e1bcc94eeb141018252bb292e9719782f09 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 24 Mar 2016 23:51:40 +0500 Subject: [PATCH 033/166] HW3 patch 3-09 --- 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..44dd64eb26b8 --- /dev/null +++ b/src/main/resources/db/postgres.properties @@ -0,0 +1,7 @@ +#database.url=jdbc:postgresql://ec2-54-217-202-110.eu-west-1.compute.amazonaws.com:5432/dehm6lvm8bink0?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory +#database.username=wegxlfzjjgxaxy +#database.password=SSQyKKE_e93kiUCR-ehzMcKCxZ + +database.url=jdbc:postgresql://localhost:5432/topjava +database.username=user +database.password=password From aefc67a77d5d4cb5cf46aeeaa8ecf1fe01e37c77 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 26 Mar 2016 15:19:16 +0500 Subject: [PATCH 034/166] HW3 patch 3-10 --- src/main/resources/db/initDB.sql | 25 +++++++++++++++++++++++++ src/main/resources/db/populateDB.sql | 13 +++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/main/resources/db/initDB.sql create mode 100644 src/main/resources/db/populateDB.sql diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql new file mode 100644 index 000000000000..a0631c79567b --- /dev/null +++ b/src/main/resources/db/initDB.sql @@ -0,0 +1,25 @@ +DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS users; +DROP SEQUENCE IF EXISTS global_seq; + +CREATE SEQUENCE global_seq START 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(), + enabled BOOL DEFAULT TRUE, + 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 +); diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql new file mode 100644 index 000000000000..199e2ace615d --- /dev/null +++ b/src/main/resources/db/populateDB.sql @@ -0,0 +1,13 @@ +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'); + +INSERT INTO users (name, email, password) +VALUES ('Admin', 'admin@gmail.com', 'admin'); + +INSERT INTO user_roles (role, user_id) VALUES + ('ROLE_USER', 100000), + ('ROLE_ADMIN', 100001); From 427a5fa3d96083201606b704c911c07a14ad78ec Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 26 Mar 2016 15:53:42 +0500 Subject: [PATCH 035/166] HW3 patch 3-11 --- pom.xml | 5 ++ .../jdbc/JdbcUserRepositoryImpl.java | 84 +++++++++++++++++++ src/main/resources/spring/spring-db.xml | 25 ++++++ 3 files changed, 114 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java create mode 100644 src/main/resources/spring/spring-db.xml diff --git a/pom.xml b/pom.xml index 07cfa1c089ba..47231f550ac1 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,11 @@ ${spring.version} test + + org.springframework + spring-jdbc + ${spring.version} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java new file mode 100644 index 000000000000..5646bad071ed --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -0,0 +1,84 @@ +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 javax.sql.DataSource; +import java.util.List; + +/** + * User: gkislin + * Date: 26.08.2014 + */ + +@Repository +public class JdbcUserRepositoryImpl implements UserRepository { + + private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertUser; + + @Autowired + public JdbcUserRepositoryImpl(DataSource dataSource) { + this.insertUser = new SimpleJdbcInsert(dataSource) + .withTableName("USERS") + .usingGeneratedKeyColumns("id"); + } + + @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 { + namedParameterJdbcTemplate.update( + "UPDATE users SET name=:name, email=:email, password=:password, " + + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map); + } + 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); + } + + @Override + public List getAll() { + return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + } +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml new file mode 100644 index 000000000000..a1f6b09a7464 --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From aed36e0c36d3266b08b5628f8ae3cc86661fad06 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 27 Mar 2016 14:15:08 +0500 Subject: [PATCH 036/166] HW3 patch 3-12 --- .../javawebinar/topjava/model/BaseEntity.java | 23 +++++ .../ru/javawebinar/topjava/model/User.java | 4 + .../jdbc/JdbcUserMealRepositoryImpl.java | 42 ++++++++ .../javawebinar/topjava/util/DbPopulator.java | 28 ++++++ src/main/resources/db/postgres.properties | 2 +- src/main/resources/spring/spring-app.xml | 2 +- src/main/resources/spring/spring-db.xml | 4 + .../ru/javawebinar/topjava/MealTestData.java | 14 +++ .../ru/javawebinar/topjava/UserTestData.java | 75 ++++++++++++++- .../topjava/matcher/ModelMatcher.java | 34 +++++++ .../topjava/service/UserServiceTest.java | 95 +++++++++++++++++++ 11 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DbPopulator.java create mode 100644 src/test/java/ru/javawebinar/topjava/MealTestData.java create mode 100644 src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 60f4b46f1d8e..892beb4c6281 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -5,6 +5,8 @@ * Date: 22.08.2014 */ public class BaseEntity { + public static final int START_SEQ = 100000; + protected Integer id; public BaseEntity() { @@ -25,4 +27,25 @@ public Integer getId() { public boolean isNew() { return (this.id == null); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BaseEntity that = (BaseEntity) o; + if (id == null || that.id == null) { + return false; + } + return id.equals(that.id); + } + + @Override + public int hashCode() { + return (id == null) ? 0 : id; + } + } diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 34b5977a5917..22d746307bf4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -27,6 +27,10 @@ public class User extends NamedEntity { public User() { } + public User(User u) { + this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRoles()); + } + public User(Integer id, String name, String email, String password, Role role, Role... roles) { this(id, name, email, password, UserMealsUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java new file mode 100644 index 000000000000..77c0ba8f639d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.repository.UserMealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * User: gkislin + * Date: 26.08.2014 + */ + +@Repository +public class JdbcUserMealRepositoryImpl implements UserMealRepository { + + @Override + public UserMeal save(UserMeal UserMeal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public UserMeal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java b/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java new file mode 100644 index 000000000000..8d4a6cc3c719 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java @@ -0,0 +1,28 @@ +package ru.javawebinar.topjava.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; + +import javax.sql.DataSource; + +/** + * User: gkislin + * Date: 26.08.2014 + */ +public class DbPopulator extends ResourceDatabasePopulator { + private static final ResourceLoader RESOURCE_LOADER = new DefaultResourceLoader(); + + @Autowired + private DataSource dataSource; + + public DbPopulator(String scriptLocation) { + super(RESOURCE_LOADER.getResource(scriptLocation)); + } + + public void execute() { + DatabasePopulatorUtils.execute(this, dataSource); + } +} diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 44dd64eb26b8..b5841f354d98 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -3,5 +3,5 @@ #database.password=SSQyKKE_e93kiUCR-ehzMcKCxZ database.url=jdbc:postgresql://localhost:5432/topjava -database.username=user +database.username=t_user database.password=password diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 96e40e902b66..04810da82e92 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -14,7 +14,7 @@ - + diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index a1f6b09a7464..051f225300cd 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -6,6 +6,10 @@ + + + + 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..ac44047caa52 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -0,0 +1,14 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.matcher.ModelMatcher; +import ru.javawebinar.topjava.model.UserMeal; + +/** + * GKislin + * 13.03.2015. + */ +public class MealTestData { + + public static final ModelMatcher MATCHER = new ModelMatcher<>(UserMeal::toString); + +} diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index bb8a9fb19788..c1c287c908fa 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -1,16 +1,87 @@ package ru.javawebinar.topjava; +import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.UserMealsUtil; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; /** * GKislin * 24.09.2015. */ public class UserTestData { - public static final int USER_ID = 1; - public static final int ADMIN_ID = 2; + public static final int USER_ID = START_SEQ; + public static final int ADMIN_ID = START_SEQ + 1; public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); + + public static final ModelMatcher MATCHER = new ModelMatcher<>(u -> ((u instanceof TestUser) ? (TestUser) u : new TestUser(u))); + + public static class TestUser extends User { + + public TestUser(User u) { + this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRoles()); + } + + public TestUser(String name, String email, String password, Role role, Role... roles) { + this(null, name, email, password, UserMealsUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + } + + public TestUser(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + super(id, name, email, password, caloriesPerDay, enabled, roles); + } + + public User asUser() { + return new User(this); + } + + @Override + public String toString() { + return "User (" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", password=" + password + + ", authorities=" + roles + + ')'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TestUser that = (TestUser) o; + return Objects.equals(this.password, that.password) + && Objects.equals(this.id, that.id) + && Objects.equals(this.name, that.name) + && Objects.equals(this.email, that.email) + && Objects.equals(this.caloriesPerDay, that.caloriesPerDay) + && Objects.equals(this.enabled, that.enabled); + } + } } diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java new file mode 100644 index 000000000000..f2b18b2e34bd --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -0,0 +1,34 @@ +package ru.javawebinar.topjava.matcher; + +import org.junit.Assert; + +import java.util.Collection; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * GKislin + * 06.01.2015. + * + * @param : entity + * @param : testEntity, converter result for compare + */ +public class ModelMatcher { + protected Function entityConverter; + + public ModelMatcher(Function entityConverter) { + this.entityConverter = entityConverter; + } + + public void assertEquals(T expected, T actual) { + Assert.assertEquals(entityConverter.apply(expected), entityConverter.apply(actual)); + } + + public void assertCollectionEquals(Collection expected, Collection actual) { + Assert.assertEquals(map(expected, entityConverter), map(actual, entityConverter)); + } + + public static Collection map(Collection collection, Function converter) { + return collection.stream().map(converter).collect(Collectors.toList()); + } +} 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..c9c6eb8fd0f0 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -0,0 +1,95 @@ +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.dao.DataAccessException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.UserTestData.*; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.DbPopulator; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import static ru.javawebinar.topjava.UserTestData.*; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +public class UserServiceTest { + + @Autowired + protected UserService service; + + @Autowired + private DbPopulator dbPopulator; + + @Before + public void setUp() throws Exception { + dbPopulator.execute(); + } + + @Test + public void testSave() throws Exception { + TestUser tu = new TestUser(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); + User created = service.save(tu.asUser()); + tu.setId(created.getId()); + MATCHER.assertCollectionEquals(Arrays.asList(ADMIN, tu, USER), service.getAll()); + } + + @Test(expected = DataAccessException.class) + public void testDuplicateMailSave() throws Exception { + service.save(new TestUser("Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER).asUser()); + } + + @Test + public void testDelete() throws Exception { + service.delete(USER_ID); + MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), service.getAll()); + } + + @Test(expected = NotFoundException.class) + public void testNotFoundDelete() throws Exception { + service.delete(1); + } + + @Test + public void testGet() throws Exception { + User user = service.get(USER_ID); + MATCHER.assertEquals(USER, user); + } + + @Test(expected = NotFoundException.class) + public void testGetNotFound() throws Exception { + service.get(1); + } + + @Test + public void testGetByEmail() throws Exception { + User user = service.getByEmail("user@yandex.ru"); + MATCHER.assertEquals(USER, user); + } + + @Test + public void testGetAll() throws Exception { + Collection all = service.getAll(); + MATCHER.assertCollectionEquals(Arrays.asList(ADMIN, USER), all); + } + + @Test + public void testUpdate() throws Exception { + TestUser updated = new TestUser(USER); + updated.setName("UpdatedName"); + updated.setCaloriesPerDay(330); + service.update(updated.asUser()); + MATCHER.assertEquals(updated, service.get(USER_ID)); + } +} \ No newline at end of file From ba20a3063ac52a31249936d01707098465c8938d Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 27 Mar 2016 14:16:43 +0500 Subject: [PATCH 037/166] HW3 patch 3-13 --- src/test/resources/logback-test.xml | 21 +++++++++++++++++++++ 1 file changed, 21 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..80c38026d409 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,21 @@ + + + + true + + + + + UTF-8 + %-5level %logger{0} - %msg%n + + + + + + + + + + + \ No newline at end of file From 4553961694e2fc34679709eca2e7009310de1a70 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 27 Mar 2016 14:17:43 +0500 Subject: [PATCH 038/166] HW3 patch 3-14 --- src/main/java/ru/javawebinar/topjava/LoggedUser.java | 4 +++- src/main/java/ru/javawebinar/topjava/web/MealServlet.java | 2 +- src/main/webapp/index.html | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java index 115866e29f99..813658a2b4c9 100644 --- a/src/main/java/ru/javawebinar/topjava/LoggedUser.java +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -2,12 +2,14 @@ import ru.javawebinar.topjava.util.UserMealsUtil; +import ru.javawebinar.topjava.model.BaseEntity; + /** * GKislin * 06.03.2015. */ public class LoggedUser { - public static int id = 1; + public static int id = BaseEntity.START_SEQ; public static int id() { return id; diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 025642a40de5..4b5ab54eeaad 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -32,7 +32,7 @@ public class MealServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { super.init(config); - springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-db.xml"); mealController = springContext.getBean(UserMealRestController.class); } diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index b178fa0eeba4..f3c54386a713 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -9,8 +9,8 @@

Проект " Login as: From 4c88bdc831ece6f19c249ac57927a9e2050ea6fc Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 27 Mar 2016 14:52:28 +0500 Subject: [PATCH 039/166] HW3 patch 4-1 --- .gitignore | 1 + .../javawebinar/topjava/model/UserMeal.java | 21 +++++- .../jdbc/JdbcUserMealRepositoryImpl.java | 71 ++++++++++++++++--- src/main/resources/db/initDB.sql | 11 +++ src/main/resources/db/populateDB.sql | 11 +++ .../ru/javawebinar/topjava/SpringMain.java | 2 +- .../web/InMemoryAdminRestControllerTest.java | 2 +- 7 files changed, 105 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index c82fcde2c451..3185ada6371d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target *.iml log *.patch +patch /Prepare_to_HW0.patch diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index 5fc41074a979..28f585dbeb57 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -8,11 +8,14 @@ */ public class UserMeal extends BaseEntity { - private final LocalDateTime dateTime; + private LocalDateTime dateTime; - private final String description; + private String description; - private final int calories; + private int calories; + + public UserMeal() { + } public UserMeal(LocalDateTime dateTime, String description, int calories) { this(null, dateTime, description, calories); @@ -37,6 +40,18 @@ public int getCalories() { return calories; } + 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 "UserMeal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java index 77c0ba8f639d..692c565d0dac 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java @@ -1,9 +1,18 @@ package ru.javawebinar.topjava.repository.jdbc; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; +import javax.sql.DataSource; import java.time.LocalDateTime; import java.util.List; @@ -15,28 +24,72 @@ @Repository public class JdbcUserMealRepositoryImpl implements UserMealRepository { + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(UserMeal.class); + +/* + private static final RowMapper ROW_MAPPER = + (rs, rowNum) -> + new UserMeal(rs.getInt("id"), rs.getTimestamp("date_time").toLocalDateTime(), + rs.getString("description"), rs.getInt("calories")); +*/ + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertUserMeal; + + @Autowired + public JdbcUserMealRepositoryImpl(DataSource dataSource) { + this.insertUserMeal = new SimpleJdbcInsert(dataSource) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + } + @Override - public UserMeal save(UserMeal UserMeal, int userId) { - return null; + public UserMeal save(UserMeal userMeal, int userId) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", userMeal.getId()) + .addValue("description", userMeal.getDescription()) + .addValue("calories", userMeal.getCalories()) + .addValue("date_time", userMeal.getDateTime()) + .addValue("user_id", userId); + + if (userMeal.isNew()) { + Number newId = insertUserMeal.executeAndReturnKey(map); + userMeal.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 userMeal; } @Override public boolean delete(int id, int userId) { - return false; + return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; } @Override public UserMeal get(int id, int userId) { - return null; + List userMeals = jdbcTemplate.query( + "SELECT * FROM meals WHERE id = ? AND user_id = ?", ROW_MAPPER, id, userId); + return DataAccessUtils.singleResult(userMeals); } - - @Override public List getAll(int userId) { - return null; + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? ORDER BY date_time DESC", ROW_MAPPER, userId); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC", + ROW_MAPPER, userId, startDate, endDate); } -} +} \ No newline at end of file diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index a0631c79567b..62eca097467b 100644 --- a/src/main/resources/db/initDB.sql +++ b/src/main/resources/db/initDB.sql @@ -1,4 +1,5 @@ DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS meals; DROP TABLE IF EXISTS users; DROP SEQUENCE IF EXISTS global_seq; @@ -23,3 +24,13 @@ 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'), + user_id INTEGER NOT NULL, + date_time TIMESTAMP, + description TEXT, + calories INT, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals(user_id, date_time) \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index 199e2ace615d..0fbffd0dfb58 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -1,4 +1,5 @@ DELETE FROM user_roles; +DELETE FROM meals; DELETE FROM users; ALTER SEQUENCE global_seq RESTART WITH 100000; @@ -11,3 +12,13 @@ VALUES ('Admin', 'admin@gmail.com', 'admin'); INSERT INTO user_roles (role, user_id) VALUES ('ROLE_USER', 100000), ('ROLE_ADMIN', 100001); + +INSERT INTO meals (date_time, description, calories, user_id) VALUES + ('2015-05-30 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-30 13:00:00', 'Обед', 1000, 100000), + ('2015-05-30 20:00:00', 'Ужин', 500, 100000), + ('2015-05-31 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-31 13:00:00', 'Обед', 1000, 100000), + ('2015-05-31 20:00:00', 'Ужин', 510, 100000), + ('2015-06-01 14:00:00', 'Админ ланч', 510, 100001), + ('2015-06-01 21:00:00', 'Админ ужин', 1500, 100001); diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 1e61564819e0..321ceefc637b 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -19,7 +19,7 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/spring-db.xml")) { System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); System.out.println(adminUserController.create(UserTestData.USER)); diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java index fe08ae623eec..78ed6ee73048 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -21,7 +21,7 @@ public class InMemoryAdminRestControllerTest { @BeforeClass public static void beforeClass() { - appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/spring-db.xml"); System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); controller = appCtx.getBean(AdminRestController.class); } From f7130b09acd0a3dc7cb7f7abb603349282cfd28d Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 27 Mar 2016 15:09:11 +0500 Subject: [PATCH 040/166] HW3 patch 4-2 --- pom.xml | 7 ++ .../ru/javawebinar/topjava/MealTestData.java | 29 ++++++ .../topjava/service/UserMealServiceTest.java | 92 +++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java diff --git a/pom.xml b/pom.xml index 47231f550ac1..1d13bea5df36 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,13 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + -Dfile.encoding=UTF-8 + + diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index ac44047caa52..173673299f57 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -3,6 +3,14 @@ import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.UserMeal; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +import static java.time.LocalDateTime.of; +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; + /** * GKislin * 13.03.2015. @@ -11,4 +19,25 @@ public class MealTestData { public static final ModelMatcher MATCHER = new ModelMatcher<>(UserMeal::toString); + public static final int MEAL1_ID = START_SEQ + 2; + public static final int ADMIN_MEAL_ID = START_SEQ + 8; + + public static final UserMeal MEAL1 = new UserMeal(MEAL1_ID, LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500); + public static final UserMeal MEAL2 = new UserMeal(MEAL1_ID + 1, LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000); + public static final UserMeal MEAL3 = new UserMeal(MEAL1_ID + 2, LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500); + public static final UserMeal MEAL4 = new UserMeal(MEAL1_ID + 3, LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500); + public static final UserMeal MEAL5 = new UserMeal(MEAL1_ID + 4, LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 1000); + public static final UserMeal MEAL6 = new UserMeal(MEAL1_ID + 5, LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510); + public static final UserMeal ADMIN_MEAL = new UserMeal(ADMIN_MEAL_ID, LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510); + public static final UserMeal ADMIN_MEAL2 = new UserMeal(ADMIN_MEAL_ID + 1, LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500); + + public static final List USER_MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + + public static UserMeal getCreated() { + return new UserMeal(null, of(2015, Month.JUNE, 1, 18, 0), "Созданный ужин", 300); + } + + public static UserMeal getUpdated() { + return new UserMeal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); + } } diff --git a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java new file mode 100644 index 000000000000..91ba08d6c015 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java @@ -0,0 +1,92 @@ +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.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.util.DbPopulator; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.Month; +import java.util.Arrays; + +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(SpringJUnit4ClassRunner.class) +public class UserMealServiceTest { + + @Autowired + protected UserMealService service; + + @Autowired + private DbPopulator dbPopulator; + + @Before + public void setUp() throws Exception { + dbPopulator.execute(); + } + + @Test + public void testDelete() throws Exception { + service.delete(MealTestData.MEAL1_ID, USER_ID); + MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID)); + } + + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { + service.delete(MEAL1_ID, 1); + } + + @Test + public void testSave() throws Exception { + UserMeal created = getCreated(); + service.save(created, USER_ID); + MATCHER.assertCollectionEquals(Arrays.asList(created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), service.getAll(USER_ID)); + } + + @Test + public void testGet() throws Exception { + UserMeal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); + MATCHER.assertEquals(ADMIN_MEAL, actual); + } + + @Test(expected = NotFoundException.class) + public void testGetNotFound() throws Exception { + service.get(MEAL1_ID, ADMIN_ID); + } + + @Test + public void testUpdate() throws Exception { + UserMeal updated = getUpdated(); + service.update(updated, USER_ID); + MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); + } + + @Test(expected = NotFoundException.class) + public void testNotFoundUpdate() throws Exception { + UserMeal item = service.get(MEAL1_ID, USER_ID); + service.update(item, ADMIN_ID); + } + + @Test + public void testGetAll() throws Exception { + MATCHER.assertCollectionEquals(USER_MEALS, service.getAll(USER_ID)); + } + + @Test + public void testGetBetween() throws Exception { + MATCHER.assertCollectionEquals(Arrays.asList(MEAL3, MEAL2, MEAL1), + service.getBetweenDates(LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID)); + } +} \ No newline at end of file From 2e636e6fc2881cbc15253739c4adc19a60c27798 Mon Sep 17 00:00:00 2001 From: ilnur Date: Wed, 30 Mar 2016 14:33:45 +0300 Subject: [PATCH 041/166] update MealServlet, mealList.jsp, web.xml --- .../ru/javawebinar/topjava/web/meal/UserMealRestController.java | 1 + src/main/resources/db/postgres.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index 305604f46144..b9ff52ebe97a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -7,6 +7,7 @@ import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.service.UserMealService; +import ru.javawebinar.topjava.service.UserMealServiceImpl; import ru.javawebinar.topjava.to.UserMealWithExceed; import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.util.UserMealsUtil; diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index b5841f354d98..44dd64eb26b8 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -3,5 +3,5 @@ #database.password=SSQyKKE_e93kiUCR-ehzMcKCxZ database.url=jdbc:postgresql://localhost:5432/topjava -database.username=t_user +database.username=user database.password=password From 4ee92ae5dbd455379855feb6a887b051b149b877 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 3 Apr 2016 13:54:02 +0500 Subject: [PATCH 042/166] HW3 patch 4-3 --- README.md | 4 ++++ .../java/ru/javawebinar/topjava/LoggedUser.java | 5 ++++- .../java/ru/javawebinar/topjava/util/TimeUtil.java | 7 +++++-- .../topjava/util/exception/ExceptionUtil.java | 7 ++++++- .../ru/javawebinar/topjava/web/MealServlet.java | 8 ++++---- .../topjava/web/user/AbstractUserController.java | 14 +++++++------- .../mock/InMemoryUserMealRepositoryImpl.java | 7 ++++--- .../mock/InMemoryUserRepositoryImpl.java | 5 +++-- 8 files changed, 37 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 613028cc00aa..ba0f51330725 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ Java Enterprise Online Project =============================== + +[![Codacy Badge](https://api.codacy.com/project/badge/grade/0aa43bd345364fab8d455f04e367fe0c)](https://www.codacy.com/app/javawebinar/topjava06) + + Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java index 813658a2b4c9..6d90cc328d69 100644 --- a/src/main/java/ru/javawebinar/topjava/LoggedUser.java +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -9,7 +9,10 @@ * 06.03.2015. */ public class LoggedUser { - public static int id = BaseEntity.START_SEQ; + private LoggedUser() { + } + + private static int id = BaseEntity.START_SEQ; public static int id() { return id; diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 872dbd411e06..a008b652e479 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -12,16 +12,19 @@ * 07.01.2015. */ public class TimeUtil { - public static final DateTimeFormatter DATE_TME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); public static final LocalDate MIN_DATE = LocalDate.of(0, 1, 1); public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + private TimeUtil() { + } + public static > boolean isBetween(T value, T start, T end) { return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; } public static String toString(LocalDateTime ldt) { - return ldt == null ? "" : ldt.format(DATE_TME_FORMATTER); + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); } public static LocalDate parseLocalDate(String str) { diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java index de97182f9d87..9fd18938830f 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java @@ -6,6 +6,9 @@ * Date: 14.05.2014 */ public class ExceptionUtil { + private ExceptionUtil() { + } + public static void check(boolean found, int id) { check(found, "id=" + id); } @@ -20,6 +23,8 @@ public static T check(T object, String msg) { } public static void check(boolean found, String msg) { - if (!found) throw new NotFoundException("Not found entity with " + msg); + if (!found) { + throw new NotFoundException("Not found entity with " + msg); + } } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 4b5ab54eeaad..1e7f28d7dbf6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -42,7 +42,7 @@ public void destroy() { super.destroy(); } - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); if (action == null) { @@ -77,13 +77,13 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t LOG.info("getAll"); request.setAttribute("mealList", mealController.getAll()); request.getRequestDispatcher("mealList.jsp").forward(request, response); - } else if (action.equals("delete")) { + } else if ("delete".equals(action)) { int id = getId(request); LOG.info("Delete {}", id); mealController.delete(id); response.sendRedirect("meals"); } else { - final UserMeal meal = action.equals("create") ? + final UserMeal meal = "create".equals(action) ? new UserMeal(LocalDateTime.now(), "", 1000) : // create mealController.get(getId(request)); // update request.setAttribute("meal", meal); @@ -98,7 +98,7 @@ private String resetParam(String param, HttpServletRequest request) { } private int getId(HttpServletRequest request) { - String paramId = Objects.requireNonNull(request.getParameter("id")); + String paramId = Objects.requireNonNull(request.getParameter("id"), "parameter id must not be null"); return Integer.valueOf(paramId); } } 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 e2b847678af2..c519e53e6bee 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -12,40 +12,40 @@ * User: gkislin */ public abstract class AbstractUserController { - protected final Logger LOG = LoggerFactory.getLogger(getClass()); + protected final Logger log = LoggerFactory.getLogger(getClass()); @Autowired private UserService service; public List getAll() { - LOG.info("getAll"); + log.info("getAll"); return service.getAll(); } public User get(int id) { - LOG.info("get " + id); + log.info("get " + id); return service.get(id); } public User create(User user) { user.setId(null); - LOG.info("create " + user); + log.info("create " + user); return service.save(user); } public void delete(int id) { - LOG.info("delete " + id); + log.info("delete " + id); service.delete(id); } public void update(User user, int id) { user.setId(id); - LOG.info("update " + user); + log.info("update " + user); service.update(user); } public User getByMail(String email) { - LOG.info("getByEmail " + email); + log.info("getByEmail " + email); return service.getByEmail(email); } } diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java index d68c3e3555b7..190c7281c572 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.repository.mock; import org.springframework.stereotype.Repository; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; import ru.javawebinar.topjava.util.TimeUtil; @@ -38,7 +39,7 @@ public class InMemoryUserMealRepositoryImpl implements UserMealRepository { @Override public UserMeal save(UserMeal userMeal, int userId) { - Objects.requireNonNull(userMeal); + Assert.notNull(userMeal, "userMeal must not be null"); Integer mealId = userMeal.getId(); @@ -75,8 +76,8 @@ public Collection getAll(int userId) { @Override public Collection getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { - Objects.requireNonNull(startDateTime); - Objects.requireNonNull(endDateTime); + Assert.notNull(startDateTime, "startDateTime must not be null"); + Assert.notNull(endDateTime, "endDateTime must not be null"); return getAll(userId).stream() .filter(um -> TimeUtil.isBetween(um.getDateTime(), startDateTime, endDateTime)) .collect(Collectors.toList()); diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java index a76cfe226c06..4e63c009f71c 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -3,6 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; @@ -31,7 +32,7 @@ public class InMemoryUserRepositoryImpl implements UserRepository { @Override public User save(User user) { - Objects.requireNonNull(user); + Assert.notNull(user, "user must not be null"); if (user.isNew()) { user.setId(counter.incrementAndGet()); } @@ -66,7 +67,7 @@ public List getAll() { @Override public User getByEmail(String email) { - Objects.requireNonNull(email); + Assert.notNull(email, "email must not be null"); return repository.values().stream().filter(u -> email.equals(u.getEmail())).findFirst().orElse(null); } } From 0293fc87c2b5747c247c3851234142c8763cf3b0 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 3 Apr 2016 14:11:53 +0500 Subject: [PATCH 043/166] fix bug --- src/main/resources/db/postgres.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index b5841f354d98..44dd64eb26b8 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -3,5 +3,5 @@ #database.password=SSQyKKE_e93kiUCR-ehzMcKCxZ database.url=jdbc:postgresql://localhost:5432/topjava -database.username=t_user +database.username=user database.password=password From fc76026f82760d5429260eac5c29ddf42b57b8dc Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 3 Apr 2016 14:14:01 +0500 Subject: [PATCH 044/166] HW4 patch 4-4 --- .../javawebinar/topjava/util/DbPopulator.java | 28 ------------------- src/main/resources/db/postgres.properties | 2 ++ src/main/resources/spring/spring-db.xml | 11 ++++---- .../topjava/service/UserMealServiceTest.java | 13 ++------- .../topjava/service/UserServiceTest.java | 13 ++------- 5 files changed, 14 insertions(+), 53 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/util/DbPopulator.java diff --git a/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java b/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java deleted file mode 100644 index 8d4a6cc3c719..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.javawebinar.topjava.util; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.ResourceLoader; -import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; - -import javax.sql.DataSource; - -/** - * User: gkislin - * Date: 26.08.2014 - */ -public class DbPopulator extends ResourceDatabasePopulator { - private static final ResourceLoader RESOURCE_LOADER = new DefaultResourceLoader(); - - @Autowired - private DataSource dataSource; - - public DbPopulator(String scriptLocation) { - super(RESOURCE_LOADER.getResource(scriptLocation)); - } - - public void execute() { - DatabasePopulatorUtils.execute(this, dataSource); - } -} diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 44dd64eb26b8..1b5ffc9c73b1 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,3 +5,5 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password + +database.init=true diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 051f225300cd..0e228352bad7 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,14 +1,15 @@ + 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"> - - - + + + + Date: Sun, 3 Apr 2016 14:55:11 +0500 Subject: [PATCH 045/166] HW4 patch 4-4fix bug --- src/main/resources/db/postgres.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 1b5ffc9c73b1..96fc29c601f4 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,5 +5,5 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password - database.init=true + From 81b4526554796fa5e551eb6068bc6b12361c73a4 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 4 Apr 2016 11:09:26 +0500 Subject: [PATCH 046/166] refactor - delete out-of-date comment --- .../java/ru/javawebinar/topjava/LoggedUser.java | 4 ---- src/main/java/ru/javawebinar/topjava/Main.java | 7 ------- .../ru/javawebinar/topjava/model/BaseEntity.java | 4 ---- .../javawebinar/topjava/model/NamedEntity.java | 4 ---- .../java/ru/javawebinar/topjava/model/Role.java | 4 ---- .../java/ru/javawebinar/topjava/model/User.java | 4 ---- .../ru/javawebinar/topjava/model/UserMeal.java | 4 ---- .../topjava/repository/UserMealRepository.java | 4 ---- .../topjava/repository/UserRepository.java | 4 ---- .../jdbc/JdbcUserMealRepositoryImpl.java | 5 ----- .../repository/jdbc/JdbcUserRepositoryImpl.java | 5 ----- .../topjava/service/UserMealService.java | 4 ---- .../topjava/service/UserMealServiceImpl.java | 4 ---- .../javawebinar/topjava/service/UserService.java | 4 ---- .../topjava/service/UserServiceImpl.java | 4 ---- .../topjava/to/UserMealWithExceed.java | 4 ---- .../ru/javawebinar/topjava/util/TimeUtil.java | 4 ---- .../javawebinar/topjava/util/UserMealsUtil.java | 4 ---- .../topjava/util/exception/ExceptionUtil.java | 5 ----- .../util/exception/NotFoundException.java | 4 ---- .../ru/javawebinar/topjava/web/MealServlet.java | 4 ---- .../ru/javawebinar/topjava/web/UserServlet.java | 4 ---- .../topjava/web/meal/UserMealRestController.java | 4 ---- .../topjava/web/user/AbstractUserController.java | 3 --- .../topjava/web/user/AdminRestController.java | 4 ---- .../topjava/web/user/ProfileRestController.java | 4 ---- .../ru/javawebinar/topjava/MealTestData.java | 4 ---- .../java/ru/javawebinar/topjava/SpringMain.java | 4 ---- .../ru/javawebinar/topjava/UserTestData.java | 16 ---------------- .../topjava/matcher/ModelMatcher.java | 3 --- .../mock/InMemoryUserMealRepositoryImpl.java | 4 ---- .../mock/InMemoryUserRepositoryImpl.java | 4 ---- .../InMemoryAdminRestControllerSpringTest.java | 4 ---- 33 files changed, 148 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java index 6d90cc328d69..ca7fc8266330 100644 --- a/src/main/java/ru/javawebinar/topjava/LoggedUser.java +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -4,10 +4,6 @@ import ru.javawebinar.topjava.model.BaseEntity; -/** - * GKislin - * 06.03.2015. - */ public class LoggedUser { private LoggedUser() { } diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java index e78cff96c229..b3061ff4a240 100644 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ b/src/main/java/ru/javawebinar/topjava/Main.java @@ -1,12 +1,5 @@ package ru.javawebinar.topjava; -/** - * User: gkislin - * Date: 05.08.2015 - * - * @link http://caloriesmng.herokuapp.com/ - * @link https://github.com/JavaOPs/topjava - */ public class Main { public static void main(String[] args) { System.out.println("Hello Topjava Enterprise!"); diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 892beb4c6281..3ff0cdf8c4cc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,9 +1,5 @@ package ru.javawebinar.topjava.model; -/** - * User: gkislin - * Date: 22.08.2014 - */ public class BaseEntity { public static final int START_SEQ = 100000; diff --git a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java index ca92e394dc98..d38d0ead73b8 100644 --- a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -1,9 +1,5 @@ package ru.javawebinar.topjava.model; -/** - * User: gkislin - * Date: 22.08.2014 - */ public class NamedEntity extends BaseEntity { protected String name; diff --git a/src/main/java/ru/javawebinar/topjava/model/Role.java b/src/main/java/ru/javawebinar/topjava/model/Role.java index f0de2b4c4a81..616f2cc9ee66 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Role.java +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -1,9 +1,5 @@ package ru.javawebinar.topjava.model; -/** - * User: gkislin - * Date: 22.08.2014 - */ public enum Role { ROLE_USER, ROLE_ADMIN diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 22d746307bf4..0ec43414efc1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -6,10 +6,6 @@ import java.util.EnumSet; import java.util.Set; -/** - * User: gkislin - * Date: 22.08.2014 - */ public class User extends NamedEntity { protected String email; diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index 28f585dbeb57..76eadba22975 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -2,10 +2,6 @@ import java.time.LocalDateTime; -/** - * GKislin - * 11.01.2015. - */ public class UserMeal extends BaseEntity { private LocalDateTime dateTime; diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java index 8a7687fb85cb..3b47215e470c 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java @@ -5,10 +5,6 @@ import java.time.LocalDateTime; import java.util.Collection; -/** - * GKislin - * 06.03.2015. - */ public interface UserMealRepository { // null if updated meal do not belong to userId UserMeal save(UserMeal userMeal, int userId); diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java index 24c1c45beca6..8b31e7414ba2 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -4,10 +4,6 @@ import java.util.List; -/** - * User: gkislin - * Date: 22.08.2014 - */ public interface UserRepository { User save(User user); diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java index 692c565d0dac..de42c6ac75b1 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java @@ -16,11 +16,6 @@ import java.time.LocalDateTime; import java.util.List; -/** - * User: gkislin - * Date: 26.08.2014 - */ - @Repository public class JdbcUserMealRepositoryImpl implements UserMealRepository { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java index 5646bad071ed..ac968db43e1d 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -14,11 +14,6 @@ import javax.sql.DataSource; import java.util.List; -/** - * User: gkislin - * Date: 26.08.2014 - */ - @Repository public class JdbcUserRepositoryImpl implements UserRepository { diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java index 9e4ea6413504..76c48ae53875 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java @@ -8,10 +8,6 @@ import java.time.LocalTime; import java.util.Collection; -/** - * GKislin - * 15.06.2015. - */ public interface UserMealService { UserMeal get(int id, int userId) throws NotFoundException; diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java index fe2aa873de5c..10f26d5da445 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java @@ -9,10 +9,6 @@ import java.time.LocalDateTime; import java.util.Collection; -/** - * GKislin - * 06.03.2015. - */ @Service public class UserMealServiceImpl implements UserMealService { diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 85c15e48f951..4a1567e020ad 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -6,10 +6,6 @@ import java.util.List; -/** - * User: gkislin - * Date: 22.08.2014 - */ public interface UserService { User save(User user); diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index f83d039b7fbc..873fbbb91ed8 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -9,10 +9,6 @@ import java.util.List; -/** - * GKislin - * 06.03.2015. - */ @Service public class UserServiceImpl implements UserService { diff --git a/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java index 3dcc17d30b2f..6d2848bf254a 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java @@ -2,10 +2,6 @@ import java.time.LocalDateTime; -/** - * GKislin - * 11.01.2015. - */ public class UserMealWithExceed { protected final Integer id; diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index a008b652e479..1a4ac02be7e7 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -7,10 +7,6 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; -/** - * GKislin - * 07.01.2015. - */ public class TimeUtil { public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); public static final LocalDate MIN_DATE = LocalDate.of(0, 1, 1); diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index dea298e4cf4c..a788db999ca5 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -10,10 +10,6 @@ import java.util.*; import java.util.stream.Collectors; -/** - * GKislin - * 31.05.2015. - */ public class UserMealsUtil { public static final List MEAL_LIST = Arrays.asList( new UserMeal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java index 9fd18938830f..479c1f3cf483 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java @@ -1,10 +1,5 @@ package ru.javawebinar.topjava.util.exception; - -/** - * User: gkislin - * Date: 14.05.2014 - */ public class ExceptionUtil { private ExceptionUtil() { } diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java index 7a770f0db834..3fb8042703eb 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -1,9 +1,5 @@ package ru.javawebinar.topjava.util.exception; -/** - * User: gkislin - * Date: 19.08.2014 - */ public class NotFoundException extends RuntimeException { public NotFoundException(String message) { super(message); diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 1e7f28d7dbf6..a63b7103927d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -19,10 +19,6 @@ import java.time.LocalTime; import java.util.Objects; -/** - * User: gkislin - * Date: 19.08.2014 - */ public class MealServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index b76bd0771733..340bafedc6cc 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -11,10 +11,6 @@ import static org.slf4j.LoggerFactory.getLogger; -/** - * User: gkislin - * Date: 19.08.2014 - */ public class UserServlet extends HttpServlet { private static final Logger LOG = getLogger(UserServlet.class); diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index b9ff52ebe97a..d928cb78f8c5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -16,10 +16,6 @@ import java.time.LocalTime; import java.util.List; -/** - * GKislin - * 06.03.2015. - */ @Controller public class UserMealRestController { private static final Logger LOG = LoggerFactory.getLogger(UserMealRestController.class); 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 c519e53e6bee..23cc33c46fc9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -8,9 +8,6 @@ import java.util.List; -/** - * User: gkislin - */ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); 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 d6b8bb420803..da439f8bf7ec 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -5,10 +5,6 @@ import java.util.List; -/** - * GKislin - * 06.03.2015. - */ @Controller public class AdminRestController extends AbstractUserController { 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 1114e57eeb1e..478ed36307ba 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -4,10 +4,6 @@ import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.model.User; -/** - * GKislin - * 06.03.2015. - */ @Controller public class ProfileRestController extends AbstractUserController { diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index 173673299f57..2d70c7d54d0a 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -11,10 +11,6 @@ import static java.time.LocalDateTime.of; import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; -/** - * GKislin - * 13.03.2015. - */ public class MealTestData { public static final ModelMatcher MATCHER = new ModelMatcher<>(UserMeal::toString); diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 321ceefc637b..b9c235662be8 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -12,10 +12,6 @@ import java.util.Arrays; import java.util.List; -/** - * User: gkislin - * Date: 22.08.2014 - */ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index c1c287c908fa..8888d536928e 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -11,22 +11,6 @@ import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; -import java.util.EnumSet; -import java.util.Objects; -import java.util.Set; - -import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; - -import java.util.EnumSet; -import java.util.Objects; -import java.util.Set; - -import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; - -/** - * GKislin - * 24.09.2015. - */ public class UserTestData { 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/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index f2b18b2e34bd..f2af6c1e1551 100644 --- a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -7,9 +7,6 @@ import java.util.stream.Collectors; /** - * GKislin - * 06.01.2015. - * * @param : entity * @param : testEntity, converter result for compare */ diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java index 190c7281c572..659636c6e4a6 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java @@ -17,10 +17,6 @@ import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; -/** - * GKislin - * 15.09.2015. - */ @Repository public class InMemoryUserMealRepositoryImpl implements UserMealRepository { diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java index 4e63c009f71c..5c53c41023ce 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -17,10 +17,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -/** - * GKislin - * 15.06.2015. - */ @Repository public class InMemoryUserRepositoryImpl implements UserRepository { private static final Logger LOG = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java index 9396810afe48..ded00ae83578 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -18,10 +18,6 @@ import static ru.javawebinar.topjava.UserTestData.ADMIN; import static ru.javawebinar.topjava.UserTestData.USER; -/** - * GKislin - * 13.03.2015. - */ @ContextConfiguration("classpath:spring/spring-app.xml") @RunWith(SpringJUnit4ClassRunner.class) public class InMemoryAdminRestControllerSpringTest { From b14f3bcfb9e08a6d31cd4041950821c07e5102e6 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 5 Apr 2016 16:06:59 +0500 Subject: [PATCH 047/166] refactor - delete out-of-date comment --- src/main/resources/spring/spring-app.xml | 2 -- src/main/resources/spring/spring-db.xml | 2 ++ src/test/java/ru/javawebinar/topjava/SpringMain.java | 6 ++++-- .../topjava/web/InMemoryAdminRestControllerSpringTest.java | 2 +- .../topjava/web/InMemoryAdminRestControllerTest.java | 1 + src/test/resources/spring/mock.xml | 7 +++++++ 6 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/spring/mock.xml diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 04810da82e92..eff98648bc5a 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -14,8 +14,6 @@ - - diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 0e228352bad7..a2c9da98e9f8 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -6,6 +6,8 @@ + + diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index b9c235662be8..9fe6a004b653 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -2,6 +2,8 @@ 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.UserMealWithExceed; import ru.javawebinar.topjava.web.meal.UserMealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; @@ -15,10 +17,10 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/spring-db.xml")) { + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/mock.xml")) { System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); - System.out.println(adminUserController.create(UserTestData.USER)); + System.out.println(adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); System.out.println(); UserMealRestController mealController = appCtx.getBean(UserMealRestController.class); diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java index ded00ae83578..e82101a7146c 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -18,7 +18,7 @@ import static ru.javawebinar.topjava.UserTestData.ADMIN; import static ru.javawebinar.topjava.UserTestData.USER; -@ContextConfiguration("classpath:spring/spring-app.xml") +@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/mock.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class InMemoryAdminRestControllerSpringTest { diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java index 78ed6ee73048..ef619db86394 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -21,6 +21,7 @@ public class InMemoryAdminRestControllerTest { @BeforeClass public static void beforeClass() { + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/mock.xml"); appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/spring-db.xml"); System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); controller = appCtx.getBean(AdminRestController.class); diff --git a/src/test/resources/spring/mock.xml b/src/test/resources/spring/mock.xml new file mode 100644 index 000000000000..c7c9f62cc5d8 --- /dev/null +++ b/src/test/resources/spring/mock.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file From f3af793736990d9451a881f70719a81047b633f9 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 5 Apr 2016 17:21:33 +0500 Subject: [PATCH 048/166] HW4 patch 4-6 and 4-7 --- pom.xml | 28 +++++++- .../javawebinar/topjava/model/BaseEntity.java | 6 ++ .../topjava/model/NamedEntity.java | 12 ++++ .../ru/javawebinar/topjava/model/User.java | 39 +++++++++-- .../javawebinar/topjava/model/UserMeal.java | 18 ++++- .../jpa/JpaUserMealRepositoryImpl.java | 42 +++++++++++ .../repository/jpa/JpaUserRepositoryImpl.java | 70 +++++++++++++++++++ src/main/resources/db/postgres.properties | 3 + src/main/resources/spring/spring-db.xml | 53 +++++++++++--- .../ru/javawebinar/topjava/UserTestData.java | 2 +- 10 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java diff --git a/pom.xml b/pom.xml index 1d13bea5df36..abef64c3358d 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,10 @@ 9.4.1208 4.12 + + + 5.1.0.Final + 5.2.4.Final @@ -128,10 +132,30 @@ org.springframework - spring-jdbc + spring-orm ${spring.version} - + + org.hibernate + hibernate-entitymanager + ${hibernate.version} + + + javax.transaction + jta + 1.1 + runtime + + + org.hibernate + hibernate-validator + ${hibernate-validator.version} + + + javax.el + javax.el-api + 2.2.5 + diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 3ff0cdf8c4cc..756b446b0ed5 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,8 +1,14 @@ package ru.javawebinar.topjava.model; +import javax.persistence.*; +@MappedSuperclass +@Access(AccessType.FIELD) public class BaseEntity { public static final int START_SEQ = 100000; + @Id + @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") protected Integer id; public BaseEntity() { diff --git a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java index d38d0ead73b8..e593d37981e7 100644 --- a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -1,7 +1,19 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +@MappedSuperclass public class NamedEntity extends BaseEntity { + @NotEmpty + @Column(name = "name", nullable = false) protected String name; public NamedEntity() { diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 0ec43414efc1..771f304e4741 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -2,31 +2,62 @@ import ru.javawebinar.topjava.util.UserMealsUtil; +import org.hibernate.validator.constraints.Email; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.persistence.*; +import javax.validation.constraints.Digits; import java.util.Date; import java.util.EnumSet; import java.util.Set; +/** + * User: gkislin + * Date: 22.08.2014 + */ +@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 NamedEntity { + public static final String DELETE = "User.delete"; + public static final String ALL_SORTED = "User.getAllSorted"; + public static final String BY_EMAIL = "User.getByEmail"; + + @Column(name = "email", nullable = false, unique = true) + @Email + @NotEmpty protected String email; + @Column(name = "password", nullable = false) + @NotEmpty + @Length(min = 5) protected String password; + @Column(name = "enabled", nullable = false) protected boolean enabled = true; + @Column(name = "registered", columnDefinition = "timestamp default now()") protected Date registered = new Date(); + @Enumerated(EnumType.STRING) + @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) + @Column(name = "role") + @ElementCollection(fetch = FetchType.EAGER) protected Set roles; + @Column(name = "calories_per_day", columnDefinition = "default 2000") + @Digits(fraction = 0, integer = 4) protected int caloriesPerDay = UserMealsUtil.DEFAULT_CALORIES_PER_DAY; public User() { } - public User(User u) { - this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRoles()); - } - public User(Integer id, String name, String email, String password, Role role, Role... roles) { this(id, name, email, password, UserMealsUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); } diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index 76eadba22975..8ebcfc703529 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; import java.time.LocalDateTime; public class UserMeal extends BaseEntity { @@ -8,7 +10,13 @@ public class UserMeal extends BaseEntity { private String description; - private int calories; + protected int calories; + + @ManyToOne(fetch = FetchType.LAZY) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + private User user; public UserMeal() { } @@ -48,6 +56,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 "UserMeal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java new file mode 100644 index 000000000000..647a24eef180 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.repository.UserMealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * User: gkisline + * Date: 26.08.2014 + */ + +@Repository +public class JpaUserMealRepositoryImpl implements UserMealRepository { + + @Override + public UserMeal save(UserMeal userMeal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public UserMeal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java new file mode 100644 index 000000000000..2b4057f3a4b4 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -0,0 +1,70 @@ +package ru.javawebinar.topjava.repository.jpa; + +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; + +/** + * User: gkislin + * Date: 29.08.2014 + */ +@Repository +@Transactional(readOnly = true) +public class JpaUserRepositoryImpl 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) { + return em.createNamedQuery(User.BY_EMAIL, User.class).setParameter(1, email).getSingleResult(); + } + + @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 96fc29c601f4..d912f0415583 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -7,3 +7,6 @@ database.username=user database.password=password database.init=true +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index a2c9da98e9f8..40fe536e5c4d 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,12 +1,17 @@ + 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> - + @@ -22,11 +27,41 @@ - - - + + + + + + + + + + - - + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 8888d536928e..12b46cd5632a 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -35,7 +35,7 @@ public TestUser(Integer id, String name, String email, String password, int calo } public User asUser() { - return new User(this); + return new User(); } @Override From 2e4a8175b48e1fbdf79f94895de198746cd938e2 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 5 Apr 2016 17:23:13 +0500 Subject: [PATCH 049/166] HW4 patch 4-6 and 4-7 fix bug - duplicate field --- src/main/java/ru/javawebinar/topjava/model/UserMeal.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index 8ebcfc703529..060084aab786 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -15,9 +15,6 @@ public class UserMeal extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private User user; - @ManyToOne(fetch = FetchType.LAZY) - private User user; - public UserMeal() { } From f41714e92459288bc6801ce8ee3c17ebe16dacfb Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 5 Apr 2016 22:56:18 +0500 Subject: [PATCH 050/166] HW4 patch 4-6 and 4-7 fix method asUser - in UserServiceTest --- src/test/java/ru/javawebinar/topjava/UserTestData.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 12b46cd5632a..3ad0d8a4a2b9 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -35,7 +35,8 @@ public TestUser(Integer id, String name, String email, String password, int calo } public User asUser() { - return new User(); + return new User(this.getId(), this.getName(), this.getEmail(), this.getPassword(), this.getCaloriesPerDay(), + this.isEnabled(), this.getRoles()); } @Override From 24318e1cea1dcd1de4664f2b24e173b9484dff9e Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 5 Apr 2016 23:00:22 +0500 Subject: [PATCH 051/166] HW4 patch 4-8 --- pom.xml | 6 ++++ src/main/resources/db/hsqldb.properties | 13 ++++++++ src/main/resources/db/initDB_hsql.sql | 37 +++++++++++++++++++++++ src/main/resources/db/postgres.properties | 2 ++ src/main/resources/spring/spring-db.xml | 7 +++-- 5 files changed, 63 insertions(+), 2 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 abef64c3358d..a64cf1367efb 100644 --- a/pom.xml +++ b/pom.xml @@ -130,6 +130,12 @@ ${spring.version} test + + org.hsqldb + hsqldb + 2.3.2 + + org.springframework spring-orm diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties new file mode 100644 index 000000000000..560579e13f71 --- /dev/null +++ b/src/main/resources/db/hsqldb.properties @@ -0,0 +1,13 @@ +#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=initDB_hsql.sql +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true diff --git a/src/main/resources/db/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql new file mode 100644 index 000000000000..a2c615d7b6e7 --- /dev/null +++ b/src/main/resources/db/initDB_hsql.sql @@ -0,0 +1,37 @@ +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), + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, + registered TIMESTAMP DEFAULT now(), + enabled BOOLEAN DEFAULT TRUE, + 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, + description VARCHAR(255), + calories INT, + 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 d912f0415583..04c648364f89 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=initDB.sql jpa.showSql=true hibernate.format_sql=true hibernate.use_sql_comments=true diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 40fe536e5c4d..faf5aaa0afa9 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -9,19 +9,22 @@ 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"> + + + - + - + From f225f79ccb1a665366000d16a09c144be3f13653 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 6 Apr 2016 01:34:41 +0500 Subject: [PATCH 052/166] HW4 patch 5-0 --- .../ru/javawebinar/topjava/model/UserMeal.java | 12 ++++++++++-- .../repository/jpa/JpaUserMealRepositoryImpl.java | 14 +++++++++++++- .../java/ru/javawebinar/topjava/util/TimeUtil.java | 2 +- src/main/resources/db/initDB.sql | 6 +++--- src/main/resources/db/initDB_hsql.sql | 6 +++--- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index 060084aab786..9fbc79ffdb47 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -1,11 +1,18 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import javax.persistence.*; import java.time.LocalDateTime; +@NamedQueries({ + @NamedQuery(name = UserMeal.DELETE, query = "DELETE FROM UserMeal um WHERE um.id=:id AND um.user.id=:user_id"), + @NamedQuery(name = UserMeal.ALL_SORTED, query = "SELECT um FROM UserMeal um ORDER BY um.dateTime DESC "), +}) +@Entity +@Table(name = "meals") public class UserMeal extends BaseEntity { + public static final String DELETE = "UserMeal.delete"; + public static final String ALL_SORTED = "UserMeal.getAll"; private LocalDateTime dateTime; private String description; @@ -13,6 +20,7 @@ public class UserMeal extends BaseEntity { protected int calories; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) private User user; public UserMeal() { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java index 647a24eef180..7ece7ccf4e25 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java @@ -1,9 +1,13 @@ package ru.javawebinar.topjava.repository.jpa; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @@ -13,16 +17,24 @@ */ @Repository +@Transactional (readOnly = true) public class JpaUserMealRepositoryImpl implements UserMealRepository { + @PersistenceContext + private EntityManager em; + @Override public UserMeal save(UserMeal userMeal, int userId) { return null; } @Override + @Transactional public boolean delete(int id, int userId) { - return false; + return em.createNamedQuery(UserMeal.DELETE). + setParameter("id", id). + setParameter("user_id", userId). + executeUpdate() != 0; } @Override diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 1a4ac02be7e7..50c06a798e51 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -9,7 +9,7 @@ public class TimeUtil { public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - public static final LocalDate MIN_DATE = LocalDate.of(0, 1, 1); + public static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); private TimeUtil() { diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index 62eca097467b..aede3736c9d0 100644 --- a/src/main/resources/db/initDB.sql +++ b/src/main/resources/db/initDB.sql @@ -28,9 +28,9 @@ CREATE TABLE user_roles CREATE TABLE meals ( id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), user_id INTEGER NOT NULL, - date_time TIMESTAMP, - description TEXT, - calories INT, + date_time TIMESTAMP NOT NULL, + description TEXT NOT NULL, + calories INT NOT NULL, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ); CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals(user_id, date_time) \ No newline at end of file diff --git a/src/main/resources/db/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql index a2c615d7b6e7..56f978ac3b03 100644 --- a/src/main/resources/db/initDB_hsql.sql +++ b/src/main/resources/db/initDB_hsql.sql @@ -28,9 +28,9 @@ CREATE TABLE user_roles CREATE TABLE meals ( id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY, - date_time TIMESTAMP, - description VARCHAR(255), - calories INT, + 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 ); From 2099ad7e24a9c605690f31e3b8fbc17ea27f45c7 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 6 Apr 2016 01:45:59 +0500 Subject: [PATCH 053/166] HW4 patch 5-1 --- pom.xml | 5 +++ .../javawebinar/topjava/model/UserMeal.java | 32 +++++++++++++++---- .../jpa/JpaUserMealRepositoryImpl.java | 31 ++++++++++++++---- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index a64cf1367efb..89f1c360f3a4 100644 --- a/pom.xml +++ b/pom.xml @@ -146,6 +146,11 @@ hibernate-entitymanager ${hibernate.version} + + org.hibernate + hibernate-java8 + ${hibernate.version} + javax.transaction jta diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index 9fbc79ffdb47..3ad362ba30a3 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -1,22 +1,42 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.NotEmpty; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; import javax.persistence.*; import java.time.LocalDateTime; + @NamedQueries({ - @NamedQuery(name = UserMeal.DELETE, query = "DELETE FROM UserMeal um WHERE um.id=:id AND um.user.id=:user_id"), - @NamedQuery(name = UserMeal.ALL_SORTED, query = "SELECT um FROM UserMeal um ORDER BY um.dateTime DESC "), + @NamedQuery(name = UserMeal.GET, query = "SELECT m FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId"), + @NamedQuery(name = UserMeal.ALL_SORTED, query = "SELECT m FROM UserMeal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC"), + @NamedQuery(name = UserMeal.DELETE, query = "DELETE FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId"), + @NamedQuery(name = UserMeal.GET_BETWEEN, query = "SELECT m FROM UserMeal m "+ + "WHERE m.user.id=:userId AND m.dateTime BETWEEN :startDate AND :endDate ORDER BY m.dateTime DESC"), + +// @NamedQuery(name = UserMeal.UPDATE, query = "UPDATE UserMeal m SET m.dateTime = :datetime, m.calories= :calories," + +// "m.description=:desc where m.id=:id and m.user.id=:userId") }) @Entity -@Table(name = "meals") +@Table(name = "meals", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "date_time"}, name = "meals_unique_user_datetime_idx")}) public class UserMeal extends BaseEntity { - - public static final String DELETE = "UserMeal.delete"; + public static final String GET = "UserMeal.get"; public static final String ALL_SORTED = "UserMeal.getAll"; + public static final String DELETE = "UserMeal.delete"; + public static final String GET_BETWEEN = "UserMeal.getBetween"; + + @Column(name = "date_time", nullable = false) + @NotNull private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotEmpty private String description; + @Column(name = "calories", nullable = false) + @Range(min = 10, max = 5000) protected int calories; @ManyToOne(fetch = FetchType.LAZY) @@ -78,4 +98,4 @@ public String toString() { ", calories=" + calories + '}'; } -} +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java index 7ece7ccf4e25..a24866cbca84 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java @@ -1,7 +1,9 @@ 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.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; @@ -17,15 +19,23 @@ */ @Repository -@Transactional (readOnly = true) +@Transactional(readOnly = true) public class JpaUserMealRepositoryImpl implements UserMealRepository { @PersistenceContext private EntityManager em; @Override + @Transactional public UserMeal save(UserMeal userMeal, int userId) { - return null; + User ref = em.getReference(User.class, userId); + userMeal.setUser(ref); + if (userMeal.isNew()) { + em.persist(userMeal); + return userMeal; + } else { + return get(userMeal.getId(), userId) == null ? null : em.merge(userMeal); + } } @Override @@ -33,22 +43,31 @@ public UserMeal save(UserMeal userMeal, int userId) { public boolean delete(int id, int userId) { return em.createNamedQuery(UserMeal.DELETE). setParameter("id", id). - setParameter("user_id", userId). + setParameter("userId", userId). executeUpdate() != 0; } @Override public UserMeal get(int id, int userId) { - return null; + List userMeals = em.createNamedQuery(UserMeal.GET, UserMeal.class) + .setParameter("id", id) + .setParameter("userId", userId) + .getResultList(); + return DataAccessUtils.singleResult(userMeals); } @Override public List getAll(int userId) { - return null; + return em.createNamedQuery(UserMeal.ALL_SORTED, UserMeal.class) + .setParameter("userId", userId) + .getResultList(); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return em.createNamedQuery(UserMeal.GET_BETWEEN, UserMeal.class) + .setParameter("userId", userId) + .setParameter("startDate", startDate) + .setParameter("endDate", endDate).getResultList(); } } \ No newline at end of file From 1ac2d6bf83720beb7bd6ef0620324710f543fc46 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 6 Apr 2016 21:11:31 +0500 Subject: [PATCH 054/166] HW4 patch 5-1 --- pom.xml | 41 +++++++++++++------ .../java/ru/javawebinar/topjava/Profiles.java | 13 ++++++ src/main/resources/spring/spring-db.xml | 34 ++++++++++----- .../topjava/service/UserMealServiceTest.java | 38 +++++++++++++++-- .../topjava/service/UserServiceTest.java | 4 ++ 5 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/Profiles.java diff --git a/pom.xml b/pom.xml index 89f1c360f3a4..8694d4d381d8 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ UTF-8 4.2.5.RELEASE + 7.0.57 1.1.6 @@ -98,13 +99,6 @@ - - - org.postgresql - postgresql - ${postgresql.version} - - junit @@ -130,11 +124,6 @@ ${spring.version} test - - org.hsqldb - hsqldb - 2.3.2 - org.springframework @@ -170,6 +159,34 @@ + + hsqldb + + + org.hsqldb + hsqldb + 2.3.2 + + + + + postgres + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + + + + 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..23e905ed9c20 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -0,0 +1,13 @@ +package ru.javawebinar.topjava; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class Profiles { + public static final String + POSTGRES = "postgres", + HSQLDB = "hsqldb", + JDBC = "jdbc", + JPA = "jpa"; +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index faf5aaa0afa9..5e29e21c16d8 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -11,7 +11,7 @@ - + @@ -21,15 +21,6 @@ - - - - - - - - @@ -67,4 +58,27 @@ --> + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java index 9069c6759569..fa806c8dd76c 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java @@ -1,19 +1,28 @@ package ru.javawebinar.topjava.service; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +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.SpringJUnit4ClassRunner; import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.time.LocalDate; import java.time.Month; import java.util.Arrays; +import java.util.concurrent.TimeUnit; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; @@ -25,7 +34,26 @@ }) @RunWith(SpringJUnit4ClassRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(Profiles.POSTGRES) public class UserMealServiceTest { + private static final Logger LOG = LoggerFactory.getLogger(UserMealServiceTest.class);; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public Stopwatch stopwatch = new Stopwatch() { + private void logInfo(Description description, long nanos) { + LOG.info(String.format("+++ Test %s spent %d ms", + description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos))); + } + + @Override + protected void finished(long nanos, Description description) { + logInfo(description, nanos); + } + }; @Autowired protected UserMealService service; @@ -36,8 +64,9 @@ public void testDelete() throws Exception { MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID)); } - @Test(expected = NotFoundException.class) + @Test public void testDeleteNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @@ -54,8 +83,9 @@ public void testGet() throws Exception { MATCHER.assertEquals(ADMIN_MEAL, actual); } - @Test(expected = NotFoundException.class) + @Test public void testGetNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @@ -66,9 +96,11 @@ public void testUpdate() throws Exception { MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); } - @Test(expected = NotFoundException.class) + @Test public void testNotFoundUpdate() throws Exception { UserMeal item = service.get(MEAL1_ID, USER_ID); + thrown.expect(NotFoundException.class); + thrown.expectMessage("Not found entity with id=" + MEAL1_ID); service.update(item, ADMIN_ID); } diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index e3e37e8dce48..e69dd9d7aeb4 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -4,10 +4,12 @@ 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; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.UserTestData.*; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; @@ -25,6 +27,8 @@ }) @RunWith(SpringJUnit4ClassRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +//@ActiveProfiles(Profiles.POSTGRES) +@ActiveProfiles("hsqldb") public class UserServiceTest { @Autowired From d33d2dec57dde21b69965fe2a7d4444da08f1792 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 6 Apr 2016 21:14:09 +0500 Subject: [PATCH 055/166] HW4 patch 5-3 --- src/main/resources/spring/spring-db.xml | 23 +++++++++++++++++++ .../topjava/service/UserMealServiceTest.java | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 5e29e21c16d8..423253e97255 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -71,6 +71,29 @@ p:password="${database.password}"/> + + + + + + + + + + + + + diff --git a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java index fa806c8dd76c..f331e9bd66af 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java @@ -34,7 +34,8 @@ }) @RunWith(SpringJUnit4ClassRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.POSTGRES) +//@ActiveProfiles(Profiles.POSTGRES) +@ActiveProfiles("hsqldb") public class UserMealServiceTest { private static final Logger LOG = LoggerFactory.getLogger(UserMealServiceTest.class);; From a0c7e8cf0a002acf2a359fe6fc576b03a00a3b32 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 6 Apr 2016 21:48:48 +0500 Subject: [PATCH 056/166] HW4 patch 5-3 set and then- test passed --- src/main/resources/spring/spring-db.xml | 36 +++++++++---------- .../topjava/service/UserMealServiceTest.java | 4 +-- .../topjava/service/UserServiceTest.java | 4 +-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 423253e97255..20797dbdf845 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -71,16 +71,16 @@ p:password="${database.password}"/> - - + + - - + + + + + + + @@ -94,14 +94,14 @@ p:password="${database.password}"/> - - + + - - - - - - - + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java index f331e9bd66af..9a0c0712f409 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java @@ -34,8 +34,8 @@ }) @RunWith(SpringJUnit4ClassRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -//@ActiveProfiles(Profiles.POSTGRES) -@ActiveProfiles("hsqldb") +@ActiveProfiles(Profiles.POSTGRES) +//@ActiveProfiles("hsqldb") public class UserMealServiceTest { private static final Logger LOG = LoggerFactory.getLogger(UserMealServiceTest.class);; diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index e69dd9d7aeb4..a28430234d96 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -27,8 +27,8 @@ }) @RunWith(SpringJUnit4ClassRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -//@ActiveProfiles(Profiles.POSTGRES) -@ActiveProfiles("hsqldb") +@ActiveProfiles(Profiles.POSTGRES) +//@ActiveProfiles("hsqldb") public class UserServiceTest { @Autowired From a8549ee78d91c5e8d16b3e0120e9338fa1c18c88 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 7 Apr 2016 23:55:22 +0500 Subject: [PATCH 057/166] HW4 patch 5-4 --- pom.xml | 16 +++++-- .../DataJpaUserMealRepositoryImpl.java | 40 ++++++++++++++++ .../datajpa/DataJpaUserRepositoryImpl.java | 47 +++++++++++++++++++ .../datajpa/ProxyUserRepository.java | 37 +++++++++++++++ src/main/resources/spring/spring-db.xml | 16 ++++++- 5 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java diff --git a/pom.xml b/pom.xml index 8694d4d381d8..d213770a19c4 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ UTF-8 UTF-8 + 1.9.4.RELEASE 4.2.5.RELEASE 7.0.57 @@ -126,9 +127,9 @@ - org.springframework - spring-orm - ${spring.version} + org.springframework.data + spring-data-jpa + ${spring-data-jpa.version} org.hibernate @@ -190,5 +191,14 @@ + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java new file mode 100644 index 000000000000..3069deb03861 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java @@ -0,0 +1,40 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.repository.UserMealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * GKislin + * 27.03.2015. + */ +@Repository +public class DataJpaUserMealRepositoryImpl implements UserMealRepository{ + @Override + public UserMeal save(UserMeal userMeal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public UserMeal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java new file mode 100644 index 000000000000..139946a33baa --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.beans.factory.annotation.Autowired; +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; + +/** + * GKislin + * 27.03.2015. + */ + +@Repository +public class DataJpaUserRepositoryImpl implements UserRepository { + private static final Sort SORT_NAME_EMAIL = new Sort("name", "email"); + + @Autowired + private ProxyUserRepository proxy; + + @Override + public User save(User user) { + return proxy.save(user); + } + + @Override + public boolean delete(int id) { + return proxy.delete(id) != 0; + } + + @Override + public User get(int id) { + return proxy.findOne(id); + } + + @Override + public User getByEmail(String email) { + return proxy.getByEmail(email); + } + + @Override + public List getAll() { + return proxy.findAll(SORT_NAME_EMAIL); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java new file mode 100644 index 000000000000..092d847f2bb0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java @@ -0,0 +1,37 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.domain.Sort; +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; + +import java.util.List; + +/** + * GKislin + * 27.03.2015. + */ +@Transactional(readOnly = true) +public interface ProxyUserRepository extends JpaRepository { + + @Transactional + @Modifying +// @Query(name = User.DELETE) + @Query("DELETE FROM User u WHERE u.id=:id") + int delete(@Param("id") int id); + + @Override + @Transactional + User save(User user); + + @Override + User findOne(Integer id); + + @Override + List findAll(Sort sort); + + User getByEmail(String email); +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 20797dbdf845..63303e2783a4 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -4,17 +4,22 @@ 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"> - + + + + @@ -104,4 +109,11 @@ p:username="${database.username}" p:password="${database.password}"/> + + + + + + + \ No newline at end of file From 4be9f40e13895f7290d9ecf108db7dee327ea5af Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Fri, 8 Apr 2016 00:04:01 +0500 Subject: [PATCH 058/166] HW4 patch 5-4 refactor spring-db.xml - delete duplicate annotations --- src/main/resources/spring/spring-db.xml | 28 ------------------------- 1 file changed, 28 deletions(-) diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 63303e2783a4..45277981c669 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -11,11 +11,6 @@ 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"> - - - - - @@ -76,29 +71,6 @@ p:password="${database.password}"/> - - - - - - - - - - - - - - - - - - From 43169c3d3b73237b084550e931d6273f8b0b3217 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 15:20:27 +0500 Subject: [PATCH 059/166] HW5 add proxyUserMealRepository --- .../repository/datajpa/ProxyUserMealRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java new file mode 100644 index 000000000000..17712b945675 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +@Transactional (readOnly = true) +public interface ProxyUserMealRepository extends JpaRepository { +} From 5158a75d4ba2baf78d2fdb1c059ed28c73801e57 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 15:33:44 +0500 Subject: [PATCH 060/166] HW5 patch 5-5 --- pom.xml | 12 +- .../DataJpaUserMealRepositoryImpl.java | 15 +- .../datajpa/ProxyUserMealRepository.java | 16 +- .../topjava/service/UserService.java | 2 + .../topjava/service/UserServiceImpl.java | 14 + src/main/resources/cache/ehcache.xml | 28 ++ src/main/resources/cache/ehcache.xsd | 419 ++++++++++++++++++ src/main/resources/spring/spring-app.xml | 1 + src/main/resources/spring/spring-db.xml | 26 +- src/main/resources/spring/spring-tools.xml | 24 + .../topjava/service/UserServiceTest.java | 6 + 11 files changed, 544 insertions(+), 19 deletions(-) create mode 100644 src/main/resources/cache/ehcache.xml create mode 100644 src/main/resources/cache/ehcache.xsd create mode 100644 src/main/resources/spring/spring-tools.xml diff --git a/pom.xml b/pom.xml index d213770a19c4..c34dc007d70e 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,9 @@ 5.1.0.Final 5.2.4.Final + + + 2.10.1 @@ -90,7 +93,7 @@ org.springframework - spring-context + spring-context-support ${spring.version} @@ -152,6 +155,13 @@ hibernate-validator ${hibernate-validator.version} + + + net.sf.ehcache + ehcache + ${ehcache.version} + + javax.el javax.el-api diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java index 3069deb03861..8ba54a8b0def 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java @@ -1,9 +1,13 @@ package ru.javawebinar.topjava.repository.datajpa; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @@ -13,9 +17,18 @@ */ @Repository public class DataJpaUserMealRepositoryImpl implements UserMealRepository{ + + @Autowired + ProxyUserMealRepository proxy; + + @PersistenceContext + private EntityManager em; + @Override public UserMeal save(UserMeal userMeal, int userId) { - return null; + User ref = em.getReference(User.class, userId); + userMeal.setUser(ref); + return proxy.save(userMeal); } @Override diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java index 17712b945675..163d1e6d2601 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java @@ -1,8 +1,22 @@ 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.UserMeal; @Transactional (readOnly = true) -public interface ProxyUserMealRepository extends JpaRepository { +public interface ProxyUserMealRepository extends JpaRepository { + + @Override + @Transactional + UserMeal save(UserMeal userMeal); + + @Transactional + @Modifying + @Query("DELETE FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId") + int delete(@Param("id") int id, @Param("userId") int userId); + } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 4a1567e020ad..d2c51fb9e1e3 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -19,4 +19,6 @@ public interface UserService { List getAll(); void update(User user); + + void evictCache(); } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 873fbbb91ed8..06d45bfd853d 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,12 +1,16 @@ package ru.javawebinar.topjava.service; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.ExceptionUtil; import ru.javawebinar.topjava.util.exception.NotFoundException; +import java.util.Collection; +import java.util.Objects; import java.util.List; @Service @@ -15,10 +19,12 @@ public class UserServiceImpl implements UserService { @Autowired private UserRepository repository; + @CacheEvict(value = "users", allEntries = true) public User save(User user) { return repository.save(user); } + @CacheEvict(value = "users", allEntries = true) public void delete(int id) { ExceptionUtil.check(repository.delete(id), id); } @@ -28,14 +34,22 @@ public User get(int id) throws NotFoundException { } public User getByEmail(String email) throws NotFoundException { + Objects.requireNonNull(email, "Email must not be empty"); return ExceptionUtil.check(repository.getByEmail(email), "email=" + email); } + @Cacheable("users") public List getAll() { return repository.getAll(); } + @CacheEvict(value = "users", allEntries = true) public void update(User user) { repository.save(user); } + + @CacheEvict(value = "users", allEntries = true) + @Override + public void evictCache() { + } } diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml new file mode 100644 index 000000000000..8d420010d88d --- /dev/null +++ b/src/main/resources/cache/ehcache.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/src/main/resources/cache/ehcache.xsd b/src/main/resources/cache/ehcache.xsd new file mode 100644 index 000000000000..bfc19ddb1e7d --- /dev/null +++ b/src/main/resources/cache/ehcache.xsd @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index eff98648bc5a..fbf54e789877 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -11,6 +11,7 @@ --> + diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 45277981c669..5cb430f01807 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -11,11 +11,9 @@ 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"> - - @@ -43,25 +41,12 @@ - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring-tools.xml b/src/main/resources/spring/spring-tools.xml new file mode 100644 index 000000000000..784de2d79e20 --- /dev/null +++ b/src/main/resources/spring/spring-tools.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + \ 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 a28430234d96..2bb64c2791fd 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -1,5 +1,6 @@ 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; @@ -34,6 +35,11 @@ public class UserServiceTest { @Autowired protected UserService service; + @Before + public void setUp() throws Exception { + service.evictCache(); + } + @Test public void testSave() throws Exception { TestUser tu = new TestUser(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); From f409e079e7666baf41cdecbaeb9ebea570eacd2e Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 16:47:45 +0500 Subject: [PATCH 061/166] HW5 dataJpaUserMeal --- .../javawebinar/topjava/model/UserMeal.java | 2 +- .../DataJpaUserMealRepositoryImpl.java | 26 +++++++++---------- .../datajpa/ProxyUserMealRepository.java | 23 +++++++++++----- .../jpa/JpaUserMealRepositoryImpl.java | 8 +++--- src/main/resources/logback.xml | 3 +++ src/test/resources/logback-test.xml | 2 +- 6 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java index 3ad362ba30a3..50078833f04a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -1,3 +1,4 @@ +//@SuppressWarnings("JpaQlInspection") package ru.javawebinar.topjava.model; import org.hibernate.validator.constraints.NotEmpty; @@ -5,7 +6,6 @@ import javax.persistence.*; import javax.validation.constraints.NotNull; -import javax.persistence.*; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java index 8ba54a8b0def..ccb78a5a3a80 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java @@ -2,12 +2,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; -import ru.javawebinar.topjava.model.User; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @@ -16,38 +13,41 @@ * 27.03.2015. */ @Repository -public class DataJpaUserMealRepositoryImpl implements UserMealRepository{ +public class DataJpaUserMealRepositoryImpl implements UserMealRepository { @Autowired ProxyUserMealRepository proxy; - @PersistenceContext - private EntityManager em; + @Autowired + ProxyUserRepository userProxy; @Override + @Transactional public UserMeal save(UserMeal userMeal, int userId) { - User ref = em.getReference(User.class, userId); - userMeal.setUser(ref); + if (!userMeal.isNew() && get(userMeal.getId(), userId) == null) { + return null; + } + userMeal.setUser(userProxy.getOne(userId)); return proxy.save(userMeal); } @Override public boolean delete(int id, int userId) { - return false; + return proxy.delete(id, userId) != 0; } @Override public UserMeal get(int id, int userId) { - return null; + return proxy.get(id, userId); } @Override public List getAll(int userId) { - return null; + return proxy.getAll(userId); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return proxy.getBetween(startDate, endDate, userId); } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java index 163d1e6d2601..6aa835840e6c 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java @@ -7,16 +7,27 @@ import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.UserMeal; -@Transactional (readOnly = true) -public interface ProxyUserMealRepository extends JpaRepository { +import java.time.LocalDateTime; +import java.util.List; - @Override - @Transactional - UserMeal save(UserMeal userMeal); +@Transactional(readOnly = true) +public interface ProxyUserMealRepository extends JpaRepository { - @Transactional @Modifying + @Transactional @Query("DELETE FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId") int delete(@Param("id") int id, @Param("userId") int userId); + @Override + UserMeal save(UserMeal item); + + @Query("SELECT m FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId") + UserMeal get(@Param("id") int id, @Param("userId") int userId); + + @Query("SELECT m FROM UserMeal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC") + List getAll(@Param("userId") int userId); + + @SuppressWarnings("JpaQlInspection") + @Query("SELECT m from UserMeal m WHERE m.user.id=:userId AND m.dateTime BETWEEN :startDate AND :endDate ORDER BY m.dateTime DESC") + List getBetween(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate, @Param("userId") int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java index a24866cbca84..e24cef9a05cf 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java @@ -28,13 +28,15 @@ public class JpaUserMealRepositoryImpl implements UserMealRepository { @Override @Transactional public UserMeal save(UserMeal userMeal, int userId) { - User ref = em.getReference(User.class, userId); - userMeal.setUser(ref); + if (!userMeal.isNew() && get(userMeal.getId(), userId) == null) { + return null; + } + userMeal.setUser(em.getReference(User.class, userId)); if (userMeal.isNew()) { em.persist(userMeal); return userMeal; } else { - return get(userMeal.getId(), userId) == null ? null : em.merge(userMeal); + return em.merge(userMeal); } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 7f75cdc87d09..6d8c66b4dc17 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -26,6 +26,9 @@ + + + diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 80c38026d409..661579943554 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -11,7 +11,7 @@ - + From 3e63d7a157206c1150c643ada8919544936b2019 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 17:03:21 +0500 Subject: [PATCH 062/166] HW5profiles --- .../java/ru/javawebinar/topjava/Profiles.java | 3 +- src/main/resources/spring/spring-db.xml | 61 ++++++++++--------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index 23e905ed9c20..a3f5266a97ec 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -9,5 +9,6 @@ public class Profiles { POSTGRES = "postgres", HSQLDB = "hsqldb", JDBC = "jdbc", - JPA = "jpa"; + JPA = "jpa", + DATAJPA = "datajpa"; } diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 5cb430f01807..702570616e0b 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -19,38 +19,13 @@ - - - - - - - - - - - - - - - - - - - - - + @@ -58,9 +33,10 @@ + @@ -71,15 +47,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 4ce0c0299f517df6f6f1a4de3a5199553013e31e Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 17:21:05 +0500 Subject: [PATCH 063/166] HW5 tests --- 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 702570616e0b..928e452715a7 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -79,6 +79,7 @@ + From 94bf7f165c0ee811a7449c2c7a9ec291a82a48c5 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 17:21:31 +0500 Subject: [PATCH 064/166] HW5 tests --- .../jdbc/Java8JdbcUserMealRepositoryImpl.java | 31 +++++++ .../jdbc/JdbcUserMealRepositoryImpl.java | 90 ------------------- .../TimestampJdbcUserMealRepositoryImpl.java | 34 +++++++ .../topjava/service/AbstractServiceTest.java | 51 +++++++++++ ....java => AbstractUserMealServiceTest.java} | 19 +--- ...Test.java => AbstractUserServiceTest.java} | 20 +---- .../datajpa/DataJpaUserMealServiceTest.java | 10 +++ .../datajpa/DataJpaUserServiceTest.java | 10 +++ .../service/jdbc/JdbcUserMealServiceTest.java | 10 +++ .../service/jdbc/JdbcUserServiceTest.java | 10 +++ .../service/jpa/JpaUserMealServiceTest.java | 10 +++ .../service/jpa/JpaUserServiceTest.java | 10 +++ 12 files changed, 180 insertions(+), 125 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/Java8JdbcUserMealRepositoryImpl.java delete mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/TimestampJdbcUserMealRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java rename src/test/java/ru/javawebinar/topjava/service/{UserMealServiceTest.java => AbstractUserMealServiceTest.java} (80%) rename src/test/java/ru/javawebinar/topjava/service/{UserServiceTest.java => AbstractUserServiceTest.java} (78%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.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/JdbcUserMealServiceTest.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/JpaUserMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/Java8JdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/Java8JdbcUserMealRepositoryImpl.java new file mode 100644 index 000000000000..7bbb7ae02d08 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/Java8JdbcUserMealRepositoryImpl.java @@ -0,0 +1,31 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.model.UserMeal; + +import javax.sql.DataSource; +import java.time.LocalDateTime; + +/** + * User: gkislin + * Date: 26.08.2014 + */ + +@Repository +@Profile(Profiles.POSTGRES) +public class Java8JdbcUserMealRepositoryImpl extends AbstractJdbcUserMealRepositoryImpl { + + @Autowired + public Java8JdbcUserMealRepositoryImpl(DataSource dataSource) { + super(BeanPropertyRowMapper.newInstance(UserMeal.class), dataSource); + } + + @Override + protected LocalDateTime toDbValue(LocalDateTime ldt) { + return ldt; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java deleted file mode 100644 index de42c6ac75b1..000000000000 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -package ru.javawebinar.topjava.repository.jdbc; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.support.DataAccessUtils; -import org.springframework.jdbc.core.BeanPropertyRowMapper; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.jdbc.core.simple.SimpleJdbcInsert; -import org.springframework.stereotype.Repository; -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.repository.UserMealRepository; - -import javax.sql.DataSource; -import java.time.LocalDateTime; -import java.util.List; - -@Repository -public class JdbcUserMealRepositoryImpl implements UserMealRepository { - - private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(UserMeal.class); - -/* - private static final RowMapper ROW_MAPPER = - (rs, rowNum) -> - new UserMeal(rs.getInt("id"), rs.getTimestamp("date_time").toLocalDateTime(), - rs.getString("description"), rs.getInt("calories")); -*/ - - @Autowired - private JdbcTemplate jdbcTemplate; - - @Autowired - private NamedParameterJdbcTemplate namedParameterJdbcTemplate; - - private SimpleJdbcInsert insertUserMeal; - - @Autowired - public JdbcUserMealRepositoryImpl(DataSource dataSource) { - this.insertUserMeal = new SimpleJdbcInsert(dataSource) - .withTableName("meals") - .usingGeneratedKeyColumns("id"); - } - - @Override - public UserMeal save(UserMeal userMeal, int userId) { - MapSqlParameterSource map = new MapSqlParameterSource() - .addValue("id", userMeal.getId()) - .addValue("description", userMeal.getDescription()) - .addValue("calories", userMeal.getCalories()) - .addValue("date_time", userMeal.getDateTime()) - .addValue("user_id", userId); - - if (userMeal.isNew()) { - Number newId = insertUserMeal.executeAndReturnKey(map); - userMeal.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 userMeal; - } - - @Override - public boolean delete(int id, int userId) { - return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; - } - - @Override - public UserMeal get(int id, int userId) { - List userMeals = jdbcTemplate.query( - "SELECT * FROM meals WHERE id = ? AND user_id = ?", ROW_MAPPER, id, userId); - return DataAccessUtils.singleResult(userMeals); - } - public List getAll(int userId) { - return jdbcTemplate.query( - "SELECT * FROM meals WHERE user_id=? ORDER BY date_time DESC", ROW_MAPPER, userId); - } - - @Override - public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return jdbcTemplate.query( - "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC", - ROW_MAPPER, userId, startDate, endDate); - } -} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/TimestampJdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/TimestampJdbcUserMealRepositoryImpl.java new file mode 100644 index 000000000000..559bd51d5169 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/TimestampJdbcUserMealRepositoryImpl.java @@ -0,0 +1,34 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.model.UserMeal; + +import javax.sql.DataSource; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * User: gkislin + * Date: 26.08.2014 + */ + +@Repository +@Profile(Profiles.HSQLDB) +public class TimestampJdbcUserMealRepositoryImpl extends AbstractJdbcUserMealRepositoryImpl { + + @Autowired + public TimestampJdbcUserMealRepositoryImpl(DataSource dataSource) { + super((rs, rowNum) -> + new UserMeal(rs.getInt("id"), rs.getTimestamp("date_time").toLocalDateTime(), + rs.getString("description"), rs.getInt("calories")), dataSource + ); + } + + @Override + protected Timestamp toDbValue(LocalDateTime ldt) { + return Timestamp.valueOf(ldt); + } +} \ No newline at end of file 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..d23a4f9b54ff --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -0,0 +1,51 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.SpringJUnit4ClassRunner; + +import java.util.concurrent.TimeUnit; + +import static ru.javawebinar.topjava.Profiles.HSQLDB; +import static ru.javawebinar.topjava.Profiles.POSTGRES; + +/** + * User: gkislin + */ +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +@ActiveProfiles(POSTGRES) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +abstract public class AbstractServiceTest { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public Stopwatch stopwatch = new Stopwatch() { + private void logInfo(Description description, long nanos) { + log.info(String.format("+++ Test %s spent %d microseconds", + description.getMethodName(), TimeUnit.NANOSECONDS.toMicros(nanos))); + } + + @Override + protected void finished(long nanos, Description description) { + logInfo(description, nanos); + } + }; +} diff --git a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java similarity index 80% rename from src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java index 9a0c0712f409..80fe9c544dd6 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java @@ -5,17 +5,10 @@ import org.junit.rules.ExpectedException; import org.junit.rules.Stopwatch; import org.junit.runner.Description; -import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -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.SpringJUnit4ClassRunner; import ru.javawebinar.topjava.MealTestData; -import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -28,16 +21,8 @@ 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(SpringJUnit4ClassRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.POSTGRES) -//@ActiveProfiles("hsqldb") -public class UserMealServiceTest { - private static final Logger LOG = LoggerFactory.getLogger(UserMealServiceTest.class);; +abstract public class AbstractUserMealServiceTest extends AbstractServiceTest { + private static final Logger LOG = LoggerFactory.getLogger(AbstractUserMealServiceTest.class); @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java similarity index 78% rename from src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 2bb64c2791fd..9f7aeb97628a 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -2,15 +2,8 @@ import org.junit.Before; 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.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.SpringJUnit4ClassRunner; -import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.UserTestData.*; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; @@ -22,16 +15,7 @@ import static ru.javawebinar.topjava.UserTestData.*; -@ContextConfiguration({ - "classpath:spring/spring-app.xml", - "classpath:spring/spring-db.xml" -}) -@RunWith(SpringJUnit4ClassRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.POSTGRES) -//@ActiveProfiles("hsqldb") -public class UserServiceTest { - +abstract public class AbstractUserServiceTest extends AbstractServiceTest { @Autowired protected UserService service; @@ -39,7 +23,7 @@ public class UserServiceTest { public void setUp() throws Exception { service.evictCache(); } - + @Test public void testSave() throws Exception { TestUser tu = new TestUser(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java new file mode 100644 index 000000000000..b2252cc6cff4 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; + +@ActiveProfiles(DATAJPA) +public class DataJpaUserMealServiceTest extends AbstractUserMealServiceTest { +} \ No newline at end of file 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..a0757e739339 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; + +@ActiveProfiles(DATAJPA) +public class DataJpaUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserMealServiceTest.java new file mode 100644 index 000000000000..d310717c67a5 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jdbc; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JDBC; + +@ActiveProfiles(JDBC) +public class JdbcUserMealServiceTest extends AbstractUserMealServiceTest { +} \ 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/JpaUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserMealServiceTest.java new file mode 100644 index 000000000000..778235ed8ef0 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JPA; + +@ActiveProfiles(JPA) +public class JpaUserMealServiceTest extends AbstractUserMealServiceTest { +} \ 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 6c77aee57174324bdb8b47a3f73f7a38fafa93a4 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 17:31:45 +0500 Subject: [PATCH 065/166] HW5 tests remove duplicate field log --- .../topjava/service/AbstractUserMealServiceTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java index 80fe9c544dd6..bc744b9b46d9 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java @@ -5,8 +5,6 @@ import org.junit.rules.ExpectedException; import org.junit.rules.Stopwatch; import org.junit.runner.Description; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.model.UserMeal; @@ -22,7 +20,6 @@ import static ru.javawebinar.topjava.UserTestData.USER_ID; abstract public class AbstractUserMealServiceTest extends AbstractServiceTest { - private static final Logger LOG = LoggerFactory.getLogger(AbstractUserMealServiceTest.class); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -31,7 +28,7 @@ abstract public class AbstractUserMealServiceTest extends AbstractServiceTest { // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev public Stopwatch stopwatch = new Stopwatch() { private void logInfo(Description description, long nanos) { - LOG.info(String.format("+++ Test %s spent %d ms", + log.info(String.format("+++ Test %s spent %d ms", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos))); } From c3e5929e23fd68deb76b81288f08e2841a8e8791 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 17:38:50 +0500 Subject: [PATCH 066/166] HW5 patch 6-04 --- src/main/java/ru/javawebinar/topjava/Profiles.java | 3 +++ .../java/ru/javawebinar/topjava/web/MealServlet.java | 10 +++++++--- src/test/java/ru/javawebinar/topjava/SpringMain.java | 9 ++++++--- .../topjava/service/AbstractServiceTest.java | 5 ++--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index a3f5266a97ec..4ce31d6f690a 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -11,4 +11,7 @@ public class Profiles { JDBC = "jdbc", JPA = "jpa", DATAJPA = "datajpa"; + + public static final String ACTIVE_DB = POSTGRES; + public static final String DB_IMPLEMENTATION = DATAJPA; } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index a63b7103927d..e24733716932 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,8 +2,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.web.meal.UserMealRestController; @@ -22,13 +22,17 @@ public class MealServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - private ConfigurableApplicationContext springContext; + private ClassPathXmlApplicationContext springContext; private UserMealRestController mealController; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-db.xml"); + springContext = new ClassPathXmlApplicationContext(new String[]{"spring/spring-app.xml", "spring/spring-db.xml"}, false); +// springContext = new ClassPathXmlApplicationContext(); + springContext.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.DB_IMPLEMENTATION); +// springContext.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); + springContext.refresh(); mealController = springContext.getBean(UserMealRestController.class); } diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 9fe6a004b653..50ff12880461 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -1,7 +1,6 @@ package ru.javawebinar.topjava; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.GenericXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserMealWithExceed; @@ -17,7 +16,11 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/mock.xml")) { + try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) { + appCtx.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.DB_IMPLEMENTATION); + appCtx.load("spring/spring-app.xml", "spring/spring-db.xml"); + appCtx.refresh(); + System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); System.out.println(adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index d23a4f9b54ff..6548d1e8d644 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -15,8 +15,7 @@ import java.util.concurrent.TimeUnit; -import static ru.javawebinar.topjava.Profiles.HSQLDB; -import static ru.javawebinar.topjava.Profiles.POSTGRES; +import static ru.javawebinar.topjava.Profiles.ACTIVE_DB; /** * User: gkislin @@ -26,7 +25,7 @@ "classpath:spring/spring-db.xml" }) @RunWith(SpringJUnit4ClassRunner.class) -@ActiveProfiles(POSTGRES) +@ActiveProfiles(ACTIVE_DB) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) abstract public class AbstractServiceTest { From cd079c3e3760b9c867701e483e718749dcd054ef Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 9 Apr 2016 17:46:26 +0500 Subject: [PATCH 067/166] HW5 patch 6-05 --- .../java/ru/javawebinar/topjava/model/User.java | 9 +++++++++ .../topjava/repository/UserMealRepository.java | 4 ++++ .../topjava/repository/UserRepository.java | 4 ++++ .../datajpa/DataJpaUserMealRepositoryImpl.java | 5 +++++ .../datajpa/DataJpaUserRepositoryImpl.java | 5 +++++ .../datajpa/ProxyUserMealRepository.java | 3 +++ .../repository/datajpa/ProxyUserRepository.java | 3 +++ .../topjava/service/UserMealService.java | 2 ++ .../topjava/service/UserMealServiceImpl.java | 11 +++++++++++ .../topjava/service/UserService.java | 4 +++- .../topjava/service/UserServiceImpl.java | 8 ++++++-- .../datajpa/DataJpaUserMealServiceTest.java | 17 +++++++++++++++++ .../service/datajpa/DataJpaUserServiceTest.java | 17 +++++++++++++++++ 13 files changed, 89 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 771f304e4741..91318b321ba9 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -10,6 +10,7 @@ import javax.validation.constraints.Digits; import java.util.Date; import java.util.EnumSet; +import java.util.List; import java.util.Set; /** @@ -55,6 +56,10 @@ public class User extends NamedEntity { @Digits(fraction = 0, integer = 4) protected int caloriesPerDay = UserMealsUtil.DEFAULT_CALORIES_PER_DAY; + @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "user") + @OrderBy("dateTime DESC") + protected List meals; + public User() { } @@ -115,6 +120,10 @@ public String getPassword() { return password; } + public List getMeals() { + return meals; + } + @Override public String toString() { return "User (" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java index 3b47215e470c..f91533b31efe 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java @@ -20,4 +20,8 @@ public interface UserMealRepository { // ORDERED dateTime Collection getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); + + default UserMeal getWithUser(Integer id, Integer 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 8b31e7414ba2..fccf8c02e7c9 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -17,4 +17,8 @@ public interface UserRepository { User getByEmail(String email); List getAll(); + + default User getWithMeals(int id){ + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java index ccb78a5a3a80..dd0dc7630dce 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java @@ -50,4 +50,9 @@ public List getAll(int userId) { public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { return proxy.getBetween(startDate, endDate, userId); } + + @Override + public UserMeal getWithUser(Integer id, Integer userId) { + return proxy.getWithUser(id, userId); + } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java index 139946a33baa..422478684ec4 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -44,4 +44,9 @@ public User getByEmail(String email) { public List getAll() { return proxy.findAll(SORT_NAME_EMAIL); } + + @Override + public User getWithMeals(int id) { + return proxy.getWithMeals(id); + } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java index 6aa835840e6c..3c857a4c4880 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java @@ -30,4 +30,7 @@ public interface ProxyUserMealRepository extends JpaRepository getBetween(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate, @Param("userId") int userId); + + @Query("SELECT um FROM UserMeal um JOIN FETCH um.user WHERE um.id = ?1 and um.user.id = ?2") + UserMeal getWithUser(Integer id, Integer userId); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java index 092d847f2bb0..b3c497b46c31 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java @@ -34,4 +34,7 @@ public interface ProxyUserRepository extends JpaRepository { List findAll(Sort sort); User getByEmail(String email); + + @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") + User getWithMeals(Integer id); } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java index 76c48ae53875..3647c7365384 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java @@ -24,4 +24,6 @@ default Collection getBetweenDates(LocalDate startDate, LocalDate endD UserMeal update(UserMeal meal, int userId) throws NotFoundException; UserMeal save(UserMeal meal, int userId); + + UserMeal getWithUser(Integer id, Integer userId); } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java index 10f26d5da445..da66b7d23c92 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java @@ -44,4 +44,15 @@ public UserMeal update(UserMeal meal, int userId) { public UserMeal save(UserMeal meal, int userId) { return repository.save(meal, userId); } + +// @Autowired +// private UserRepository userRepository; + + // @Transactional + public UserMeal getWithUser(Integer id, Integer userId) { +// UserMeal meal = get(id, userId); +// meal.setUser(userRepository.get(userId)); +// return meal; + return ExceptionUtil.check(repository.getWithUser(id, userId), id); + } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index d2c51fb9e1e3..286842b698a8 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -19,6 +19,8 @@ public interface UserService { List getAll(); void update(User user); - + void evictCache(); + + User getWithMeals(int id); } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 06d45bfd853d..00488c762e65 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -9,9 +9,8 @@ import ru.javawebinar.topjava.util.exception.ExceptionUtil; import ru.javawebinar.topjava.util.exception.NotFoundException; -import java.util.Collection; -import java.util.Objects; import java.util.List; +import java.util.Objects; @Service public class UserServiceImpl implements UserService { @@ -52,4 +51,9 @@ public void update(User user) { @Override public void evictCache() { } + + @Override + public User getWithMeals(int id) { + return ExceptionUtil.check(repository.getWithMeals(id), id); + } } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java index b2252cc6cff4..6a8ea592572c 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java @@ -1,10 +1,27 @@ package ru.javawebinar.topjava.service.datajpa; +import org.junit.Test; import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.service.AbstractUserMealServiceTest; +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; @ActiveProfiles(DATAJPA) public class DataJpaUserMealServiceTest extends AbstractUserMealServiceTest { + @Test + public void testGetWithUser() throws Exception { + UserMeal adminMeal = service.getWithUser(ADMIN_MEAL_ID, ADMIN_ID); + MATCHER.assertEquals(ADMIN_MEAL, adminMeal); + UserTestData.MATCHER.assertEquals(UserTestData.ADMIN, adminMeal.getUser()); + } + + @Test(expected = NotFoundException.class) + public void testGetWithMealsNotFound() throws Exception { + service.getWithUser(MEAL1_ID, ADMIN_ID); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index a0757e739339..84ba35cf9032 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -1,10 +1,27 @@ package ru.javawebinar.topjava.service.datajpa; +import org.junit.Test; +import org.springframework.dao.DataAccessException; import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.MealTestData; +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.*; @ActiveProfiles(DATAJPA) public class DataJpaUserServiceTest extends AbstractUserServiceTest { + @Test + public void testGetWithMeals() throws Exception { + User user = service.getWithMeals(USER_ID); + MATCHER.assertEquals(USER, user); + MealTestData.MATCHER.assertCollectionEquals(MealTestData.USER_MEALS, user.getMeals()); + } + + @Test(expected = NotFoundException.class) + public void testGetWithMealsNotFound() throws Exception { + service.getWithMeals(1); + } } \ No newline at end of file From 1486a4a4d84fa23395aa7d072bd1dfcd6e2d9571 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 13 Apr 2016 12:11:36 +0500 Subject: [PATCH 068/166] HW5 patch 6-05 add abstractJdbcUserMealrepository --- .../AbstractJdbcUserMealRepositoryImpl.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java new file mode 100644 index 000000000000..0d68d20318ec --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java @@ -0,0 +1,83 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.support.DataAccessUtils; +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 ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.repository.UserMealRepository; + +import javax.sql.DataSource; +import java.time.LocalDateTime; +import java.util.List; + +public abstract class AbstractJdbcUserMealRepositoryImpl implements UserMealRepository { + private final RowMapper rowMapper; + + public AbstractJdbcUserMealRepositoryImpl(RowMapper rowMapper, DataSource dataSource) { + this.rowMapper = rowMapper; + this.insertUserMeal = new SimpleJdbcInsert(dataSource) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + + } + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertUserMeal; + + protected abstract T toDbValue(LocalDateTime ldt); + + @Override + public UserMeal save(UserMeal userMeal, int userId) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", userMeal.getId()) + .addValue("description", userMeal.getDescription()) + .addValue("calories", userMeal.getCalories()) + .addValue("date_time", toDbValue(userMeal.getDateTime())) + .addValue("user_id", userId); + + if (userMeal.isNew()) { + Number newId = insertUserMeal.executeAndReturnKey(map); + userMeal.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 userMeal; + } + + @Override + public boolean delete(int id, int userId) { + return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; + } + + @Override + public UserMeal get(int id, int userId) { + List userMeals = jdbcTemplate.query( + "SELECT * FROM meals WHERE id = ? AND user_id = ?", rowMapper, id, userId); + return DataAccessUtils.singleResult(userMeals); + } + + public List getAll(int userId) { + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? ORDER BY date_time DESC", rowMapper, userId); + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC", + rowMapper, userId, toDbValue(startDate), toDbValue(endDate)); + } +} \ No newline at end of file From aa14b1127d69bdb60dabd8d0ac0e922f97a0dbea Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 13 Apr 2016 23:52:27 +0500 Subject: [PATCH 069/166] HW6 patch 6-06 --- pom.xml | 5 ++++ .../ru/javawebinar/topjava/model/User.java | 4 +++ .../topjava/repository/JpaUtil.java | 25 +++++++++++++++++++ src/main/resources/cache/ehcache.xml | 3 +++ src/main/resources/spring/spring-db.xml | 8 ++++++ .../service/AbstractUserServiceTest.java | 5 ++++ 6 files changed, 50 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java diff --git a/pom.xml b/pom.xml index c34dc007d70e..99440325daf9 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,11 @@ hibernate-validator ${hibernate-validator.version} + + org.hibernate + hibernate-ehcache + ${hibernate.version} + net.sf.ehcache diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 91318b321ba9..f3c965387b7e 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -2,6 +2,8 @@ import ru.javawebinar.topjava.util.UserMealsUtil; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; @@ -22,6 +24,7 @@ @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"), }) +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Entity @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) public class User extends NamedEntity { @@ -50,6 +53,7 @@ public class User extends NamedEntity { @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") @ElementCollection(fetch = FetchType.EAGER) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) protected Set roles; @Column(name = "calories_per_day", columnDefinition = "default 2000") diff --git a/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java b/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java new file mode 100644 index 000000000000..75b3d82ba3cb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.repository; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +public class JpaUtil { + + @PersistenceContext + private EntityManager em; + + public void clear2ndLevelHibernateCache() { + Session s = (Session) em.getDelegate(); + SessionFactory sf = s.getSessionFactory(); +// sf.evict(User.class); +// sf.getCache().evictEntity(User.class, BaseEntity.START_SEQ); +// sf.getCache().evictEntityRegion(User.class); + sf.getCache().evictQueryRegions(); + sf.getCache().evictDefaultQueryRegion(); + sf.getCache().evictCollectionRegions(); + sf.getCache().evictEntityRegions(); + } +} diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml index 8d420010d88d..b0275e589a4a 100644 --- a/src/main/resources/cache/ehcache.xml +++ b/src/main/resources/cache/ehcache.xml @@ -25,4 +25,7 @@ diskExpiryThreadIntervalSeconds="1" memoryStoreEvictionPolicy="LRU"/> + + diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 928e452715a7..351ee8aad0ef 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -64,6 +64,12 @@ + + + + + + @@ -78,6 +84,8 @@ + + diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 9f7aeb97628a..4a0f374f56fa 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -7,6 +7,7 @@ import ru.javawebinar.topjava.UserTestData.*; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.util.Arrays; @@ -19,9 +20,13 @@ abstract public class AbstractUserServiceTest extends AbstractServiceTest { @Autowired protected UserService service; + @Autowired + protected JpaUtil jpaUtil; + @Before public void setUp() throws Exception { service.evictCache(); + jpaUtil.clear2ndLevelHibernateCache(); } @Test From b0a19ca27d8b4d6656505bce9fb68154eb42868f Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 21 Apr 2016 21:59:16 +0500 Subject: [PATCH 070/166] HW6 patch 6-07 --- pom.xml | 18 +++++++++++++++--- .../javawebinar/topjava/web/MealServlet.java | 17 +++-------------- src/main/webapp/WEB-INF/web.xml | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 99440325daf9..2e303b31043b 100644 --- a/pom.xml +++ b/pom.xml @@ -117,11 +117,23 @@ - javax.servlet - servlet-api - 2.5 + org.apache.tomcat + tomcat-servlet-api + ${tomcat.version} provided + + + org.springframework + spring-web + ${spring.version} + + + commons-logging + commons-logging + + + org.springframework spring-test diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index e24733716932..ccbd1f032007 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,8 +2,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import ru.javawebinar.topjava.Profiles; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.web.meal.UserMealRestController; @@ -22,26 +22,15 @@ public class MealServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - private ClassPathXmlApplicationContext springContext; private UserMealRestController mealController; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - springContext = new ClassPathXmlApplicationContext(new String[]{"spring/spring-app.xml", "spring/spring-db.xml"}, false); -// springContext = new ClassPathXmlApplicationContext(); - springContext.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.DB_IMPLEMENTATION); -// springContext.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); - springContext.refresh(); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); mealController = springContext.getBean(UserMealRestController.class); } - @Override - public void destroy() { - springContext.close(); - super.destroy(); - } - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 6306688d0998..e194d26d8fff 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -5,6 +5,22 @@ Topjava + + spring.profiles.default + postgres,datajpa + + + + contextConfigLocation + + classpath:spring/spring-app.xml + classpath:spring/spring-db.xml + + + + + org.springframework.web.context.ContextLoaderListener + userServlet ru.javawebinar.topjava.web.UserServlet From 694eb1f8dddb8ff8bc9bee6da201afcb0ad7472a Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 21 Apr 2016 22:23:40 +0500 Subject: [PATCH 071/166] HW6 patch 6-08 --- .../javawebinar/topjava/web/UserServlet.java | 19 +++++++++- src/main/resources/messages/app.properties | 2 + src/main/resources/messages/app_ru.properties | 2 + src/main/webapp/css/style.css | 8 ++++ src/main/webapp/fragments/bodyHeader.jsp | 5 +++ src/main/webapp/fragments/footer.jsp | 5 +++ src/main/webapp/fragments/headTag.jsp | 10 +++++ src/main/webapp/index.html | 22 ----------- src/main/webapp/index.jsp | 26 +++++++++++++ src/main/webapp/mealList.jsp | 2 +- src/main/webapp/userList.jsp | 38 ++++++++++++++++--- 11 files changed, 109 insertions(+), 30 deletions(-) create mode 100644 src/main/resources/messages/app.properties create mode 100644 src/main/resources/messages/app_ru.properties create mode 100644 src/main/webapp/fragments/bodyHeader.jsp create mode 100644 src/main/webapp/fragments/footer.jsp create mode 100644 src/main/webapp/fragments/headTag.jsp delete mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/index.jsp diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 340bafedc6cc..4ea64cae8aa6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,8 +1,12 @@ package ru.javawebinar.topjava.web; import org.slf4j.Logger; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.web.user.AdminRestController; +import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -14,6 +18,15 @@ public class UserServlet extends HttpServlet { private static final Logger LOG = getLogger(UserServlet.class); + private AdminRestController adminController; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + adminController = springContext.getBean(AdminRestController.class); + } + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int userId = Integer.valueOf(request.getParameter("userId")); LoggedUser.setId(userId); @@ -21,8 +34,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - LOG.debug("redirect to userList"); -// request.getRequestDispatcher("/userList.jsp").forward(request, response); + LOG.debug("getAll"); + request.setAttribute("userList", adminController.getAll()); + request.getRequestDispatcher("/userList.jsp").forward(request, response); +// response.sendRedirect("userList.jsp"); // request.getRequestDispatcher("/userList.jsp").forward(request, response); response.sendRedirect("userList.jsp"); } diff --git a/src/main/resources/messages/app.properties b/src/main/resources/messages/app.properties new file mode 100644 index 000000000000..195b36b13dd3 --- /dev/null +++ b/src/main/resources/messages/app.properties @@ -0,0 +1,2 @@ +app.title=Calories management +app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application diff --git a/src/main/resources/messages/app_ru.properties b/src/main/resources/messages/app_ru.properties new file mode 100644 index 000000000000..461c4eeb279d --- /dev/null +++ b/src/main/resources/messages/app_ru.properties @@ -0,0 +1,2 @@ +app.title=\u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 +app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0443 Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css index 0c9fc667371c..36305de387ee 100644 --- a/src/main/webapp/css/style.css +++ b/src/main/webapp/css/style.css @@ -22,3 +22,11 @@ dd { .exceeded { color: red; } + +header, footer { + background: none repeat scroll 0 0 #A6C9E2; + color: #2E6E9E; + font-size: 20px; + padding: 5px 20px; + margin: 6px 0; +} \ No newline at end of file diff --git a/src/main/webapp/fragments/bodyHeader.jsp b/src/main/webapp/fragments/bodyHeader.jsp new file mode 100644 index 000000000000..ad383f16f619 --- /dev/null +++ b/src/main/webapp/fragments/bodyHeader.jsp @@ -0,0 +1,5 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + +
diff --git a/src/main/webapp/fragments/footer.jsp b/src/main/webapp/fragments/footer.jsp new file mode 100644 index 000000000000..6df7790c26c3 --- /dev/null +++ b/src/main/webapp/fragments/footer.jsp @@ -0,0 +1,5 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + +
+
diff --git a/src/main/webapp/fragments/headTag.jsp b/src/main/webapp/fragments/headTag.jsp new file mode 100644 index 000000000000..0238860698d7 --- /dev/null +++ b/src/main/webapp/fragments/headTag.jsp @@ -0,0 +1,10 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> + + + + + <fmt:message key="app.title"/> + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html deleted file mode 100644 index f3c54386a713..000000000000 --- a/src/main/webapp/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - Top Java. - - -

Проект "Top Java"

-
-
- Login as: - - - - - diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 000000000000..6177319f7252 --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,26 @@ +<%@ 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" %> + + + + + + + +
+
+ Login as: + + + +
+ + + diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/mealList.jsp index 0f198569b67f..c797f5a43aaf 100644 --- a/src/main/webapp/mealList.jsp +++ b/src/main/webapp/mealList.jsp @@ -9,7 +9,7 @@
-

Home

+

Home

Meal list

diff --git a/src/main/webapp/userList.jsp b/src/main/webapp/userList.jsp index eae4726d4400..215e82937d98 100644 --- a/src/main/webapp/userList.jsp +++ b/src/main/webapp/userList.jsp @@ -1,10 +1,38 @@ <%@ 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" %> + + + - - User list - + -

Home

-

User list

+ + +
+

+ + + + + + + + + + + + + + + + + + + +
NameEmailRolesActiveRegistered
${user.email}${user.roles}<%=user.isEnabled()%> +
+
+ From 28571e1ae2a610f847bcb5d95810ded7f2309ded Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 21 Apr 2016 23:37:46 +0500 Subject: [PATCH 072/166] HW6 patch 6-08 remove duplicate code into userservlet --- src/main/java/ru/javawebinar/topjava/web/UserServlet.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 4ea64cae8aa6..4aa6c14ce7dc 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -38,7 +38,5 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t request.setAttribute("userList", adminController.getAll()); request.getRequestDispatcher("/userList.jsp").forward(request, response); // response.sendRedirect("userList.jsp"); -// request.getRequestDispatcher("/userList.jsp").forward(request, response); - response.sendRedirect("userList.jsp"); } } From 1f7e27b19219d5bb5d08158b9f980dc70d635cee Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Fri, 22 Apr 2016 00:20:47 +0500 Subject: [PATCH 073/166] HW6 patch 6-09 --- pom.xml | 26 +++++++++++- src/main/resources/db/hsqldb.properties | 1 - src/main/resources/db/postgres.properties | 1 - src/main/resources/db/tomcat.properties | 7 ++++ src/main/resources/spring/spring-db.xml | 9 ++++- src/main/resources/tomcat/context.xml | 49 +++++++++++++++++++++++ 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/db/tomcat.properties create mode 100644 src/main/resources/tomcat/context.xml diff --git a/pom.xml b/pom.xml index 2e303b31043b..8ef3c93f648b 100644 --- a/pom.xml +++ b/pom.xml @@ -53,10 +53,33 @@ org.apache.maven.plugins maven-surefire-plugin - 2.19.1 + 2.19.1 + -Dfile.encoding=UTF-8 + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + src/main/resources/tomcat/context.xml + + UTF-8 + tomcat,datajpa + + org.apache.coyote.http11.Http11NioProtocol + + + + org.postgresql + postgresql + ${postgresql.version} + + + + @@ -183,6 +206,7 @@ javax.el javax.el-api 2.2.5 + provided diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties index 560579e13f71..a9449359016a 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 diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 04c648364f89..3c98627c2407 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=initDB.sql diff --git a/src/main/resources/db/tomcat.properties b/src/main/resources/db/tomcat.properties new file mode 100644 index 000000000000..09ed9c2548ee --- /dev/null +++ b/src/main/resources/db/tomcat.properties @@ -0,0 +1,7 @@ +database.init=false + +jdbc.initLocation=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 351ee8aad0ef..790d3fcbe0a1 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -5,11 +5,13 @@ 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" + xmlns:jee="http://www.springframework.org/schema/jee" 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/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd + http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd"> @@ -42,6 +44,11 @@ p:password="${database.password}"/> + + + + + diff --git a/src/main/resources/tomcat/context.xml b/src/main/resources/tomcat/context.xml new file mode 100644 index 000000000000..cad687c2d4fd --- /dev/null +++ b/src/main/resources/tomcat/context.xml @@ -0,0 +1,49 @@ + + + + + + + + WEB-INF/web.xml + ${catalina.base}/conf/web.xml + + + + + + + + + From 779b61c6e25250a85a3009969491db0d815b5ca7 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Fri, 22 Apr 2016 00:42:03 +0500 Subject: [PATCH 074/166] HW6 patch 6-10 --- pom.xml | 2 +- .../topjava/web/RootController.java | 39 +++++++++++++++++++ src/main/resources/spring/spring-app.xml | 3 -- src/main/resources/spring/spring-mvc.xml | 18 +++++++++ .../jsp}/fragments/bodyHeader.jsp | 0 .../{ => WEB-INF/jsp}/fragments/footer.jsp | 0 .../{ => WEB-INF/jsp}/fragments/headTag.jsp | 2 +- src/main/webapp/{ => WEB-INF/jsp}/index.jsp | 0 .../webapp/{ => WEB-INF/jsp}/mealEdit.jsp | 0 .../webapp/{ => WEB-INF/jsp}/mealList.jsp | 2 +- .../webapp/{ => WEB-INF/jsp}/userList.jsp | 0 src/main/webapp/WEB-INF/web.xml | 17 ++++++++ src/main/webapp/{ => resources}/css/style.css | 2 +- 13 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/RootController.java create mode 100644 src/main/resources/spring/spring-mvc.xml rename src/main/webapp/{ => WEB-INF/jsp}/fragments/bodyHeader.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/fragments/footer.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/fragments/headTag.jsp (85%) rename src/main/webapp/{ => WEB-INF/jsp}/index.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/mealEdit.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/mealList.jsp (99%) rename src/main/webapp/{ => WEB-INF/jsp}/userList.jsp (100%) rename src/main/webapp/{ => resources}/css/style.css (99%) diff --git a/pom.xml b/pom.xml index 8ef3c93f648b..51ef9c95038c 100644 --- a/pom.xml +++ b/pom.xml @@ -148,7 +148,7 @@ org.springframework - spring-web + spring-webmvc ${spring.version} diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java new file mode 100644 index 000000000000..2134910b0078 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -0,0 +1,39 @@ +package ru.javawebinar.topjava.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.service.UserService; + +import javax.servlet.http.HttpServletRequest; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +@Controller +public class RootController { + @Autowired + private UserService service; + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String root() { + return "index"; + } + + @RequestMapping(value = "/users", method = RequestMethod.GET) + public String userList(Model model) { + model.addAttribute("userList", service.getAll()); + return "userList"; + } + + @RequestMapping(value = "/users", method = RequestMethod.POST) + public String setUser(HttpServletRequest request) { + int userId = Integer.valueOf(request.getParameter("userId")); + LoggedUser.setId(userId); + return "redirect:meals"; + } +} diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index fbf54e789877..c3e7587cfdea 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -16,7 +16,4 @@ - - - \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml new file mode 100644 index 000000000000..7c71d5af5939 --- /dev/null +++ b/src/main/resources/spring/spring-mvc.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp similarity index 100% rename from src/main/webapp/fragments/bodyHeader.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp diff --git a/src/main/webapp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp similarity index 100% rename from src/main/webapp/fragments/footer.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/footer.jsp diff --git a/src/main/webapp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp similarity index 85% rename from src/main/webapp/fragments/headTag.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 0238860698d7..4fb114c10428 100644 --- a/src/main/webapp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -6,5 +6,5 @@ <fmt:message key="app.title"/> - + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp similarity index 100% rename from src/main/webapp/index.jsp rename to src/main/webapp/WEB-INF/jsp/index.jsp diff --git a/src/main/webapp/mealEdit.jsp b/src/main/webapp/WEB-INF/jsp/mealEdit.jsp similarity index 100% rename from src/main/webapp/mealEdit.jsp rename to src/main/webapp/WEB-INF/jsp/mealEdit.jsp diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp similarity index 99% rename from src/main/webapp/mealList.jsp rename to src/main/webapp/WEB-INF/jsp/mealList.jsp index c797f5a43aaf..c6f15c80f3fc 100644 --- a/src/main/webapp/mealList.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp @@ -59,4 +59,4 @@
- \ No newline at end of file + diff --git a/src/main/webapp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp similarity index 100% rename from src/main/webapp/userList.jsp rename to src/main/webapp/WEB-INF/jsp/userList.jsp diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index e194d26d8fff..330fb5bba850 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -18,9 +18,25 @@ + org.springframework.web.context.ContextLoaderListener + + mvc-dispatcher + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + classpath:spring/spring-mvc.xml + + 1 + + + mvc-dispatcher + / + + + diff --git a/src/main/webapp/css/style.css b/src/main/webapp/resources/css/style.css similarity index 99% rename from src/main/webapp/css/style.css rename to src/main/webapp/resources/css/style.css index 36305de387ee..d4807fb38efb 100644 --- a/src/main/webapp/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -29,4 +29,4 @@ header, footer { font-size: 20px; padding: 5px 20px; margin: 6px 0; -} \ No newline at end of file +} From 72cdbce44cd0ad7371f991a6d805184d6aee4536 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 23 Apr 2016 18:46:29 +0500 Subject: [PATCH 075/166] HW6 patch 6-10 replace diskpath in ehcache.xml, because it was error of create tmp files --- src/main/resources/cache/ehcache.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml index b0275e589a4a..b791538f1af7 100644 --- a/src/main/resources/cache/ehcache.xml +++ b/src/main/resources/cache/ehcache.xml @@ -2,7 +2,7 @@ xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false"> - + Date: Sat, 23 Apr 2016 19:10:35 +0500 Subject: [PATCH 076/166] HW6 patch 6-11 --- .../messages/app.properties | 0 config/messages/app_ru.properties | 2 ++ src/main/resources/messages/app_ru.properties | 2 -- src/main/resources/spring/spring-mvc.xml | 18 ++++++++++++++++++ .../WEB-INF/jsp/fragments/bodyHeader.jsp | 1 - .../webapp/WEB-INF/jsp/fragments/footer.jsp | 1 - .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 1 - src/main/webapp/WEB-INF/jsp/index.jsp | 2 -- src/main/webapp/WEB-INF/jsp/userList.jsp | 2 -- 9 files changed, 20 insertions(+), 9 deletions(-) rename {src/main/resources => config}/messages/app.properties (100%) create mode 100644 config/messages/app_ru.properties delete mode 100644 src/main/resources/messages/app_ru.properties diff --git a/src/main/resources/messages/app.properties b/config/messages/app.properties similarity index 100% rename from src/main/resources/messages/app.properties rename to config/messages/app.properties diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties new file mode 100644 index 000000000000..7c7d9e9e781c --- /dev/null +++ b/config/messages/app_ru.properties @@ -0,0 +1,2 @@ +app.title=Подсчет калорий +app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery \ No newline at end of file diff --git a/src/main/resources/messages/app_ru.properties b/src/main/resources/messages/app_ru.properties deleted file mode 100644 index 461c4eeb279d..000000000000 --- a/src/main/resources/messages/app_ru.properties +++ /dev/null @@ -1,2 +0,0 @@ -app.title=\u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 -app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0443 Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 7c71d5af5939..12ac233daa15 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -15,4 +15,22 @@ + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index ad383f16f619..833a84d0b2a4 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,5 +1,4 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -
diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp index 6df7790c26c3..997a1677296c 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,5 +1,4 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -
diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 4fb114c10428..fc2b58f65786 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -1,7 +1,6 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> - diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp index 6177319f7252..74dd0c49a982 100644 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -2,8 +2,6 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index 215e82937d98..2419b79b3167 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -2,8 +2,6 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - From d1cd676d94dd9a0398370a95a16779b9d3b54d7f Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sat, 23 Apr 2016 19:25:57 +0500 Subject: [PATCH 077/166] HW6 patch 6-11 back to default diskstore path="java.io.tmpdir', and give grant to currentuser of create file int directory /usr/local/apache-tomcat-7.0.67/temp --- src/main/resources/cache/ehcache.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml index b791538f1af7..b0275e589a4a 100644 --- a/src/main/resources/cache/ehcache.xml +++ b/src/main/resources/cache/ehcache.xml @@ -2,7 +2,7 @@ xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false"> - + Date: Tue, 24 May 2016 22:23:27 +0500 Subject: [PATCH 078/166] HW7 --- config/messages/app.properties | 9 ++ config/messages/app_ru.properties | 11 ++- .../ru/javawebinar/topjava/LoggedUser.java | 4 +- .../ru/javawebinar/topjava/model/User.java | 9 +- .../AbstractJdbcUserMealRepositoryImpl.java | 21 +++-- .../jdbc/JdbcUserRepositoryImpl.java | 82 +++++++++++++++- .../javawebinar/topjava/web/MealServlet.java | 93 ------------------- .../topjava/web/RootController.java | 8 +- .../javawebinar/topjava/web/UserServlet.java | 42 --------- .../web/meal/AbstractUserMealController.java | 70 ++++++++++++++ .../web/meal/JspUserMealController.java | 84 +++++++++++++++++ .../web/meal/UserMealRestController.java | 58 +----------- src/main/resources/db/populateDB.sql | 3 +- src/main/resources/spring/spring-db.xml | 10 ++ .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 3 + src/main/webapp/WEB-INF/jsp/index.jsp | 8 +- src/main/webapp/WEB-INF/jsp/mealEdit.jsp | 11 +-- src/main/webapp/WEB-INF/jsp/mealList.jsp | 20 ++-- src/main/webapp/WEB-INF/jsp/userList.jsp | 1 + src/main/webapp/WEB-INF/web.xml | 37 ++++---- .../ru/javawebinar/topjava/MealTestData.java | 16 ++-- .../ru/javawebinar/topjava/SpringMain.java | 7 +- .../ru/javawebinar/topjava/UserTestData.java | 5 +- .../mock/InMemoryUserRepositoryImpl.java | 1 - .../service/AbstractJpaUserServiceTest.java | 21 +++++ .../service/AbstractUserMealServiceTest.java | 3 +- .../service/AbstractUserServiceTest.java | 6 +- .../datajpa/DataJpaUserMealServiceTest.java | 2 +- .../datajpa/DataJpaUserServiceTest.java | 4 +- .../service/jpa/JpaUserServiceTest.java | 4 +- ...InMemoryAdminRestControllerSpringTest.java | 2 +- .../web/InMemoryAdminRestControllerTest.java | 6 +- 32 files changed, 378 insertions(+), 283 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java delete mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java diff --git a/config/messages/app.properties b/config/messages/app.properties index 195b36b13dd3..c6cc565f0ab9 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -1,2 +1,11 @@ app.title=Calories management app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application +app.login=Login as + +users.title=User list +users.edit=Edit user + +meals.title=Meals list +meals.edit=Edit meal + +common.select=Select \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 7c7d9e9e781c..7a7d45b8fb06 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -1,2 +1,11 @@ app.title=Подсчет калорий -app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery \ No newline at end of file +app.footer=Приложение по вебинару Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery +app.login=Зайти как + +users.title=Список пользователей +users.edit=Редактировать пользователя + +meals.title=Список еды +meals.edit=Редактировать еду + +common.select=Выбрать \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java index ca7fc8266330..65cd3a023472 100644 --- a/src/main/java/ru/javawebinar/topjava/LoggedUser.java +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -5,11 +5,11 @@ import ru.javawebinar.topjava.model.BaseEntity; public class LoggedUser { + private static int id = BaseEntity.START_SEQ; + private LoggedUser() { } - private static int id = BaseEntity.START_SEQ; - public static int id() { return id; } diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index f3c965387b7e..ec0fab898280 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,12 +1,11 @@ package ru.javawebinar.topjava.model; -import ru.javawebinar.topjava.util.UserMealsUtil; - import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; +import ru.javawebinar.topjava.util.UserMealsUtil; import javax.persistence.*; import javax.validation.constraints.Digits; @@ -22,7 +21,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 DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"), }) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Entity @@ -128,6 +127,10 @@ public List getMeals() { return meals; } + public void setRoles(List roles) { + this.roles = EnumSet.copyOf(roles); + } + @Override public String toString() { return "User (" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java index 0d68d20318ec..5c3e5a8cdab2 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java @@ -7,23 +7,19 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.repository.UserMealRepository; import javax.sql.DataSource; import java.time.LocalDateTime; import java.util.List; - +@Transactional(readOnly = true) public abstract class AbstractJdbcUserMealRepositoryImpl implements UserMealRepository { - private final RowMapper rowMapper; - public AbstractJdbcUserMealRepositoryImpl(RowMapper rowMapper, DataSource dataSource) { - this.rowMapper = rowMapper; - this.insertUserMeal = new SimpleJdbcInsert(dataSource) - .withTableName("meals") - .usingGeneratedKeyColumns("id"); + private final SimpleJdbcInsert insertUserMeal; - } + private final RowMapper rowMapper; @Autowired private JdbcTemplate jdbcTemplate; @@ -31,11 +27,17 @@ public AbstractJdbcUserMealRepositoryImpl(RowMapper rowMapper, DataSou @Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate; - private SimpleJdbcInsert insertUserMeal; + public AbstractJdbcUserMealRepositoryImpl(RowMapper rowMapper, DataSource dataSource) { + this.rowMapper = rowMapper; + this.insertUserMeal = new SimpleJdbcInsert(dataSource) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + } protected abstract T toDbValue(LocalDateTime ldt); @Override + @Transactional public UserMeal save(UserMeal userMeal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", userMeal.getId()) @@ -58,6 +60,7 @@ public UserMeal save(UserMeal userMeal, int userId) { } @Override + @Transactional public boolean delete(int id, int userId) { return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java index ac968db43e1d..7840410ae2ed 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -2,22 +2,34 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import javax.sql.DataSource; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; @Repository +@Transactional(readOnly = true) public class JdbcUserRepositoryImpl implements UserRepository { private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); + private static final RowMapper ROLE_ROW_MAPPER = (rs, rowNum) -> Role.valueOf(rs.getString("role")); @Autowired private JdbcTemplate jdbcTemplate; @@ -35,6 +47,7 @@ public JdbcUserRepositoryImpl(DataSource dataSource) { } @Override + @Transactional public User save(User user) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", user.getId()) @@ -48,7 +61,10 @@ public User save(User user) { if (user.isNew()) { Number newKey = insertUser.executeAndReturnKey(map); user.setId(newKey.intValue()); + insertRoles(user); } else { + deleteRoles(user); + insertRoles(user); namedParameterJdbcTemplate.update( "UPDATE users SET name=:name, email=:email, password=:password, " + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map); @@ -57,6 +73,7 @@ public User save(User user) { } @Override + @Transactional public boolean delete(int id) { return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0; } @@ -64,16 +81,75 @@ public boolean delete(int id) { @Override public User get(int id) { List users = jdbcTemplate.query("SELECT * FROM users WHERE id=?", ROW_MAPPER, id); - return DataAccessUtils.singleResult(users); + return setRoles(DataAccessUtils.singleResult(users)); } @Override public User getByEmail(String email) { - return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + return setRoles(jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email)); } @Override public List getAll() { - return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + List users = jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + + class UserRole { + UserRole(Role role, int userId) { + this.role = role; + this.userId = userId; + } + + final private Role role; + final private int userId; + + public Role getRole() { + return role; + } + + public int getUserId() { + return userId; + } + } + + Map> userRolesMap = jdbcTemplate.query("SELECT role, user_id FROM user_roles", + (rs, rowNum) -> new UserRole(Role.valueOf(rs.getString("role")), rs.getInt("user_id"))).stream() + .collect( + Collectors.groupingBy(UserRole::getUserId, Collectors.mapping(UserRole::getRole, Collectors.toList())) + ); + + users.forEach(u -> u.setRoles(userRolesMap.get(u.getId()))); + return users; + } + + private void insertRoles(User u) { + Set roles = u.getRoles(); + Iterator iterator = roles.iterator(); + + jdbcTemplate.batchUpdate("INSERT INTO user_roles (user_id, role) VALUES (?, ?)", + new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setInt(1, u.getId()); + ps.setString(2, iterator.next().name()); + } + + @Override + public int getBatchSize() { + return roles.size(); + } + }); + } + + private void deleteRoles(User u) { + jdbcTemplate.update("DELETE FROM user_roles WHERE user_id=?", u.getId()); + } + + private User setRoles(User u) { + if (u != null) { + List roles = jdbcTemplate.query("SELECT role FROM user_roles WHERE user_id=?", + ROLE_ROW_MAPPER, u.getId()); + u.setRoles(roles); + } + return u; } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java deleted file mode 100644 index ccbd1f032007..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ /dev/null @@ -1,93 +0,0 @@ -package ru.javawebinar.topjava.web; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.util.TimeUtil; -import ru.javawebinar.topjava.web.meal.UserMealRestController; - -import javax.servlet.ServletConfig; -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.util.Objects; - -public class MealServlet extends HttpServlet { - private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - - private UserMealRestController mealController; - - @Override - public void init(ServletConfig config) throws ServletException { - super.init(config); - WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - mealController = springContext.getBean(UserMealRestController.class); - } - - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - request.setCharacterEncoding("UTF-8"); - String action = request.getParameter("action"); - if (action == null) { - final UserMeal userMeal = new UserMeal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.valueOf(request.getParameter("calories"))); - - if (request.getParameter("id").isEmpty()) { - LOG.info("Create {}", userMeal); - mealController.create(userMeal); - } else { - LOG.info("Update {}", userMeal); - mealController.update(userMeal, getId(request)); - } - response.sendRedirect("meals"); - - } else if (action.equals("filter")) { - LocalDate startDate = TimeUtil.parseLocalDate(resetParam("startDate", request)); - LocalDate endDate = TimeUtil.parseLocalDate(resetParam("endDate", request)); - LocalTime startTime = TimeUtil.parseLocalTime(resetParam("startTime", request)); - LocalTime endTime = TimeUtil.parseLocalTime(resetParam("endTime", request)); - request.setAttribute("mealList", mealController.getBetween(startDate, startTime, endDate, endTime)); - request.getRequestDispatcher("/mealList.jsp").forward(request, response); - } - } - - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String action = request.getParameter("action"); - - if (action == null) { - LOG.info("getAll"); - request.setAttribute("mealList", mealController.getAll()); - request.getRequestDispatcher("mealList.jsp").forward(request, response); - } else if ("delete".equals(action)) { - int id = getId(request); - LOG.info("Delete {}", id); - mealController.delete(id); - response.sendRedirect("meals"); - } else { - final UserMeal meal = "create".equals(action) ? - new UserMeal(LocalDateTime.now(), "", 1000) : // create - mealController.get(getId(request)); // update - request.setAttribute("meal", meal); - request.getRequestDispatcher("mealEdit.jsp").forward(request, response); - } - } - - private String resetParam(String param, HttpServletRequest request) { - String value = request.getParameter(param); - request.setAttribute(param, value); - return value; - } - - private int getId(HttpServletRequest request) { - String paramId = Objects.requireNonNull(request.getParameter("id"), "parameter id must not be null"); - return Integer.valueOf(paramId); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 2134910b0078..0046617052f6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.service.UserMealService; import ru.javawebinar.topjava.service.UserService; import javax.servlet.http.HttpServletRequest; @@ -17,7 +18,10 @@ @Controller public class RootController { @Autowired - private UserService service; + private UserService userService; + + @Autowired + private UserMealService mealService; @RequestMapping(value = "/", method = RequestMethod.GET) public String root() { @@ -26,7 +30,7 @@ public String root() { @RequestMapping(value = "/users", method = RequestMethod.GET) public String userList(Model model) { - model.addAttribute("userList", service.getAll()); + model.addAttribute("userList", userService.getAll()); return "userList"; } diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java deleted file mode 100644 index 4aa6c14ce7dc..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.javawebinar.topjava.web; - -import org.slf4j.Logger; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import ru.javawebinar.topjava.LoggedUser; -import ru.javawebinar.topjava.web.user.AdminRestController; - -import javax.servlet.ServletConfig; -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); - - private AdminRestController adminController; - - @Override - public void init(ServletConfig config) throws ServletException { - super.init(config); - WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - adminController = springContext.getBean(AdminRestController.class); - } - - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - int userId = Integer.valueOf(request.getParameter("userId")); - LoggedUser.setId(userId); - response.sendRedirect("meals"); - } - - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - LOG.debug("getAll"); - request.setAttribute("userList", adminController.getAll()); - request.getRequestDispatcher("/userList.jsp").forward(request, response); -// response.sendRedirect("userList.jsp"); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java new file mode 100644 index 000000000000..c045bc7e6428 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java @@ -0,0 +1,70 @@ +package ru.javawebinar.topjava.web.meal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.service.UserMealService; +import ru.javawebinar.topjava.to.UserMealWithExceed; +import ru.javawebinar.topjava.util.TimeUtil; +import ru.javawebinar.topjava.util.UserMealsUtil; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +/** + * GKislin + * 06.03.2015. + */ +@Controller +public class AbstractUserMealController { + private static final Logger LOG = LoggerFactory.getLogger(AbstractUserMealController.class); + + @Autowired + private UserMealService service; + + public UserMeal get(int id) { + int userId = LoggedUser.id(); + LOG.info("get meal {} for User {}", id, userId); + return service.get(id, userId); + } + + public void delete(int id) { + int userId = LoggedUser.id(); + LOG.info("delete meal {} for User {}", id, userId); + service.delete(id, userId); + } + + public List getAll() { + int userId = LoggedUser.id(); + LOG.info("getAll for User {}", userId); + return UserMealsUtil.getWithExceeded(service.getAll(userId), LoggedUser.getCaloriesPerDay()); + } + + public void update(UserMeal meal, int id) { + meal.setId(id); + int userId = LoggedUser.id(); + LOG.info("update {} for User {}", meal, userId); + service.update(meal, userId); + } + + public UserMeal create(UserMeal meal) { + meal.setId(null); + int userId = LoggedUser.id(); + LOG.info("create {} for User {}", meal, userId); + return service.save(meal, userId); + } + + public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { + int userId = LoggedUser.id(); + LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId); + return UserMealsUtil.getFilteredWithExceeded( + service.getBetweenDates( + startDate != null ? startDate : TimeUtil.MIN_DATE, endDate != null ? endDate : TimeUtil.MAX_DATE, userId), + startTime != null ? startTime : LocalTime.MIN, endTime != null ? endTime : LocalTime.MAX, LoggedUser.getCaloriesPerDay() + ); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java new file mode 100644 index 000000000000..148810c687db --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java @@ -0,0 +1,84 @@ +package ru.javawebinar.topjava.web.meal; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.util.TimeUtil; + +import javax.servlet.http.HttpServletRequest; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Objects; + +/** + * GKislin + * 06.03.2015. + */ +@Controller +@RequestMapping(value = "/meals") +public class JspUserMealController extends AbstractUserMealController { + + @RequestMapping(method = RequestMethod.GET) + public String mealList(Model model) { + model.addAttribute("mealList", super.getAll()); + return "mealList"; + } + + @RequestMapping(value = "/delete", method = RequestMethod.GET) + public String delete(HttpServletRequest request) { + super.delete(getId(request)); + return "redirect:/meals"; + } + + @RequestMapping(value = "/update", method = RequestMethod.GET) + public String editForUpdate(HttpServletRequest request, Model model) { + model.addAttribute("meal", super.get(getId(request))); + return "mealEdit"; + } + + @RequestMapping(value = "/create", method = RequestMethod.GET) + public String editForCreate(Model model) { + model.addAttribute("meal", new UserMeal(LocalDateTime.now(), "", 1000)); + return "mealEdit"; + } + + @RequestMapping(method = RequestMethod.POST) + public String updateOrCreate(HttpServletRequest request) { + String id = request.getParameter("id"); + UserMeal userMeal = new UserMeal(id.isEmpty() ? null : Integer.valueOf(id), + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); + + if (userMeal.isNew()) { + super.create(userMeal); + } else { + super.update(userMeal, userMeal.getId()); + } + return "redirect:/meals"; + } + + @RequestMapping(value = "/filter", method = RequestMethod.POST) + public String getBetween(HttpServletRequest request, Model model) { + LocalDate startDate = TimeUtil.parseLocalDate(resetParam("startDate", request)); + LocalDate endDate = TimeUtil.parseLocalDate(resetParam("endDate", request)); + LocalTime startTime = TimeUtil.parseLocalTime(resetParam("startTime", request)); + LocalTime endTime = TimeUtil.parseLocalTime(resetParam("endTime", request)); + model.addAttribute("mealList", super.getBetween(startDate, startTime, endDate, endTime)); + return "mealList"; + } + + private String resetParam(String param, HttpServletRequest request) { + String value = request.getParameter(param); + request.setAttribute(param, value); + return value; + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.valueOf(paramId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index d928cb78f8c5..1bb949f62f61 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -1,67 +1,11 @@ package ru.javawebinar.topjava.web.meal; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; -import ru.javawebinar.topjava.LoggedUser; -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.service.UserMealService; import ru.javawebinar.topjava.service.UserMealServiceImpl; import ru.javawebinar.topjava.to.UserMealWithExceed; import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.util.UserMealsUtil; -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; - @Controller -public class UserMealRestController { - private static final Logger LOG = LoggerFactory.getLogger(UserMealRestController.class); - - @Autowired - private UserMealService service; - - public UserMeal get(int id) { - int userId = LoggedUser.id(); - LOG.info("get meal {} for User {}", id, userId); - return service.get(id, userId); - } - - public void delete(int id) { - int userId = LoggedUser.id(); - LOG.info("delete meal {} for User {}", id, userId); - service.delete(id, userId); - } - - public List getAll() { - int userId = LoggedUser.id(); - LOG.info("getAll for User {}", userId); - return UserMealsUtil.getWithExceeded(service.getAll(userId), LoggedUser.getCaloriesPerDay()); - } - - public void update(UserMeal meal, int id) { - meal.setId(id); - int userId = LoggedUser.id(); - LOG.info("update {} for User {}", meal, userId); - service.update(meal, userId); - } - - public UserMeal create(UserMeal meal) { - meal.setId(null); - int userId = LoggedUser.id(); - LOG.info("create {} for User {}", meal, userId); - return service.save(meal, userId); - } - - public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { - int userId = LoggedUser.id(); - LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId); - return UserMealsUtil.getFilteredWithExceeded( - service.getBetweenDates( - startDate != null ? startDate : TimeUtil.MIN_DATE, endDate != null ? endDate : TimeUtil.MAX_DATE, userId), - startTime != null ? startTime : LocalTime.MIN, endTime != null ? endTime : LocalTime.MAX, LoggedUser.getCaloriesPerDay() - ); - } +public class UserMealRestController extends AbstractUserMealController { } \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index 0fbffd0dfb58..cc816500ce36 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -11,7 +11,8 @@ VALUES ('Admin', 'admin@gmail.com', 'admin'); INSERT INTO user_roles (role, user_id) VALUES ('ROLE_USER', 100000), - ('ROLE_ADMIN', 100001); + ('ROLE_ADMIN', 100001), + ('ROLE_USER', 100001); INSERT INTO meals (date_time, description, calories, user_id) VALUES ('2015-05-30 10:00:00', 'Завтрак', 500, 100000), diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 790d3fcbe0a1..a7729a6479d5 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -21,6 +21,8 @@ + + @@ -58,6 +60,11 @@ + + + + @@ -77,6 +84,9 @@ + + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index fc2b58f65786..90f86d23e672 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -1,9 +1,12 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <fmt:message key="app.title"/> + ${pageContext.request.requestURL} + diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp index 74dd0c49a982..0208cf97703b 100644 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -8,15 +8,15 @@
- Login as: - +
diff --git a/src/main/webapp/WEB-INF/jsp/mealEdit.jsp b/src/main/webapp/WEB-INF/jsp/mealEdit.jsp index d71aeb3a6bbd..3df0c26c6dc9 100644 --- a/src/main/webapp/WEB-INF/jsp/mealEdit.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealEdit.jsp @@ -1,15 +1,13 @@ <%@ 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" %> - - Meal - - + +

Home

-

Edit meal

+


@@ -30,5 +28,6 @@
+ diff --git a/src/main/webapp/WEB-INF/jsp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp index c6f15c80f3fc..a29dcedd0645 100644 --- a/src/main/webapp/WEB-INF/jsp/mealList.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp @@ -3,15 +3,14 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - Meal list - - + +
-

Home

-

Meal list

-
+ <%--http://stackoverflow.com/questions/10327390/how-should-i-get-root-folder-path-in-jsp-page--%> +

Home

+

+
From Date:
@@ -31,7 +30,7 @@
- Add Meal + Add Meal
@@ -52,11 +51,12 @@ - - + +
${meal.description} ${meal.calories}UpdateDeleteUpdateDelete
+ diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index 2419b79b3167..e3b6ed640305 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -8,6 +8,7 @@
+

diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 330fb5bba850..abc0d013bcfc 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -36,26 +36,21 @@ / - + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index 2d70c7d54d0a..4219b708464f 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -18,14 +18,14 @@ public class MealTestData { public static final int MEAL1_ID = START_SEQ + 2; public static final int ADMIN_MEAL_ID = START_SEQ + 8; - public static final UserMeal MEAL1 = new UserMeal(MEAL1_ID, LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500); - public static final UserMeal MEAL2 = new UserMeal(MEAL1_ID + 1, LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000); - public static final UserMeal MEAL3 = new UserMeal(MEAL1_ID + 2, LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500); - public static final UserMeal MEAL4 = new UserMeal(MEAL1_ID + 3, LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500); - public static final UserMeal MEAL5 = new UserMeal(MEAL1_ID + 4, LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 1000); - public static final UserMeal MEAL6 = new UserMeal(MEAL1_ID + 5, LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510); - public static final UserMeal ADMIN_MEAL = new UserMeal(ADMIN_MEAL_ID, LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510); - public static final UserMeal ADMIN_MEAL2 = new UserMeal(ADMIN_MEAL_ID + 1, LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500); + public static final UserMeal MEAL1 = new UserMeal(MEAL1_ID, of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500); + public static final UserMeal MEAL2 = new UserMeal(MEAL1_ID + 1, of(2015, Month.MAY, 30, 13, 0), "Обед", 1000); + public static final UserMeal MEAL3 = new UserMeal(MEAL1_ID + 2, of(2015, Month.MAY, 30, 20, 0), "Ужин", 500); + public static final UserMeal MEAL4 = new UserMeal(MEAL1_ID + 3, of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500); + public static final UserMeal MEAL5 = new UserMeal(MEAL1_ID + 4, of(2015, Month.MAY, 31, 13, 0), "Обед", 1000); + public static final UserMeal MEAL6 = new UserMeal(MEAL1_ID + 5, of(2015, Month.MAY, 31, 20, 0), "Ужин", 510); + public static final UserMeal ADMIN_MEAL = new UserMeal(ADMIN_MEAL_ID, of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510); + public static final UserMeal ADMIN_MEAL2 = new UserMeal(ADMIN_MEAL_ID + 1, of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500); public static final List USER_MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 50ff12880461..206507aa8aa7 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -4,7 +4,7 @@ import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserMealWithExceed; -import ru.javawebinar.topjava.web.meal.UserMealRestController; +import ru.javawebinar.topjava.web.meal.AbstractUserMealController; import ru.javawebinar.topjava.web.user.AdminRestController; import java.time.LocalDate; @@ -18,15 +18,16 @@ public static void main(String[] args) { // java 7 Automatic resource management try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) { appCtx.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.DB_IMPLEMENTATION); - appCtx.load("spring/spring-app.xml", "spring/spring-db.xml"); + appCtx.load("spring/spring-app.xml", "spring/spring-db.xml", "spring/spring-mvc.xml"); appCtx.refresh(); System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + System.out.println(adminUserController.get(UserTestData.USER_ID)); System.out.println(adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); System.out.println(); - UserMealRestController mealController = appCtx.getBean(UserMealRestController.class); + AbstractUserMealController mealController = appCtx.getBean(AbstractUserMealController.class); List filteredMealsWithExceeded = mealController.getBetween( LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 3ad0d8a4a2b9..8949f9974a27 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -16,7 +16,7 @@ public class UserTestData { public static final int ADMIN_ID = START_SEQ + 1; public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); - public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); + public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN, Role.ROLE_USER); public static final ModelMatcher MATCHER = new ModelMatcher<>(u -> ((u instanceof TestUser) ? (TestUser) u : new TestUser(u))); @@ -66,7 +66,8 @@ public boolean equals(Object o) { && Objects.equals(this.name, that.name) && Objects.equals(this.email, that.email) && Objects.equals(this.caloriesPerDay, that.caloriesPerDay) - && Objects.equals(this.enabled, that.enabled); + && Objects.equals(this.enabled, that.enabled) + && Objects.equals(this.roles, that.roles); } } } diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java index 5c53c41023ce..34694f00ebf8 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -12,7 +12,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java new file mode 100644 index 000000000000..eb8f8e9ea70e --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java @@ -0,0 +1,21 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import ru.javawebinar.topjava.repository.JpaUtil; + +/** + * GKislin + * 07.04.2015. + */ +abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest { + @SuppressWarnings("SpringJavaAutowiringInspection") + @Autowired + private JpaUtil jpaUtil; + + @Before + public void setUp() throws Exception { + super.setUp(); + jpaUtil.clear2ndLevelHibernateCache(); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java index bc744b9b46d9..971a54b2ff5a 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java @@ -6,7 +6,6 @@ import org.junit.rules.Stopwatch; import org.junit.runner.Description; import org.springframework.beans.factory.annotation.Autowired; -import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -43,7 +42,7 @@ protected void finished(long nanos, Description description) { @Test public void testDelete() throws Exception { - service.delete(MealTestData.MEAL1_ID, USER_ID); + service.delete(MEAL1_ID, USER_ID); MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID)); } diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 4a0f374f56fa..0252e6ddff4b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -7,7 +7,6 @@ import ru.javawebinar.topjava.UserTestData.*; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.util.Arrays; @@ -20,13 +19,9 @@ abstract public class AbstractUserServiceTest extends AbstractServiceTest { @Autowired protected UserService service; - @Autowired - protected JpaUtil jpaUtil; - @Before public void setUp() throws Exception { service.evictCache(); - jpaUtil.clear2ndLevelHibernateCache(); } @Test @@ -81,6 +76,7 @@ public void testUpdate() throws Exception { TestUser updated = new TestUser(USER); updated.setName("UpdatedName"); updated.setCaloriesPerDay(330); + updated.setRoles(Arrays.asList(Role.ROLE_ADMIN, Role.ROLE_USER)); service.update(updated.asUser()); MATCHER.assertEquals(updated, service.get(USER_ID)); } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java index 6a8ea592572c..0b85be7188e8 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java @@ -21,7 +21,7 @@ public void testGetWithUser() throws Exception { } @Test(expected = NotFoundException.class) - public void testGetWithMealsNotFound() throws Exception { + public void testGetWithUserNotFound() throws Exception { service.getWithUser(MEAL1_ID, ADMIN_ID); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index 84ba35cf9032..a14ad645320d 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -5,14 +5,14 @@ import org.springframework.test.context.ActiveProfiles; import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.service.AbstractUserServiceTest; +import ru.javawebinar.topjava.service.AbstractJpaUserServiceTest; import ru.javawebinar.topjava.util.exception.NotFoundException; import static ru.javawebinar.topjava.Profiles.DATAJPA; import static ru.javawebinar.topjava.UserTestData.*; @ActiveProfiles(DATAJPA) -public class DataJpaUserServiceTest extends AbstractUserServiceTest { +public class DataJpaUserServiceTest extends AbstractJpaUserServiceTest { @Test public void testGetWithMeals() throws Exception { User user = service.getWithMeals(USER_ID); diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java index d1b3e4699785..ffb1fea03c22 100644 --- a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java @@ -1,10 +1,10 @@ package ru.javawebinar.topjava.service.jpa; import org.springframework.test.context.ActiveProfiles; -import ru.javawebinar.topjava.service.AbstractUserServiceTest; +import ru.javawebinar.topjava.service.AbstractJpaUserServiceTest; import static ru.javawebinar.topjava.Profiles.JPA; @ActiveProfiles(JPA) -public class JpaUserServiceTest extends AbstractUserServiceTest { +public class JpaUserServiceTest extends AbstractJpaUserServiceTest { } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java index e82101a7146c..d9a02871f856 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -18,7 +18,7 @@ import static ru.javawebinar.topjava.UserTestData.ADMIN; import static ru.javawebinar.topjava.UserTestData.USER; -@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/mock.xml"}) +@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/spring-mvc.xml", "classpath:spring/mock.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class InMemoryAdminRestControllerSpringTest { diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java index ef619db86394..0c38731c88bb 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -21,7 +21,7 @@ public class InMemoryAdminRestControllerTest { @BeforeClass public static void beforeClass() { - appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/mock.xml"); + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-mvc.xml", "spring/mock.xml"); appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/spring-db.xml"); System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); controller = appCtx.getBean(AdminRestController.class); @@ -38,7 +38,9 @@ public void setUp() throws Exception { @AfterClass public static void afterClass() { - appCtx.close(); +// May cause during JUnit "Cache is not alive (STATUS_SHUTDOWN)" as JUnit share Spring context for speed +// http://stackoverflow.com/questions/16281802/ehcache-shutdown-causing-an-exception-while-running-test-suite +// appCtx.close(); } @Test From 5faf6a6219bc06853b6a0375f1f43163567bd822 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 26 May 2016 21:31:04 +0500 Subject: [PATCH 079/166] HW7 - apply HW patches 07 --- doc/entrance.md | 106 +++++++++++++++++++++++ doc/graduation.md | 32 +++++++ doc/lesson01.md | 187 +++++++++++++++++++++++++++++++++++++++++ doc/lesson02.md | 191 +++++++++++++++++++++++++++++++++++++++++ doc/lesson03.md | 182 +++++++++++++++++++++++++++++++++++++++ doc/lesson04.md | 153 +++++++++++++++++++++++++++++++++ doc/lesson05.md | 124 +++++++++++++++++++++++++++ doc/lesson06.md | 201 ++++++++++++++++++++++++++++++++++++++++++++ doc/lesson07.md | 134 +++++++++++++++++++++++++++++ doc/lesson08.md | 148 ++++++++++++++++++++++++++++++++ doc/lesson09.md | 128 ++++++++++++++++++++++++++++ doc/lesson10.md | 105 +++++++++++++++++++++++ doc/lesson_final.md | 108 ++++++++++++++++++++++++ 13 files changed, 1799 insertions(+) create mode 100644 doc/entrance.md create mode 100644 doc/graduation.md create mode 100644 doc/lesson01.md create mode 100644 doc/lesson02.md create mode 100644 doc/lesson03.md create mode 100644 doc/lesson04.md create mode 100644 doc/lesson05.md create mode 100644 doc/lesson06.md create mode 100644 doc/lesson07.md create mode 100644 doc/lesson08.md create mode 100644 doc/lesson09.md create mode 100644 doc/lesson10.md create mode 100644 doc/lesson_final.md diff --git a/doc/entrance.md b/doc/entrance.md new file mode 100644 index 000000000000..8a2ea1e294eb --- /dev/null +++ b/doc/entrance.md @@ -0,0 +1,106 @@ +Java Enterprise Online Project +=============================== +Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: +Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. + +Вводное занятие +=============== +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Системы управления версиями. Git. + +- Система управления версиями. VCS/DVSC. +- Ресурсы: + - Интерактивная Git обучалка + - Основы Git + - Working with remote repositories + - Видео по обучению Git + - Как начать работать с GitHub: быстрый старт + - Справочник в графическом виде + - Git Overview + - Видеокурс по Git + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Работа с проектом (выполнять инструкции) +- **prepare-to-HW0.patch (скачать и положить в каталог вашего проекта)** +- Доступно о Java 8 Lambda + +## Инструкция по шагам (из видео): +- Установить ПО (git, JDK8, IntelliJ IDEA, Maven) +- Создать аккаунт на GitHub +- Сделать Fork ЭТОГО проекта (https://github.com/JavaOPs/topjava) +- Сделать локальный репозиторий проекта: +
git clone https://github.com/[Ваш аккаунт]/topjava.git
+- Открыть и настроить проект в IDEA +- По ходу видео сделать Apply Patch... скаченного патча Prepare_ to_ HW0.patch +- Закоммитить и запушить изменения (commit + push) +- Сделать ветку домашнего задания +- Выполнить задание и залить на GitHub (commit + push) +- Переключиться в основную ветку проекта master. + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW0 + +- Java 8 Date and Time API +- Time complexity +- Временная сложность алгоритма +- Вычислительная сложность +``` +Реализовать UserMealsUtil.getFilteredMealsWithExceeded: +- должны возвращаться только записи между startTime и endTime +- поле UserMealWithExceed.exceed должно показывать, + превышает ли сумма калорий за весь день параметра метода caloriesPerDay + +Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. + +- Проверте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) +- Оцените Time complexity вашего алгоритма (если он O(N*N)- попробуйте сделать O(N). +``` +Optional + +- Java 8: Lambda выражения +- Java 8: Потоки +- Pуководство по Java 8 Stream +- Лямбда-выражения в Java 8 +- Шпаргалка Java Stream API +- stream.map vs forEach + +``` +Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) +``` + +### ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Как брать диапазон дат? Какие брать дефолтные значения для null? + +Для диапазона нужно использовать уже готовый `TimeUtil.isBetween`. Как приложение должно себя вести можно проверить на demo. + +> Почему мы сначала суммируем калории, а потом фильтруем? Если записи за несколько лет то это лишняя работа. Не будет правильнее сначала фильтр, а потом все остальное? + +На вход в getFilteredWithExceeded подается список, уже отфильтрованный по датам. Будем делать это условием в SQL. + +> Лямбда выражения заменяют анонимные классы? + +Синтаксически лямбду можно преобразовать в анонимный класс (в IDEA Alt+Enter на лямбде и Replace lambda with anonymous class). +Наоборот- только SAM (Single Abstract Method) интерфейс можно преобразовать в lambda. В JVM вызов происходит через invokedynamic. + +> Как оценить временную сложность? Как определить: O(N), O(N*N) и O(N+N) ? + +Из wiki это зависимость объёма работы, которая выполняется некоторым алгоритмом, от размера входных данных. +O- это коэффициент. поэтому нет O(N+N), есть O(N). +Для нашего случая все просто- если вложенный цикл или стрим, то O(N*N), иначе O(N). + +> Какая сложность у map.containsKey(key)? не добрался до исходников. + +Вообще исходники есть: `java.util.HashMap.containsKey`. Устройство Map (и вообще коллекции)- одни из самых частых вопросов на собеседовании. +Читаем Структуры данных в картинках и Java собеседование. Коллекции. +При хорошем распределении ключей сложность `HashMap.containsKey - O(1), TreeMap.containsKey - O(log2 N), а LinkedList.contains- O(N)`. + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Типичные ошибки в домашнем задании: +- Объявление переменных + - объявляйте переменные непосредственно перед их использованием (не все вверху) + - если возможно, инициализируйте их сразу при объявлении. Если она неизменяемая, будет красиво сделать ее final. + - рассмотрите возможность сделать ее inline (вообще без переменной). Если логика несложная, становится лаконичнее. + - тип переменных-коллекций нужно объявлять интерфейсом: не HashMap, а Map + - имена переменных должны быть информативные и служить заменой комментариям + +- Оформление + - представьте, что это ваше тестовое задание на работу. Не должно быть лишних комментариев, TODO, .. + - комментариев должен быть минимум (самодокументируемый код). Если таки нужно - лучше на english. + - почаще пользуйтесь Ctrl+Alt+L - Reformat Code + - в `main()` можно выводить на `System.out`. Для этого подходит перопределение `toString` объектов вывода. diff --git a/doc/graduation.md b/doc/graduation.md new file mode 100644 index 000000000000..beeacd53ff41 --- /dev/null +++ b/doc/graduation.md @@ -0,0 +1,32 @@ +Java Enterprise Online Project +=============================== +Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: +Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. + +Graduation project +================= + +Design and implement a JSON API using Hibernate/Spring/SpringMVC (or Spring-Boot) **without frontend**. + +The task is: + +Build a voting system for deciding where to have lunch. + + * 2 types of users: admin and regular users + * Admin can input a restaurant and it's lunch menu of the day (2-5 items usually, just a dish name and price) + * Menu changes each day (admins do the updates) + * Users can vote 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 after 11:00 then it is too late, vote can't be changed + +Each restaurant provides 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 used by a frontend developer to build frontend on top of that. diff --git a/doc/lesson01.md b/doc/lesson01.md new file mode 100644 index 000000000000..e794ca3b788c --- /dev/null +++ b/doc/lesson01.md @@ -0,0 +1,187 @@ +# Онлайн проекта Topjava + +- **Напоминаю, что cвой код пишете только в ветках HWxx. Код в ветке MASTER трогать нельзя! +Модификация кода только через патчи в материалах урока (Apply Patch), иначе придется мержить код. +Все патчи объязательны и применяются по порядку. Если при применении патча предлагается мержд, cмотрите GIT Wiki, Patch не накатывается.** + +- **Делать Apply Patch лучше по одному непосредственно перед видео на эту тему - тогда при просмотре вы сразу сможете отслеживать изменения кода проекта.** После патча часто полезно запустить и посмотреть на работу приложения. + +- **Код проекта постоянно модифицируется, поэтому в видео вы можете увидеть старую версию кода. Иногда это способствует лучшему пониманию кода и вы видете, каким образом проект развивался** + +- **Не надо подробно читать все ссылки занятия, иначе времени не останется на ДЗ, что гораздо важнее. Пусть ссылки будут Вашим справочником, к которым вы в любое время можете обратиться.** + +- **Подсказка по работе с патчами (GIT Wiki)** + +## Материалы 1-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW0: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Optional: реализация getFilteredMealsWithExceeded через Stream API +- **1-1-HW0-stream.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Работа с git в IDEA. Реализация через цикл. +- **1-2-HW0-cycle.patch** + +> Изменения в проекте: `map.getOrDefault` земенил на `map.merge`, `for` заменены на `forEach` + +## Занятие 1: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Обзор используемых в проекте технологий. Интеграция ПО. +- Обзор популярности инструментов и технологий Java за 2014 г. +- Видео "Приложение Spring Pet Clinic" +- Приложение Spring Pet Clinic. +- Demo Spring Pet Clinic + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Maven. +- Среда сборки проектов Maven. +- The Central Repository +- Настройка пропертей Maven: кодировка, java version, зависимости, maven-compiler-plugin +- The Reactor. Snapshots +- Недостатки Maven. Другие инструменты сборки. +- Справочник: + - Автоматизация сборки проекта + - Home Page + - Maven: The Complete Reference + - Разработка ПО вместе с maven + - Build Lifecycle + - Dependency Mechanism + - Создание своих архетипов и каталогов в Maven + - Зависимости, профили + - Bintray: gateway to Maven Central + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. WAR. Веб-контейнер Tomcat. Сервлеты. +- **1-3-switch-to-war.patch** +> Сервлет добавляется в следующем патче, те в web.xml он будет подчеркиваться красным. + +- **1-4-add-servlet-api.patch** +- **1-5-forward-to-redirect.patch** + +- Перевод проекта на Web. +- Tomcat Home Page +- Сервлеты. +- Настройка и деплой в Tomcat. Tomcat manager. Remote debug. +- Запуск Tomcat из IDEA. Динамическое обновление без передеплоя. +- Redirect, Forward, Application context, Servlet context + - Томкат менеджер: http://localhost:8080/manager + - Наше приложение: http://localhost:8080/topjava + - Наш сервлет: http://localhost:8080/topjava/users + +- Справочник: + - Отладчик IntelliJ IDEA + - Yakov Fain: Intro to Java EE. Glassfish. Servlets (по русски) + - Yakov Fain: HTTP Sessions, Cookies, WAR deployments, JSP (по русски) + - Golovach Courses: Junior.February2014.Servlets + - Remotely debug on tomcat from IDEA + - Java Server Page + - Java Server Pages (JSP) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Логирование. +- **1-6-logging.patch** +> **установите переменную окружения на TOPJAVA_ROOT на корень проекта и перезапустите IDEA** + +> изменения в проекте: убрал `LoggerWrapper` и логирую напрямую в логгер SLF4J. При логгировании через вспомогательный класс, в логе теряется имя исходного класса. + +- Java Logging: история кошмара +- Ведение лога приложения +- Log4j, Logback +- Добавление зависимостей логирования в проект. Переменная окружения TOPJAVA_ROOT +- Конфигурирование логирования. Настройка Live Template. +- Тестирование логирования в сервлете. + +#### Проверочные вопросы: +- Что нужно изменить в `pom.xml`, чтобы перейти с logback на log4j ? + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Подсоединение к логгеру по JMX. Выбор работы. +- **1-7-remote-jmx.patch** + +> Для подключения к Remote Process (`localhost:1099`) положить `setenv.bat` в `$TOMCET_HOME/bin`. + +- Управление логированием по Java Management Extensions. + +### ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> Используются ли еще где-то в реальной разработке JSP, или это уже устаревшая технология? Заменит ли ее JSF (https://javatalks.ru/topics/38037)? + +JSF и JSP- разные ниши и задачи. +JSP- шаблонизатор, JSF- МVС фреймворк. Из моего опыта- с JSP сталкивался в 60% проектов. Его прямая замена: http://www.thymeleaf.org, но пока встречется достаточно редко. JSP не умирает, потому что просто и дешево. Кроме того включается в любой веб-контейнер (в Tomcat его реализация Jasper) + +JSF- sun-овский еще фреймворк, с которым я ни разу не сталкивался и особого желания нет. Вот он как раз, по моему мнению, активно замещается хотябы angular. + + +> А зачем мы использовали logback? Почему SLF4J нас не устроило? Почему реализация логирования не log4j? + +В `slf4j-api` это API. Там есть только пустая реализация `org.slf4j.helpers.NOPLogger` (можно посмотреть в исходниках). Logback для новых проектов стал стандарт. spring-petclinic и spring-boot используют его. http://logback.qos.ch/reasonsToSwitch.html + +--------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW01 (делаем ветку HW01 от последнего патча) + + По аналогии с Users добавить MealServlet и mealList.jsp. Время выводить без 'T'. + (пока фильтрацию не делаем и User к UserMeal не добавляем) + Задеплоить приложение (war) в Tomcat c applicationContext=topjava + (приложение должно быть доступно по http://localhost:8080/topjav) + Попробовать разные деплои в Tomcat, remote и local debug + + Сделать отображения списка еды в jsp, цвет записи в таблице зависит от параметра isExceeded (красный/зеленый). + Вариант реализации: + - из сервлета обращаетесь к реализации хранения еды в памяти; + - преобразуете результат в List; + - кладете список в запрос (request.setAttribute); + - делаете forward на jsp для отрисовки таблицы (при redirect аттрибуты теряются). + В jsp для цикла можно использовать JSTL tag forEach. + + Для подключения `JSTL` в `pom.xml` нужно добавить зависимость: + + + javax.servlet + jstl + 1.2 + + +- Ресурсы: + - Интернет-приложения на JAVA + - HTTP 1.0 vs 1.1 + - JSP + - JSTL для написания JSP страниц + - JSTL: Шаблоны для разработки веб-приложений в java + +Optional + + Сделать В ПАМЯТИ (Map) реализацию CRUD (create/read/update/delete) для списка еды с учетом многопоточности. + Хранение в памяти будет одна из наших реализаций (такжен будет jdbc, jpa и data-jpa), подумайте над ее названием. + Работать с ней через интерфейс, который не должен ничего знать о деталях реализации (Map, DB или что-то еще). + + В качестве ключа добавить id в UserMeal/ UserMealWithExceed. + Сделать форму редактирования в JSP: + AJAX/JavaScript использовать не надо, делаем через
и doPost() в сервлете. + + - SIMPLE CRUD USING JSP, SERVLET + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Решение проблем + +- Если вы не попадаете на страничку/брекпойнт в сервлете: + - внимательно проверьте url и applicationContext (Application Context должен быть тот же, что и url приложения): https://github.com/JavaOPs/topjava/wiki/IDEA + - деплоить лучше как war exploded: просто нет упаковки в war и при нажатой кнопке `Update Resources on Frame Deactivation` можно обновляться css, html, jsp без передеплоя. При изменении web.xml, добавлении методов, классов необходим redeploy. + - посомтрите в task manager: возможно у вас запущено несколько JVM и они мешают друг другу. + +- Проблемы с кодировкой в POST. Возможное решение: +``` +protected void doPost(HttpServletRequest request, ...) { + request.setCharacterEncoding("UTF-8"); +``` + +- Пробелема с maven зависимостью: иногда помогает удаление закаченного артифакта из локального maven репозитория (посмотреть его расположение можно в Maven Settings) и Reimport (последняя и первая кнопка в Maven Projects). + +- Для работы с JSTL кроме добавления зависимости в `pom.xml` не забываем добавлять в шапку JSP: +``` +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +... +``` +- После выставления переменной окружения IDEA нужно рестартовать. Проверить, видит ли java переменную окуржения можно так: `System.getenv("TOPJAVA_ROOT")`. Еще вариант: добавить `-DTOPJAVA_ROOT=...` в опции запуска приложения, тогда она доступна из java как `System.getProperty("TOPJAVA_ROOT")`. + +---------------------------- + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Типичные ошибки +- Хранить нужно `UserMeal` и конвертировать ее в `UserMealWithExceed` когда отдаем список на отображение в JSP. Иначе при редактировании любой записи или изменении юзером своей нормы `caloriesPerDay` нужно будет пересчитывать все записи юзера. +- Стили `color` можно применять ко всей строке таблицы `tr`, а не каждой ячейке. +- DateTimeFormatter можно сделать один заранее (он потокобезопасный в отличии от SimpleDateFormatter), а не создавать новый при каждом запросе. +- Реализаций интерфейса хранения будет несколько. Нужно учитывать это в названии класса. +- Если в названии класса есть Meal, не нужно использовать слово meal в методах класса. diff --git a/doc/lesson02.md b/doc/lesson02.md new file mode 100644 index 000000000000..8a6df8fb0ce3 --- /dev/null +++ b/doc/lesson02.md @@ -0,0 +1,191 @@ +## Материалы 2-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW1: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. отображения списка еды в JSP +- **1-HW1.patch** + +> Изменения в UserMealsUtil: + - Сделал константу List MEAL_LIST + - Убрал из имен методов Meals + - Сделал вспомогательный метод getWithExceeded + +- MVC - Model View Controller + +> Форматирование даты поменял на `${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Optional: реализация CRUD +- **2-HW1-optional.patch** + +> Про repository будет подробно в видео "Слои приложения" + +- HTTP 1.0 vs 1.1 + +### ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Вопросы по HW1 + +> Зачем в InMemoryUserMealRepositoryImpl наполнять map с помощью нестатического блока инициализации, а не в конструкторе? + +Разницы нет. Разве что напомнить вам про эту конструкцию:) См. Малоизвестные особенности Java + +> Почему InMemoryUserMealRepositoryImplу y нас не singleton? + +По умолчанию бины Spring являются синглетонами (в его контексте). А все наши классы слоев приложения будут создаваться через Spring. + +> Зачем вместо `` писать `` ? + +Ссылки должны быть относительные, чтобы, если мы задеплоим приложение с applicationContext=topjava (`localhost:8080/topjava`) они не указывали на корень `localhost:8080`. Т.е. или `action="meals"` или `""`. +Второй правильнее, когда путь к сервету составной (например `localhost:8080/topjava/ajax/profile/meals`). У нас будет на эту тему видео и мы будем чинить относительные ссылки. + +## Занятие 2: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Библиотека vs Фреймворк. Стандартные библиотеки Apache Commons, Guava +- Apache Commons, Guava + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Слои приложения. Создание каркаса приложения. +- **3-app-layers.patch** + +> Про DTO еще можно будет посмотреть в видео Алименкова "Босиком по Граблям", когда мы добавим Hibernate + +- Паттерн "Слои приложения" +- Архитектурные + слои приложения в Spring +- Data Access Object +- Паттерны Repository и DAO +- Паттерн DTO +- Value Object и Data Transfer Object +- Should services always return DTOs, or can they also return domain models? + +Справочник: + - Забудьте о DAO, используйте Repository + - Difference between Active Record and DAO + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Обзор Spring Framework. Spring Context. +- **4-add-spring-context.patch** + + - Spring Framework + - Проекты Spring. + - Обзор Spring Framework + +- **5-add-dependency-injection.patch** + - Инверсия управления. + - IoC, DI, IoC-контейнер — Просто о простом + +- **6-add-annotation-processing.patch** + - Difference + between @Component, @Repository & @Service annotations in Spring + - Spring Auto Scanning Components + - Использование аннотации @Autowired + +- Справочник: + - DI/ Service Locator + - Introduction to the Spring IoC container + and beans + - Constructor против Setter Injection + - Getting Started + - Spring Framework Reference Documentation + - Spring на GitHub + - Spring Annotations + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Пояснения к HW2. Обработка Autowired + + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> какова цель деления приложения на слои? + +Управляемость проекта (особенно большого) повышается на порядок: +1. Обеспечивается меньшая связываемость. Допустим если мы меняем что то в контроллере, то сервис эти изменения не задевают. +2. Облегчается тестирование (мы будем тестировать слои сервисов и контроллеров отдельно) + +> DTO используются когда есть избыточность запросов, которую мы уменьшаем, собрав данные из разных бинов в один? Когда DTO необходимо использовать? + +(D)TO может быть как частью одного entiry (набор полей) так и набором нескольких entity. +В нашем проекте для данных, которые надо отдавать наружу и они отличаются от Entiy (хранимый бин), мы будем делаем (Data) Transfer Object и класть в отдельный пакет to. +У нас наружу будет отдаваться как UserMeals, так и UserMealsWithExceeded (для таблицы). Т.е. UserMealsWithExceeded у нас является Transfer Object и его надо пернести в пакет to, а для UserMeals мы отдельного TO создавать не будем. +На многих проектах (и собеседованиях) практикуют разделение на уровне maven модулей entity слоя от логики и соответствующей конвертацией ВСЕХ Entity в TO, даже если у них те же самые поля. +Хороший ответ когда TO объязательны есть на Stackoverflow: When to Use. + +> Что такое схема в spring-app.xml xsi:schemaLocation и зачем она нужна + +XML схема нужна для валидации xml, IDEA делает по ней атвозаполнение. + +> Почему контроллеры положили в папку web, а не в conrollers? + +Тоже самое что domain/model - просто разные названия. web думаю даже чаще называют (например в spring petclinic) + +> Что означает для Spring + + + + ? + +Можно сказать так: создай и занеси в свой контекст бин UserServiceImpl и заинжекть в его проперти из своего контекста бин mockUserRepository. + +> Как биндинг происходит для `@Autowired`? Как поступать, если у нас больше одной реализации `UserRepository`? + +`@Autowired` инжектит по типу (т.е. ижектит класс который наследует `UserRepository`). Обычно он один. Если у нас несколько реализаций, Spring не поднимится и поругается - No unique bean. В этом случае можно уточнить имя бина через @Qualifier. `@Qualifier` обычно добавляют только в случае нескольких реализаций. + +> Зачем мы наследуем NotFoundException от RuntimeException? + +Так с ним удобнее работать. И у нас нет никаких действий по восстановлению состояния приложения (no recoverable conditions): checked vs unchecked exception + +-------------------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW02 + + 1. Рефакторинг репозиториев: + - переименовать MockUserRepositoryImpl в InMemoryUserRepositoryImpl и имплементировать по аналогии с + InMemoryUserMealRepositoryImpl (список пользователей возвращать отсортированным по имени) + + - зарефакторить UserMealRepository/InMemoryUserMealRepositoryImpl: + - еда принадлежит пользователю + - список еды возвращать отсортированным по времени, последние записи наверху + - если еда отсутствует или чужая, возвращать null/false (см. UserRepository) + + 2. UserMealWithExceed переносим в пакет to (transfer objects), UserMeal extends BaseEntity + + 3. Сделать реализацию слоев приложения для функциональности "еда": + - API контроллера должна удовлетворять все потребности демо приложения и ничего лишнего + (см. http://topjava.herokuapp.com) + - после авторизации запрос попадает в контроллер, методы которого будут доступны снаружи по http, + т.е. в запросе может прийти id для еды, не принадлежащее авторизированному пользователю + (id авторизованного юзера попадает (сделаем позже) в LoggedUser.id(), см. ProfileRestController). + Нельзя позволять модифицировать/смотреть чужую еду: + - у еды есть УНИКАЛЬНОЕ id, по которому можно определить из базы, чья она; + - если еда не принадлежит авторизированному пользователю или отсутствует, то бросать NotFoundException. + + 4. Включить классы в контекст Spring и проверить из SpringMain вызов метода UserMealRestController + +Примечания: + + - LoggedUser известен только на слое web (UserMealService можно тестировать без подмены логики авторизации) + - Пока можно НЕ добавлять поля в UserMeal (добавим позже), + принимаем в методах сервиса и репозитория параметр userId: id владельца еды. + - В UserMealServiceImpl постараться сделать в каждом методе только одни запрос к UserMealRepository + - Не надо в названиях методов повторять названия класса, например Meal. + +Optional + + 1. В MealServlet сделать инициализацию Spring, достать UserMealRestController из контекста + и работать с едой через него (см. SpringMain. pom.xml НЕ менять, работаем со spring-context). + + 2. Добавить в mealList.jsp и MealServlet две отдельные фильтрации еды: по дате и по времени (см. демо) + + 3. Добавить выбор текущего залогиненного пользователя (имитация авторизации) + (например захардкодить 1,2 в index.html select и сделать LoggedUser.setId(userId) в UserServlet). + +--------------------- +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW02 (для проверки, сначала сделайте самостоятельно!) + +- `InMemoryUserRepositoryImpl.getByEmail` попробуйте сделать через `stream` +- `InMemoryUserRepositoryImpl.delete` попробуйте сделать за одно обращение к map +- UserMealRestController должен уметь обрабатывать запросы: + - Отдать свою еду (для отображения в таблице, формат `List`), запрос БЕЗ параметров + - Отдать свою еду, отфильтрованную по startDate, startTime, endDate, endTime + - Отдать/удалить свою еду по id, параметр запроса - id еды. + Если еда с этим id чужая или отсутствует - `NotFoundException` + - Сохранить/обновить еду, параметр запроса - UserMeal. Если обновляемая еда с этим id чужая или отсутствует - NotFoundException +- Т.к. контроллер позволяет управлять ТОЛЬКО своей едой, `userId` снаружи не приходит (см. `ProfileRestController`) +- Фильтрацию по датам правильнее опустить на уровень репозитория т.к. из базы будем брать сразу отфильтрованные по дням записи. Следите чтобы первый и последний день не были обрезаны, иначе сумма калорий будет неверная. +- Сервлет мы удалим, а контроллер останется, поэтому возвращать `List` надо из контроллера +- Закрывать springContext в сервлете грамотнее всего в `HttpServlet.destroy()`: если где-то в контексте Spring будет ленивая инициализация, метод-фабрика, не синглетон-scope, то контекст понадобится при работе приложения. У нас такого нет, но делать надо все грамотно. +- Проверьте корректную обработку пустых значений date и time в контроллере +- Если запрашивается список и он пустой, всегда возвращайте emptyList(). По нему можно легко итерироваться без риска NullPoinException. В java 8 появились вместо null есть возможность использовать Optional (на самом деле давно уже была в библиотеках типа Guava), они полезны для цепочек вычислений. Я сторонник применять их когда код становится проще и логичнее. diff --git a/doc/lesson03.md b/doc/lesson03.md new file mode 100644 index 000000000000..956f316a9a5f --- /dev/null +++ b/doc/lesson03.md @@ -0,0 +1,182 @@ +## Материалы 3-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW2 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW2 +> Изменения в проекте: + - В репозиториях по другому инстанциировал компараторы + - Пернес обработку пустых дат в `UserMealRestController.getBetween()` + - Зарефакторил `> TimeUtil.isBetween(T value, T start, T end)`. Дженерики означают, что мы принимаем экземпляры класса, который имплементит компаратор, + который умеет сравнивать T или суперклассы от T + +- **1-HW2-repository.patch** + +**Внимание: при удалении класса он остается скомпилированный у вас в target (и classpath). В этом случае (или вообще если непонятно почему проект глючит) сделаейт в maven clean.** + +- **2-HW2-meal-layers.patch** +- **3-HW2-optional-MealServlet.patch** +- **4-HW2-optional-filter.patch** +- **5-HW2-optional-select-user.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Вопросы по API и слоям приложения +- Should services always return DTOs, or can they also return domain models? +- Mapping Entity->DTO goes in which application layer: Controller or Service? + +## Занятие 3: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Коротко о жизненном цикле Spring контекста. +- **6-bean-life-cycle.patch** +- Spring изнутри. Этапы инициализации контекста. +- Ресурсы: + - Евгений Борисов. Spring, часть 1 + - Евгений Борисов. Spring, часть 2 + - Презентация Spring framework core + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Тестирование через JUnit. +> **ВНИМЕНИЕ: перед накаткой патча создайте каталог test (из корня проекта путь `\src\test`), иначе часть файлов попадет в `src\main`.** + +- **7-add-junit.patch** + +- Перенос mock реализации в test. +- JUnit 4 +- Тестирование в Java. JUnit + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Spring Test +- **8-add-spring-test.patch** +- Интеграция Spring и JUnit. +- Spring Testing + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Базы данных. Обзор NoSQL и Java persistence solution без ORM. +- PostgreSQL. +- NoSQL or RDBMS.Обзор NoSQL систем. CAP +- JDBC +- Обзор Java persistence solution без ORM: commons-dbutils, + Spring JdbcTemplate, + MyBatis, JDBI, jOOQ + +- Основы: + - Реляционная СУБД + - Реляционные базы + - Уроки по JDBC + - Postgres Guide + - PostgreSQL Tutorial + - Try SQL + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Настройка Database в IDEA. +- **9-add-postgresql.patch** +- Настройка Database в IDEA и запуск SQL. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Скрипты инициализации базы. Spring Jdbc Template. +- **10-populate-and-init-db.patch** + +- **11-impl-JdbcUserRepository.patch** +- Подключение Spring Jdbc. + +- Конфигурирование DataSource. Property Placeholder +- **Проверьте, что в контекст Spring проекта включены оба файла конфигурации**. +- Имплементация UserRepository через Spring Jdbc Template. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. Подготовка тестовых данных и тестирование UserService. +- **12-test-UserService.patch** +- Подготовка тестовых данных в UserServiceTest. Добавление TestUser и ModelMatcher +- Тестирование UserService. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 8. Логирование тестов. +- **13-test-logging.patch** + +- **14-fix-servlet.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 9. Ответы на Ваши вопросы +- Что такое REST? +- Зачем нужна сортировка еды? +- Можно ли обойтись без `MapSqlParameterSource`? +- Как происходит работа с DB в тестах? +- Как реализовывать RowMapper? +- Мои комментарии: решения проблем разработчиком. +- Нужен ли разработчику JavaScript? + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Какая разница между @BeforeClass and @Before? + +`@BeforeClass` выполняется один раз после загрузки класса (поэтому метод может быть только статический), `@Before` перед каждым тестом. Не ленитесь сходить в исходники по Ctrl+Enter и почитать javadoc. BTW: для чистоты экземпляр тестового класса пересоздается перед каждым тестом: http://stackoverflow.com/questions/6094081/junit-using-constructor-instead-of-before + +> Тесты в классе в каком-то определенном порядке выполняются ("сверху вниз" например)? + +Порядок по умолчанию неопределен, каждый тест должен быть автономен и не зависеть от других. См. также http://stackoverflow.com/questions/3693626/how-to-run-test-methods-in-specific-order-in-junit4 + +> Объязательно ли разворачивать postgreSQL? + +Желательно: хорошая и надежная DB:) Если совсем не хочется - можно работать со своей любимой RDBMS (поправить `initDB.sql`) или работать c heroku (креденшелы к нему есть в `postgres.properties`). На следующем уроке добавим HSQLDB, она не тербует установки. + +> Зачем начали индексацию с 100000? + +Тут уже нет "как принято". Так удобно вставлять в базу (если будет потребность) записи не мешая счетчику. + +> Разве не должно быть у каждой роли примари кей ID и форин кей user_id ? + +Отношение user <-> roles многие ко многим, делается это через таблицу-связку USER_ROLES. Но отдельной таблицы ROLES у нас нет, мы храним значение роли прямо в таблице-связке USER_ROLES. Есть еще варианты использовать тип enum у Postgres или хранить там INTEGER Roles.ordinal(). При этом нельзя будет менять порядок в enum Role и добавлять новые роли можно будет только в конец: http://stackoverflow.com/questions/6789342/jpa-enum-ordinal-vs-string + +> Из 5-го видео - "Логика в базе - большое зло". Можно чуть поподробней об этом? + +- Есть успешные проекты с логикой в базе. Те все относительно. +- Логика в базе - это процедуры и триггеры. Нет никакого ООП, переиспользовать код достаточно сложно, никагого рефакторинга, поиска по коду и других плюшек IDE. Нельзя делать всякие вещи типа кэширования, хранения в сесии - это все для логики на стороне java. Например json можно напрямую отдать в процедуру и там парсить и вставлять в таблицы или наоборот - собирать из таблиц и возвращать. +А затем потребуется некоторая логика на стороне приложения и все равно придется этот json дополнительно разпарсивать в java. +Я на таком проекте делал сепециальную миграцию, чтобы процедуры мигрировать не как sql скрипты, а каждую процедуру хранить как класс с историей изменений. Если логика: триггеры и простые процедуры записи-чтения, которые не требуют логики и периспользования кода или +небольшой проект - это допустимо, иначе проект становится очень трудно поддерживать + +> У JUnit есть ассерты и у спринга тоже. Можно ли обойтись без JUnit? + +Предусловия и JUnit-тесты совершенно разные вещи. Один другого не заменит. + +> Я так понял VARCHAR быстрее, чем TEXT, когда мы работаем с небольшими записями. Наши записи будут небольшими (255). Почему вы приняли решение перейти на TEXT? + +В отличие от MySql в Postgres VARCHAR и TEXT - тоже самое: http://stackoverflow.com/questions/4848964/postgresql-difference-between-text-and-varchar-character-varying + +> Зачем при создании таблицы мы создаем `CREATE UNIQUE INDEX` и `CREATE INDEX`. При каких запросах он будет использоваться? + +UNIQUE индекс нужен для обеcпечения уникальности, DB не даст сделать одинаковый индекс. Индексы используется для скорости выполнения запросов. Обычно они задействуются, когда в запросе есть условия, на которые сделан индекс. Узнать по конкретному запросу можно запросив план запроса: см. Оптимизация запросов. Основы EXPLAIN в PostgreSQL + +> А это нормально, что у нас в базе у meals есть userId, а в классе - нет? + +Что значит - "норамльно"? приложение работает. Ненормально, когда в приложении есть "лишний" код, который не используется. Для ORM он нам понадобится- мы ссылку на User добавим. + +> Почему мы использует 1 БД sequence на разные сущности? + +Мы будем использовать Hibernate, по умолчанию он делает глобальный sequence на все таблицы. В этом подходе есть как плюсы, так и минусы, из плюсов - удобно делать ссылки в коде и в таблицах на при наследовании и мапы в коде. В дополнение: Configure Hibernate to create separate sequence for each table by default. + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW03 +``` + - Понять, почему перестали работать SpringMain/ + InMemoryAdminRestControllerTest/ InMemoryAdminRestControllerSpringTest + - Дополнить скрипты создания и инициализации базы таблицой MEALS. Запустить скрипты на вашу базу (через Run) + - Реализовать через Spring JDBC Template JdbcUserMealRepositoryImpl + - сделать каждый метод за один SQL запрос + - userId в класс UserMeal вставлять НЕ надо + (для UI и REST это лишние данные, userId это id залогиненного пользователя) + - JbdcTemplate работает через сеттеры. Нужно их добавить в UserMeal + - Cписок еды должен быть отсортирован (тогда мы его сможем сравнивать с тестовыми данными). + Кроме того это требуется для UI и API: последняя еда наверху. + - postgres драйвер не понимает LocalDateTime, использовать преобразования: + Timestamp.valueOf(ldt) / timestamp.toLocalDateTime() + - Преобразовывать Timestamp -> LocalDateTime можно в геттерах/сеттерах UserMeal, + но лучше сделать свой RowMapper + - Проверить работу MealServelt, запустив приложение +``` +Optional + + - Сделать тестовые данные `MealTestData`, АНОЛОГИЧНЫЕ пропопулированным в populateDB.sql. + Тестовый класс-обертка к `UserMeal` не требуется, сравниваем данные через готовый MATCHER (toString) + - Сделать `UserMealServiceTest` из `UserMealService` (Ctrl+Shift+T и выбрать JUnit4) и реализовать тесты. + - Сделаеть тесты на чужих юзеров (delete, get, update) с тем чтобы получить `NotFoundException` + - Предложить решение, как почнинить SpringMain/ InMemory*Test + - Сделайте индекс к таблице Meals. + +- Postgres and Indexes on Foreign Keys and Primary Keys +- Postgres Guide: Indexes + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Ошибки в HW3: +- UserMealRowMapper многие делают отдельным классом. Зачем? + Изучаем вложенные,внутренние, локальные и анонимные классы. Наконец, делаем маппер как константу через лямду. + +- Проверте, что вернется при update чужой еды. +- `InMemory*Test` предполагает использовать нашу реализацию репозитория в памяти +- Порядок таблиц при DROP и DELETE важен, если они связаны fk. Проверьте что у ваз в ДЗ работает diff --git a/doc/lesson04.md b/doc/lesson04.md new file mode 100644 index 000000000000..51b8c51948f3 --- /dev/null +++ b/doc/lesson04.md @@ -0,0 +1,153 @@ +## Материалы 4-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW3 +> SpringMain/ InMemory*Test починим в видео 3, `5-create-mock-test-ctx.patch` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) JdbcUserMealRepositoryImpl + UserMealServiceTest +- POSTGRESQL: BETWEEN CONDITION +- **1-HW3.patch** + +> Новый Postgres драйвер поддерживает Java 8 Date and Time. Преобразования c Timestamp оказались уже не нужны. Мне кажется хороший пример того, что все надо проверять самим, а не верить на слово:) + +- **2-HW3-optional.patch** + +> В JUnit тестах используется кодировка по умолчанию и тесты, если их запускать через maven, не проходят. Решается кастомизацией плагина maven-surefire-plugin + +> В meals добавил составной индекс `INDEX meals_unique_user_datetime_idx ON meals(user_id, date_time)`: + - все запросы в таблицу meals у нас идут с `user_id` + - по полю `date_time` также есть запросы + мы по нему сортируем список результатов + те они- хорошие кандидаты для индексирования. На id как на primary key индекс создается автоматически. + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Вопросы по HW3 + +> Почему в meals дата и каллории у нас не NOT NULL? Это ведь обязательные для заполнения поля. + +Спасибо, недосмотр. Сделаем NOT NULL в HW04. + + +## Занятие 4: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Методы улучшения качества кода +- **3-improve-code.patch** +- Контрактное программирование, Программирование по контракту +- Comparison Preconditions in Java +- QAPlug vs FindBugs +- QAPlug tutorials +- Codacy Home + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Spring: инициализация и популирование DB +- **4-init-and-populate-db.patch** +> - `@Sql` в тестах заменяет `@Before public void setUp()`, те выполняется перед каждым тестом + +- Инициализация базы при старте приложения +- Spring Testing Annotations +- Абстракция Resource + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Починка SpringMain/ InMemory*Test: подмена контекста при тестировании +- **5-create-mock-test-ctx.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. ORM. Hibernate. JPA. +> ВНИМАНИЕ: патч меняет postgres.properties, в котором у вас свои креденшелы к базе. + +> Тесты и приложение ломаются. `UserMealServiceTest` починится после выполнения HW04 (`JpaUserMealRepositoryImpl`) + +- **6-add-jpa.patch** +> Upgrade Hibernate to 5.0.4 needed to add `javax.transaction.jta`, see 5.0.4 requires javax.transaction.SystemException +> Upgrade Hibernate Validator needed to add `javax.el-api`, see javax.validation.ValidationException + +- ORM. +- JPA и Hibernate в вопросах и ответах +- Hibernate: введение и написания Hello world приложения +- Mapping. Описания модели Hibernate (hbm.xml/annotation) +- Hibernate. Другие ORM: TopLink, ElipseLink, EBean (used in Playframework). +- JPA (wiki). JPA (english wiki). JPA Performance Benchmark +- Подключение к проекту Spring ORM и Hibernate +- Отображения наследования объектов на таблицы +- Стратегии генерации PK +- Добавление hibernate-validator. JSR-303 -> JSR-349 +- Описание связей в модели. Ленивая загрузка объекта. +- Конфигурирование JPA. Сканировние Entities. JPA definitions +- Spring expressions: выражения в конфигурации +- Создание JPA Facet. Назначение DataSource. +- Имплементация JpaUserRepository через EntityManagerFactory/ SessionFactory +- Использование TypedQuery и @NamedQuery. Назначение параметров по индексу и имени. +- HQL/ JPQL. +- JPA Criteria API. Unified Queries for Java +- Java 8 Time API Converter + +- **7-add-named-query-and-transaction.patch** +- Транзакция. ACID. Уровни изоляции транзакций. +- Подключаем транзакции. Spring Transaction Management +- readOnly и Propagation.SUPPORTS +- @Transactional в тестах. Настройка EntityManagerFactory + +- Справочник: + - Николай Алименков — Босиком по граблям Hibernate + - Spring transaction propagation tutorial + - Getting Started with JPA + - Validate by RegExp + - Java Persistence + - Hibernate + - Spring Framework transaction management + - Working with JPA Entity Objects + - Стратегии работы с транзакциями: Распространенные ошибки + - Принципы работы СУБД. MVCC + - MVCC + + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Добавляем поддержку HSQLDB + +- **8-add-hsqldb.patch** + +> ВНИМАНИЕ: патч меняет postgres.properties + +> IDEA может `${jdbc.initLocation}` подчеркивать красным - иногда тупит... + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> Есть несколько аналогичных "встроенных" баз данных. H2, HSQLDB, Derby, SQLite. Почему был выбран HSQLDB? + +Просто с ней приходилось работать. HSQLDB и H2 наиболее популярны, в новом курсе по spring-boot планирую использовать H2. + +> Чистого JPA не существует, т.е. это всего лишь интерфейс, спецификация? Говорим JPA, подразумеваем какой-то ORM фрэймворк? А что тогда используют чистый jdbc, Spring-jdbc, MyBatis? MyBatis не реализует JPA? + +JPA- это спецификация, API. Ее реализации- Hibernate, OpenJPA, EclipceLink. Spring-Jdbc, MyBatis, db-utils не реализуют JPA, это обертки к JDBC. Все ORM также реализованы поверх JDBC. + +> В зависимостях maven `hibernate-entitymanager` тянет за собой `jboss-logging`. Как будет происходить логгирование? + +How do you configure logging in Hibernate 4 to use SLF4J: в нашем проекте автоматически подхватывается `logback-classic`. + +-------------------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW04 + + Сделать UserMeal как Hibernate entity + Добавить поддержку LocalDateTime + Имплементировать и протестировать JpaUserMealRepositoryImpl + +- IDEA не понимает в @NamedQuery `.. m.dateTime BETWEEN ..`. На функциональность это не влияет. +- JPA support for Java 8 new date and time API +- What's new in Hibernate 5? + +Optional + + Добавить в тесты UserMealServiceTest функциональность @Rule: + - проверку Exception + - вывод в лог времени выполнения каждого теста + +- JUnit @Rules + +--------------------- +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW04 + +- Тк. JPQL работает с объектами мы не можем использовать userId для сохранения. Можно сделать например так: + + User ref = em.getReference(User.class, userId); + meal.setUser(ref); + + При этом от User нам нужет только id, т.е. реального запроса в базу за юзером не будет- проверьте по логам Hibernate. + +- В JPQL запросах можно писать: `m.user.id=:userId` + +- При реализации `JpaUserMealRepositoryImpl` предпочтительно не использовать try-catch в логике реализации. Но если очень хочется, то ловить только специфичекские эксепшены (пр. `NoResultException`), чтобы, например, при отсутствии коннекта к базе приложение отвечало адекватно. + +- Мы будем смотреть генерацию db скриптов из модели, для корректоной генерации нужно в `UserMeal` добавить `uniqueConstraints` diff --git a/doc/lesson05.md b/doc/lesson05.md new file mode 100644 index 000000000000..851f9df18853 --- /dev/null +++ b/doc/lesson05.md @@ -0,0 +1,124 @@ +## Материалы 5-го урока + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Правки в проекте +- **0-fix.patch** + +> Новый драйвер Postgres работает с Java 8 Time API, но не понимает год 0. + +> Поправили в создании meals поля NOT NULL + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW4 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Разбор вопросов +- Validate by RegExp +- Working with JPA Entity Objects +- Java Persistence/Relationships +- Использование ThreadLocal переменных +- Merge vs Persist +- Видео: работа в ZK с OpenJPA (в чем Hibernate хуже) +- Паттерн- открытие транзакции в фильтре и почему это bad-practice +- Sequence Strategies +- SequenceGenerator/IdentityGenerator in PostgreSql + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW4: JPA. @Rule +- **1-HW4.patch** +- **2-HW4-optional.patch** + +Изменения в проекте: + +- Add support for Java 8 date and time types (JSR-310) +- Stopwatch + +## Занятие 5: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Транзакции +- wiki Транзакция +- readOnly и Propagation.SUPPORTS +- Ресурсы: + - How does Spring @Transactional Really Work + - Стратегии работы с транзакциями: Распространенные ошибки + - Spring @Transactional - isolation, propagation + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Профили Maven и Spring +- **3-profiles-connection-pool.patch** + > Для переключения на HSQLDB необходимо: + > - поменять в окне Maven Projects профиль (Profiles) на `hsqldb` и сделать `Reimport All Maven Projects` (1я кнопка) + > - поменять в UserServiceTest/UserMealServiceTest `@ActiveProfiles(Profiles.HSQLDB)` + +- Using Spring Profiles in XML Config +> Галочка в XML профиле влияет только на отображение в Spring и никак на выполнение кода. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Пул коннектов + +- Выбор реализации пула коннектов: BoneCP, Commons Database Connection Pooling, HikariCP +- Tomcat pool + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Spring Data JPA +- **4-spring-data-jpa.patch** + +- Spring Data JPA +- Замена AbstractDAO: JPA Repositories +- Разрешение зависимостей: Maven BOM [Bill Of Materials] Dependency +- Делегирование. (Spring Data JPA) +- Query methods +- Spring Data – новый взгляд на persistence (JeeConf) + +- Ресурсы: + - Github repositories + - Spring Data JPA Tutorial + - Spring Data JPA with QueryDSL + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Spring кэш +- **5-spring-cache** + +- Кеширование в Spring Framework +- EHCACHE +- В случае проблем с резмещением кэш в `java.io.tmpdir`: http://stackoverflow.com/questions/1924136/environment-variable-to-control-java-io-tmpdir + +- Ресурсы: + - Spring cache Abstraction + - Распределённая система кеша ehcache + - Починка JUnit: один кэш на JVM + +-------------------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Домашнее задание HW05 + Имплементировать DataJpaUserMealRepositoryImpl + Разделить реализации Repository по профилям Spring: jdbc, jpa, datajpa + (общее в профилях можно объединять, например ) + Сделать тесты всех реализаций (jdbc, jpa, datajpa) через наследование (без дублирования), + общее из UserMealServiceTest и UserServiceTest вынеси в базовый класс. + Запустить все тесты: mvn test + или в IDEA Maven Lifecycle- test (кнопку skipTest отжать) + +Optional + + Починить MealServlet / SpringMain (добавить профили. + Попробуйте поднять Spring контекст без использования `spring.profiles.activ`). + + Сделать и протестировать в сервисах методы (можно сделать разные варианты, реализация только в DataJpa): + - достать по id пользователя вместе с его едой + - достать по id еду вместе с пользователем + Обращения к DB сделать в одной транзакции + + Почините JDBC реализацию для HSQLDB, можно также разнести ее по профилям. + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> В spring-petclinic DataJpa реализована без проксирования и без доп. классов. В таком виде как у них, spring data смотрится, конечно, намного лаконичней других реализаций, но у нас получилось вдвое больше кода, чем с тем же jpa или jdbc. Плюс только пожалуй в том, что query находятся прямо в репозитории, а не где-то там в другом пакете. Так что получается, spring data лучше подходит для простейших crud без всяких "фишек"? или в чем его достоинство для больших и сложных проектов? + +Достоинство DATA-JPA по сравнению например с JPA: есть типизация, готовые реализации типовых методов CRUD а также paging, data-common. Мы можем переключить реализацию JPA, например, на mongoDb (PagingAndSortingRepository, от которого наследуется JpaRepository находится в `spring-data-common`). +Соответственно его методы будет поддерживаться всеми реализациями spring-data-common, JPA одна из них и пр. Подробнее о них есть в видео Spring Data – новый взгляд на persistence. +Я сам ввел дополнительное проксирование для устранения минусов подхода DATA-JPA: невозможность дебага и привязка к интерфейсу JpaRepository. +Приложение становится больше, но выигрыш этого стоит. Можно не делать дополнительный класс и перенести его логику в слой сервиса. + +> Почему мы для InMemory не сдалели отдельного профиля? Почему их не удалить вообще? + +Реализация InMemory является примером как в test делать моки с подменой контекста. Для них сделали отдельный `mock.xml` и запускаемый проект ничего не должен о них знать. У нас учебный проект, в котором 4 реализации репозиториев, в реальном такого не будет. + +--------------------- +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW05 +- Для того, чтобы не запускались родительские классы тестов нужно сделать их `abstract` +- Для IDEA не забудте выставить в `spring-db.xml` справа вверху в Change Profiles... профили, например `datajpa, postgres` +- Общие части для всех в `spring-db.xml` можно оставить как есть без профилей, но до первого `Материалы 6-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW5 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW5: Spring Profiles. Spring Data JPA +- **01-HW5-data-jpa.patch** +- **02-HW5-profiles.patch** +> Для IDEA не забудте выставить Spring Profiles в `spring-db.xml`: нарпимер `datajpa, postgres` + +- **3-HW5-tests.patch** +> `DbTest` переименован в `AbstractServiceTest` и сюда перенесли `@ActiveProfiles(Profiles.POSTGRES)` + +> Т.к. HSQLDB не понимает LocalDateTime, разделил `JdbcUserMealRepositoryImpl` на `Java8JdbcUserMealRepositoryImpl` и `TimestampJdbcUserMealRepositoryImpl` +- Spring 4 Conditional. Зайдите в исходники @Profile и посмотрите его реализацию. + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW5: Optional +- **4-HW5-Optional-fix-servlet-main.patch** +- Spring Profiles +> Добавлены константы `Profiles.ACTIVE_DB`, `Profiles.DB_IMPLEMENTATION` + +- **5-HW5-Optional-fetch-join.patch** +- JPA JoinColumn vs mappedBy +- Unidirectional OneToMany +> Добавлены проверки и тесты на `NotFound` для `UserMealService.getWithUser` и `UserService.getWithMeals` + + +## Занятие 6: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Кэш Hibernate +- **6-hibernate-cache.patch** +> JdbcUserServiceTest поломася. Требуется починить в HW6 + +- Уровни кэширования Hibernate +- Hibernate Cache. Практика +- Hibernate - Caching +- Починка тестов: инвалидация кэша Hibernate +- Ресурсы: + - Подключение кэша Hibernate 2-го уровня + - JPA2 @Cacheable vs Hibernate @Cache + - Распределённая система кеша ehcache + - Cache mappings + - How does Hibernate Query Cache work + - Pitfalls of the Hibernate Second-Level / Query Caches + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Spring Web +- **7-spring-web.patch** + +- Добавляем в проект веб зависимости +- Поднятие контекста Spring в веб приложении. ServletContextListener. Задание дефолтного профиля. +- Получение контекста Spring в веб-контейнере +- Servlet Lifecycle + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. JPS, JSTL, internationalization +- **8-jsp-jstl-i18n.patch** +- jsp:include +- Имплементируем UserController. Паттерн MVC + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Динамическое изменение профиля при запуске. +> -Dspring.profiles.active="datajpa,postgres" + +- Set profiles in Spring 3.1 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Конфигурирование Tomcat через maven plugin. Jndi-lookup. +> `javax.el-api` scope изменил на `provided` (`el-api.jar` является частью Tomcat) + +Запуск из коммандной строки (`clean` нужен для удаления из сборки зависимости `javax.el-api`): + + mvn clean org.apache.tomcat.maven:tomcat7-maven-plugin:run + +- **9-tomcat-pool-jndi.patch** + +- Tomcat Maven Plugin +- Tomcat JNDI Resources + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Spring Web MVC +- **10-spring-webmvc.patch** + +- Spring Web MVC +- Паттерн Front Controller +- Добавляем DispatcherServlet и MVC application context. +- Иерархия контекстов в Spring Web MVC +- Сценарий обработки запроса. HandlerMappings +- View resolving: прячем jsp под WEB-INF. +- HandlerMapping: SimpleUrlHandlerMapping, BeanNameUrlHandlerMapping +- Маппинг ресурсов. +- Ресурсы: + - Spring MVC Tutorial + - Spring MVC hello world + - Web MVC framework + - Special bean types in the WebApplicationContext + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. Spring Internationalization +- **11-spring-i18n.patch** + +> Убедитесь что в настройках IDEA кодировка везде UTF-8 + +> Проверьте, что файлы локализации у вас в UTF-8 (в IDEA справа внизу в статусе есть кодировка и можно перекодировать). + +- Spring Internationalization or i18n +- Reloadable MessageSources +- nginx: Serving Static Content + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW06 + - Починить InMemory и Jdbc тесты + + - Добавить локализацию и jsp:include в meal*.jsp + + Починить работу meals: перенести функциональность MealServlet в контроллеры, + разнести запросы на update/delete/.. по разным методам + (можно делать по аналогии с ru.javawebinar.topjava.web.RootController#setUser, + аннотации на параметры и адаптеры для LocalDate\Time мы введем позже). + +Optional + + Добавить еще одну роль к админу (у него будет 2 роли: ROLE_USER, ROLE_ADMIN), + добавить проверку ролей в тесты на User, починить тесты Jpa (дублирование) + + Починить тесты на роли Jdbc: + добавить транзакционность (DataSourceTransactionManager) и доставание ролей в JdbcUserRepositoryImpl + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Кэш hibernate надстраивается над ehcache или он живет самостоятельно? + +- Understanding Hibernate Caching: +Hibernate supports following open-source cache implementations out-of-the-box: EHCache (Easy Hibernate Cache), OSCache (Open Symphony Cache), Swarm Cache, and JBoss Tree Cache. + +> Где конфигурится интернализация для jstl? Т.е. файл, где здаются app, app_ru.properties. Достаточно указать в страницах бандл и путь в ресурсы? + +`` означает что ресурсы будут искаться в `classpath:messages\app(_xx)/properties`: +Tag setBundle: fully-qualified resource name, which has the same form as a fully-qualified class name. +После сборки проекта maven их можно найти в target\classes или target\topjava\WEB-INF\classes. + + +> Отлично, что она все пишет на том языке, который пришел в хидере запроса. А если я хочу выбрать? + +Используется JSTL Format Tag fmt:setLocale. Мы переключимся на Spring i18n и переключение локалей будет реализовано в нем. + +> Мы создаем бин, где получаем dataSource по имени ``. +Но там не указан класс, как в других dataSource? Получается по имени jdbc/topjava нам уже отдает готовый обьект dataSource и мы как бы помещаем его в бин? + +Здесь используется namespace `jee:jndi-lookup`, который прячет под собой классы реализации. Сравните в http://habrahabr.ru/post/232381/ раздел Spring namespace configuration инициализацию базы через namespace jdbc и через классы. + +> В плагине прописан профиль `tomcat,datajpa`, а в web.xml `postgres,datajpa`. +Какой же реально отрабатывает? + +См видео урока "Динамическое изменение профиля при запуске". В плагине мы задаем параметры JVM запуска Tomcat + +> Почему мы не используем элемент `` в `spring-db.xml`? + +В проекте у нас сейчас 2 Spring контекста: `spring-mvc.xml (см. web.xml, DispatcherServlet)` и родительский `spring/spring-app.xml + spring/spring-db.xml (web.xml, contextConfigLocation)`. +Грубо: 2 мапы, причем для mvc доступно все что есть в родителе, но родитель не знает об mvc. Те spring-db.xml не является отдельным самоcтоятельным контекстом и достаточно того, что `` у нас есть в `spring-app.xml`. + +> В _ehcache.xml_ чем _ getAll()` в `UserServiceImpl`. +_ru.javawebinar.topjava.model.User_ - имя региона, которое использует Hibernate для кэширования содержимого таблицы _USERS_. +Мы можем оставить настройки по умолчанию, либо задать свои. + +> A `@NamedQuery` или `@Query` подвержены кешу запросов? Т.е. если мы поставим _USE_QUERY_CACHE_value="true" будет Hibernate их кешировать? + +Чтобы запрос кэшировался, кроме true в конфигурации нужно еще явно выставить запросу _setCacheable_ (http://vladmihalcea.com/2015/06/08/how-does-hibernate-query-cache-work/). По поводу кэширования @NamedQuery нашел _@QueryHint_: https://docs.jboss.org/jbossas/docs/Clustering_Guide/5/html/ch04s02s03.html + +> Почему messages мы кладем в config и используем system environment? разве так делают в реальном проекте? не будешь же вписывать на сервере эти переменные каждый раз, если проект куда-то будет переезжать. Можно по другому, кроме systemEnvironment['TOPJAVA_ROOT'] задать путь от корня проекта? + +1. messages нам нужны в runtime (при работе приложения). Проект к собранному и задеплоенному в Tomcat war отношения никакого уже не имеет и на этом сервере он обычно не находится. Если ресурсы нужны только при сборке и тестировании, то путь к корню для одномодульного maven проекта можно задать как ${project.basedir}, но для многомодульного проекта (а все реальные проекты многомодульные) это путь к корню своего модуля. +2. В "реальном приложении" делается совершенно по разному: + - нести с собой в класспасе, но ресурсы нельзя будет динамически (без передеплоя) обновлять + - класть в war (не в класспас) и обновлять в развернутом TOMCAT_HOME/webapps/[appname]/... + - класть в зафиксированное определенное место (нарпимер в home: ~ или в путь от корня /app/congig). Можно задавать фиксированный пусть в пропертях профиля maven и фильтровать ресурсы (maven resources), чтобы они попали в проперти проекта. + - делать через переменную окуржения, как у нас + - задавать в параметрах запуска JVM как системную переменную через -D.. + - располагать в преференсах (для unix это home, для windows- registry): http://java-course.ru/articles/preferences-api/ + - держать настройки в DB + + Часто в одном приложении используют несколько способов для разных видов конфигураций. + +--------------------- +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW06 +- Неверная кодировка UTF-8 с Spring обычно решается фильтром `CharacterEncodingFilter`: +``` + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + +``` +- Функциональность еды в контроллерах должна быть в пакете meal и сделайте как для User - наследование от абстрактного класса. +- Если неправильно формируется url относительно контекста приложения посмотрите http://stackoverflow.com/questions/4764405/how-to-use-relative-paths-without-including-the-context-root-name +- JdbcUserRepositoryImpl.update - роли также должны обновлялться +- JdbcUserRepositoryImpl.getAll - постараться реализовать так, чтобы для 1000 пользователей был меньше, чем 1001 запрос (1 или 2). +- Для контроллера action уже не нужен - запросы нужно подкорректировать diff --git a/doc/lesson07.md b/doc/lesson07.md new file mode 100644 index 000000000000..8fdc15144876 --- /dev/null +++ b/doc/lesson07.md @@ -0,0 +1,134 @@ +## Материалы 7-го урока + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Небольшие правки от Codacy +- **0-codacy-fix.patch** + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW6 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW6 +- **01-HW6-fix-tests.patch** +- **02-HW6-jsp-i18n.patch** +- Root path in JSP + +- **03-HW6-meals.patch** +- **04-HW6-fix-relative-url-utf8.patch** +- Relative paths in JSP +- Spring redirect: prefix + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW6 Optional +- **05-HW6-optional-add-role.patch** +- **06-HW6-optional-jdbc.patch** +- Локальные классы + +## Занятие 7: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Auto generate DDL. +- **07-generate-ddl.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Тестирование Spring MVC +- **08-controller-test.patch** + +> В MockMvc добавился CharacterEncodingFilter + +> Закомментировали генерацию DDL + +- Hamcrest +- Unit Testing of Spring MVC Controllers + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. REST контроллеры +- **09-rest-controller.patch** + +- JSON (JavaScript Object Notation +- Understanding REST +- JAX-RS vs Spring MVC +- Request mapping +- Ресурсы: + - RESTful API для сервера – делаем правильно (Часть 1) + - RESTful API для сервера – делаем правильно (Часть 2) + - И. Головач. RestAPI + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Тестирование REST контроллеров. Jackson. +- **10-rest-test-jackson.patch** + +- http://stackoverflow.com/questions/34721851/spring-4-2-3-and-fasterxml-jackson-2-7-0-are-incompatible +- https://github.com/FasterXML/jackson-databind + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. jackson-datatype-hibernate. Тестирование через матчеры. +> Устройство матчеров может показаться сложным. Как это работает можно разбираться, только если самому на их основе что-то имплементировать. Мы используем Spring MVC и Hibernate не заботясь о том, насколько они сложные в реализации. Использование матчеров также достаточно просто. + +- **11-jackson-hibernate.patch** + +- Jackson JSON Serializer & Deserializer +- Сериализации hibernate lazy-loading с помощью jackson-datatype-hibernate + +- **12-jackson-jsr310.patch** +> Переводим сериализацию-десеарелизацию LocalDateTime на jsr310 и стандартный формат ISO-8601 +- jackson-datatype-jsr310 + +- **13-test-with-matcher.patch** +- Тестируем Spring Rest контроллеры: проверка JSON-содержимого ответа через собственный ResultMatcher + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Тестирование через SoapUi. UTF-8 +- **14-soapui-utf8-converter.patch** +- Решение проблемы с UTF-8 в StringHttpMessageConverter +- SoapUi + +> Импортировать проект в SoapUi из config\Topjava-soapui-project.xml + +> Response смотреть в формате JSON. + +- Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text +- ResponseBody and UTF-8 + +- **15-test-fix.patch** +> Поменял `artifactId` на `jackson-datatype-hibernate5` (был `hibernate4`) + +> При запуске из Maven проваливались тесты на `AdminRestControllerTest`: тесты сервиса оставляли после себя базу и кэш не в исходном состоянии. +Сделал сброс кэша и инициализацию базы не до, а после тестов. + +> Убрал лишний `import` + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW07 + + Добавить тесты контроллеров: + UserMealControllerTest для /meals по аналогии с UserControllerTest + ResourceControllerTest для style.css (status и ContentType) + + Реализовать UserMealRestController и протестировать его через UserMealRestControllerTest + + В параметрах getBetween принимать LocalDateTime (конвертировать через Spring, см. ссылку ниже), + а передавать в тестах в формате ISO_LOCAL_DATE_TIME (например '2011-12-03T10:15:30'). + Следите чтобы url в тестах совпадал с параметрами в методе контроллера. + Можно добавить логирование для проверки маршрутизации. + +- @DATETIMEFORMAT WITH JAVA 8 DATE-TIME API + +Optional + + Заменить @DATETIMEFORMAT на свой LocalDateTime конвертор или форматтер. + + Протестировать UserMealRestController через SoapUi или curl. + +- Introduction to Spring Converters and Formatters +- Difference between Spring MVC formatters and converters +- Написание HTTP-запросов с помощью Curl +(для Windows можно использовать Git Bash) + + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> После патча 7_13 тест ProfileRestControllerTest.testGet нам оставляет поле meals:null хотя мы задали чтоб не сериализовались null поля. + Почему его оставляет сериализатор? (конечно если выставим NON_EMPTY то уберет) + +Там lazy коллекция, а не null. Думаю, что Hibernate4Module уже после логики NOT_NULL (JacksonObjectMapper) ее обнуляет. + +> В реализации Jdbc нет @Transactional. Но мы же отдельно сохраняем user и его роли в разные таблицы. Может случиться, что юзер сохраниться, а роли нет? Или наоборот? + +Просто результат этих операций не атомарный. Если случится, что между этими двумя этими записями например вклинится удаление юзера, то сохранение ролей провалится. Те очень редкая ситуация. + +> При выполнении тестов через MockMvc никаких изменений на базе не видно, почему оно не сохраняет? + +У нас в тестах контроллеров стоит над классом @Transactional - это означает, что тесты идут в транзакции и после каждого теста JUnit делает rollback базы. + +--------------------- +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW07 +- для тестирования в UserMealRestController списка UserMealWithExceeded сделайте ModelMatcher<UserMealWithExceed, String> +- Ошибка в тесте _Invalid read array from JSON_ обычно расшифровывается немного ниже: читайте внимательно. diff --git a/doc/lesson08.md b/doc/lesson08.md new file mode 100644 index 000000000000..45624e45066c --- /dev/null +++ b/doc/lesson08.md @@ -0,0 +1,148 @@ +## Материалы 8-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW7 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW7 +- **1-HW07-controller-test** +- **2-HW07-rest-controller.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW7_ Optional +- **3-HW07-formatter.patch** +- **4-HW07-soapui-curl.patch** +- IDEA: Tools->Test RESTful Web Service +- Postman + +## Занятие 8: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. WebJars. jQuery and JavaScript frameworks +- **5-webjars.patch** + +> УБРАЛ из проекта Dandelion обертку к datatables: +> - не встречал нигде, кроме Spring Pet Clinic; +> - привязан к старому (legacy) API datatables; +> - поддержка работы с datatables через Dandelion оказалось гораздо более трудоемкое, +> - чем работа с плагином напрямую. + +- Подключение веб ресурсов. WebJars. +- Introducing WebJars +- Document Object Model (DOM) +- What is the DOM? +- jQuery +- Is jquery a javascript library or framework +- DataTables +- Working with jQuery DataTables + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Bootstrap +- **6-bootstrap.patch** +> В патч картинки не берутся, положить вручную icon-meal.png в `\src\main\webapp\resources\images\icon-meal.png` + +- Подключаем Bootstrap. Форматируем JSP. +- Twitter Bootstrap Tutorial +- Bootstrap 3 Tutorial +- Видео: уроки по Bootstrap 3 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. AJAX. Datatables. jQuery +- **7-ajax-datatables.patch** +> JSP полезны, если надо с сервера отдать статический html с серверной логикой (условия, циклы), сформированный на основе модели. +Для динамической отрисовки таблицы мы будем использовать REST и JSON на 9м уроке (работа с datatables через Ajax). + +> HW8 Optional - перейти на новый dataTables API + +- AJAX. +- Событие $(document).ready. +- jQuery для всех. +- jQuery для начинающих. AJAX. +- jQuery для начинающих. Селекторы. +- jQuery API +- Редактирование таблицы на основе модального окна Bootstrap. +- Javascript плагины для Bootstrap +- DataTables API + + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Notifications +- **8-notification.patch** +- Обработка ajaxError. +- jQuery notification + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Добавление к REST контроллерам Spring Security +> Правка к видео: путь в intercept-url должен быть полный - pattern="/rest/admin/**" + +> В Spring Security 4.x по умолчанию включен csrf. Будем его проходить на 10м занятии. + +- **9-add-security.patch** + +- Spring Security + +- Протокол AAA +- Методы аутентификации. +- Basic access authentication + +- Использование ThreadLocal + +- Добавляем в проект spring-security и security filter +- Конфигурируем security context для ресурсов и REST +- Тестируем Security REST через SoapUI/ curl/ Postman/ Testing RESTful Web Services + Generate Authorization Header +``` +curl -v -H 'Authorization: Basic dXNlckB5YW5kZXgucnU6cGFzc3dvcmQ=' http://localhost:8080/topjava/rest/profile/meals +``` + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW08 + Перевести mealList на datatables (mealList.jsp, UserMealAjaxController). + + Реализовать добавление записи еды через модальное окно Bootstrap + и удаление/добавление еды по ajax (без редактирования). + + При вставке данных по AJAX пропадает все JSP форматирование, + чинить перерисовку НЕ надо: отрисовываем таблицу через jsp. + Следующий урок- будем делать datatable по AJAX и форматирование на стороне клиента. + +Optional. + + Реализовать enable/disable User через checkbox в userList.jsp с сохранением в DB + + Перевести работы фильтра на AJAX (при обновлении данных таблицы учитывать поля формы фильтрации). + Попробуйте после модификации таблицы (например добавлении записи) обновлять ее также с учетом фильтра. + + Перейти на новый dataTables API + +Ресурсы + +- Converting parameter names for 1.10 +- dataTable() vs. DataTable() + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Что делает код? +``` +$('.delete').click(function () { + deleteRow($(this).attr("id")); +}); +``` + +На все элементы DOM с классом `delete` вешается обработчик события `click` который вызывает функцию `deleteRow`. Классы в HTML разделяются через пробел. + +> Тянет ли bootstrap за собой jQuery? + +Bootstrap зависит от jQuery + +> А где реально этот путь `classpath:/META-INF/resources/webjars`? Т.е. начиная от положения пакета, выходит? + +Внутри подключаемых webjars ресурсы лежат по пути `/META-INF/resources/webjars/...` Не поленитесь посмотреть на них через `Ctrl+Shift+N`. +Все подключаемые jar попадают в classpath и ресурсы доступны по этому пути. + +> У меня она лежит внутри `.m2\repository\org\webjars\`. С чем это может быть связано? + +Maven скачивает все депенденси в local repository, который по умолчанию находится в `~/.m2`. +Каталог по умолчанию можно переопределить в `APACHE-MAVEN-HOME\conf\settings.xml`, элемент `localRepository`. + +> WEBJARS лежат вообще в другом месте WEB-INF\lib*. Биндим mapping="/webjars/*" например на реальное положение jar в ware, откуда spring знает где искать наш jquery ? + +В war в `WEB-INF/lib/*` лежат все jar, которые попадают к classpath. Spring при обращении по url `/webjars/` ищет по пути биндинга `` +по всему classpath (то же самое как распаковать все jar в один каталог) в `META-INF/resources/webjars/`. В этом месте во всех jar, которые мы подключили из webjars лежат наши ресурсы. + +> Как можно в браузере сбросить введенный пароль базовой авторизации? + +Проще всего делать новый запрос в новой анонимной вкладке (`Ctrl+Shift+N` в Chrome) + +> Почему при запросе по rest с креденшелами admin возвращается еда user? + +Для запроса еды используется LoggedUser.id(), посмотрите что там. diff --git a/doc/lesson09.md b/doc/lesson09.md new file mode 100644 index 000000000000..7e9911dd9601 --- /dev/null +++ b/doc/lesson09.md @@ -0,0 +1,128 @@ +## Материалы 9-го урока + +- **Браузер кэширует javascript и css. Если изменения не работают, обновите приложение в браузере по Ctrl+F5** +- **При удалении файлов не забывайте делать clean: `mvn clean package`** + + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Правка кода +- **0-fix.patch** +- Поправил стиль и модификаторы доступа +- Поменял версию `datetimepicker` из-за проблем с DateFormatter +- Убрал `contentType` в REST тестах delete + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW8 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW8 +- **1-HW8.patch** + +> Поправка к видео: в гриде bootstrap 12 колонок. + +- Grid system +- Bootstrap description +- Bootstrap forms + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW8 Optional (enable/disable user) +- **2_HW8_enable_disable.patch** + +> Вместо использования id и селектора сделал обработчик `onclick` +- Введение в браузерные события + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. HW8 Optional (new API) +- **3_HW8_newApi.patch** + +> Новое API: поменялось как обновление таблицы, так и ее формат параметров. +- Converting parameter names for 1.10 + +## Занятие 9: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Spring Binding +- **4_binding.patch** + +> Move `ru.javawebinar.topjava.util.UserMealsUtil.DEFAULT_CALORIES_PER_DAY` to `ru.javawebinar.topjava.util.UserUtil` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Реализация update +- **5_update.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Spring Validation +- **6_validation.patch** +- Spring Validation. +- Bean Validation +- Валидация формы по AJAX. +- JSR-303, 349 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Перевод DataTables на Ajax +- **7-datatable-via-ajax.patch** +- DataTables Options + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Форма логина / логаут. +- **8-min-form-login.patch** +- Минимальный form-login +- Migrating <form-login> + + Вопросы: + Почему при логине как admin еда отдаются для user? + Почему при логине как user не отображается список пользователей? + Почему еда не редактируется и не удаляется? + +- **9-jsp-form-login.patch** +- Собственный form-login + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Реализация собственного провайдера авторицазии. +- **10_auth_via_user_service.patch** +> Изменения в проекте: вместо `LoggedUser implements UserDetails` сделал `LoggedUser extends org.springframework.security.core.userdetails.User` + +- UserDetailsService Implementations + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. Принцип работы Spring Security. Проксирование. +- Принцип работы Spring Security +- Типы проксирования +- Dynamic Proxy API +- Конфликт проксирования Data Repository +- Security фильтры + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 8. Spring Security Test +- **11-spring-security-test.patch** +> Поправил RootControllerTest. + +- Spring Security Test +- Интеграция со Spring MVC Test +- HttpBasic авторизация +- Тестирование контроллеров с помощью MockMvc (без spring-security-test) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 9. Cookie. Session. +- HTTP cookie +- Under what conditions is a JSESSIONID created? +- Tomcat Session Serialization + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW9 + + Реализовать для meal Binding/ Update/ Validation. Проверить работу при пустом значении Calories. + Перевести mealList.jsp на работу по ajax. Стиль записи таблицы сделать в зависимости от exceeded. + Починить meals тесты + +Optional + + Подключить datetime-picker + +- DateTimePicker jQuery plugin + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> В UserMealRestControllerTest нам нужны калории обязательно для LoggedUser? + +В тестах контроллера мы: +- заполняем правильно header креденшелами для базовой авторизации через userHttpBasic +- выполняем запрос +- поднятое тестами приложение авторизует юзера и заполняет LoggedUser +- выполняет запрос и возвращает ответ для залогиненного юзера +- авторизационный фильтр чистит свой контекст, LoggеdUser safeGet() возвратит null. + +Поэтому в тестах getCaloriesPerDay берите у тестового юзера, которым авторизовалась. + +> В `login.jsp` есть форма `<form:form action="spring_security_check" ..> Где такой url используется? + +Это стандартный url для авторизации в spring-security. Он его и обрабатывает. + +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW09 + +- Чтобы поле `UserMeal.dateTime` сбиндилось кроме аннотации нужно изменить в форме `mealList.jsp` `name="datetime"` на `"dateTime"` diff --git a/doc/lesson10.md b/doc/lesson10.md new file mode 100644 index 000000000000..33f54e630997 --- /dev/null +++ b/doc/lesson10.md @@ -0,0 +1,105 @@ +## Материалы 10-го урока + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Правка кода +- **0-fix.patch** +- Добавил в fail детали `jqXHR.responseJSON` +- Упростил callback для `updateTableByData` +- Поправил html и import + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW9 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) HW9 +- **1_HW9_ajax.patch** + +> Добавил UserMealTo (раньше был вместе с entity) + +- **2_HW9_test.patch** +- **3_HW9_datetimepicker.patch** + +> Изменил формат ввода даты в форме без 'T' + + - DateTimePicker jQuery plugin + - Datetimepicker and ISO-8601 + +## Занятие 10: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Spring Security Taglib. Method Security Expressions. +- **04_secure_tag_annotation.patch** +- Spring Security Taglib. +- Method Security и Method Security Expressions. Зависимость от контекста mvc/parent. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Interceptors. Редактирование профиля. JSP tag files. +- **05_interceptor.patch** +- Добавляем профиль. Spring interceptors. +- **06_profile_jsptag.patch** + +> ProfileRestController.update(user) изменился на userTo (нельзя, например, изменять себе роли). + +- Делаем jsp tag для ввода поля формы. +- Spring MVC Form Validation + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Форма регистрации. +- **07_registration.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Обработка исключений в Spring. +- **08_not_found_404.patch** +- Используем HTTP status code +- RESTful resource not found +- REST API 404 +- **9_global_exception.patch** +- Global Exception Handling +- **10_controller_based_exception.patch** + +> Т.к Rest и Ajax контроллеры уже отнаследованы от абстрактных контроллеров, сделал "подмешивание" в них ExceptionInfoHandler с помощью default методов интерфейса. + +- Примесь (mixin) +- Mixin в Java 8 +- Сериализация Exception в JSON +- Exception Handling на уровне контроллера + + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Encoding password +- **11_password_encoding.patch** +- Password Encoding + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Межсайтовая подделка запроса (CSRF). +- **12_csrf.patch** +- Межсайтовая подделка запроса (CSRF) +- Using Spring Security CSRF Protection +- Ajax and JSON Requests +- Stateless CSRF protection +- Ресурсы: + - Spring Security 4 + CSRF + - Are JSON web services vulnerable to CSRF attacks + - Правило ограничения домена (SOP) + - Cross-origin resource sharing (CORS) + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW10 + Сделать валидацию в AdminAjaxController/UserMealAjaxController через ValidationException + (вернуть клиенту ErrorInfo и статус HttpStatus.UNPROCESSABLE_ENTITY) + + Сделать обработку ошибки при дублирования email (User with this email already present in application) для: + регистрации / редактирования профиля пользователя + добавления / редактирования пользователя в списке + + +Optional. + + Сделать в приложении выбор локали (см. http://topjava.herokuapp.com/) + + Починить UTF-8 в редактировании профиля и регистрации (в windows). Подумайте, почему они поломались. + +Ресурсы: +- Spring MVC internationalization sample +- Spring 4 MVC Internationalization + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> В чем отличие между аннотоацией `@PreAuthorize("hasRole('ROLE_ADMIN')")` и конфигурацией в jsp: ``, `` ? + +Анотация `@PreAuthorize` обрабатывается Spring анологично `@Transactional`, `@Cacheable` - класс проксируется и до-после вызова метода добавляется функциональность. +В данном случае перед вызовом метода проверяются роль залогиненного юзера. JSTL тэг `authorize` выполняет проверку условия в залогиненном юзере внутри jsp. + +> Какой из `SessionLocaleResolver` и `CookieLocaleResolver` а какой лучше использовать для локализации? + +Я взял `CookieLocaleResolver`. Он хранит информацию в куках, которые хранятся долго у клиента на компьютере. Локаль в сессии проподает вместе с логаутом. diff --git a/doc/lesson_final.md b/doc/lesson_final.md new file mode 100644 index 000000000000..c62b38533427 --- /dev/null +++ b/doc/lesson_final.md @@ -0,0 +1,108 @@ +## Материалы заключительного занятия + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Refactoring & Fix +- **0-refactoring-fix.patch** + - Добавились поля serialVersionUID + - Рефакторинг REST контроллеров и обработка ошибок по `@ControllerAdvice(annotations = RestController.class)` + - Правка UTF-8 в редактировании профиля и регистрации (порядок `encodingFilter` и `springSecurityFilterChain` фильтров) + - Удалился `contentType` для get в тестах REST контроллеров (`contentType` нужен только для `@RequestMapping(consumes=..`) + - Добавилась авторизация в `RootControllerTest` + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW10 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW10 +- **1-validation-exception.patch** + +> Добавилась локализация `duplicate_email` + +> Обработка валидации формы для Ajax контроллеров сделана через `org.springframework.validation.BindException` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW10 Optional: change locale +- **2-change-locale.patch** + +> еще вариант смены локали в lang.jsp вместо javascript: `href=${requestScope['javax.servlet.forward.request_uri']}?lang=..` + +- spring-message vs fmt-message +- Bootstrap Navbar + +## Заключительное занятие + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Деплой приложения в Heroku. +- **3-heroku.patch** + +> В `DataJpaUserRepositoryImpl` добавилась защита от удаления-модификации Admin/User + +- Деплой Java Spring приложения в PaaS-платформу Heroku +``` +Config Vars + ERROR_PAGE_URL=... + TOPJAVA_ROOT=/app + +Datasources advanced + ssl=true + sslfactory=org.postgresql.ssl.NonValidatingFactory +``` +- Ресурсы: + - PaaS-платформа Heroku + - Конфигурирование приложения для запуска через Tomcat-based Java Web + - Конфигурирование DataSource profile для Heroku + - Интерграция с IDEA + - Find your Platform as a Service + - Getting Started with Java on Heroku + - Managing Your SSH Keys + - Deploy your application to Heroku + - Развертывание приложений Java с помощью PaaS от Heroku + - A Java Developer’s Guide to PaaS + - A Simple PaaS Comparison Guide (With the Java Dev in Mind) + - Java PaaS shootout + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание по проекту: + + Код для деплоя в Heroku был включен в проект, из за чего некоторые тесты перестали проходить. + Найти этот код и проверить что без него все работает. + + Сделать / обновить резюме + Отдать на ревью мне или группе + Заполнить форму "разработчик Java" + +- Составление резюме, подготовка к интервью, поиск работы +- Форма соискателя "Разработчик Java". + +## Возможные доработки приложения: +- Перевести UI на angular.js / Vaadin elements /GWT /GXT /Vaadin / ZK/ [Ваш любимый фреймворк].. +- Перевести проект на Spring Boot +- Перевести шаблоны с JSP на Thymeleaf +- Для редактирования паролей сделать отдельный интерфейс с запросом старого пароля и кнопку сброса пароля для администратора. +- Сделать авторизацию в приложение по OAuth 2.0 (Spring Security OAuth, +VK auth, github oauth, ...) +- Сделать подтверждение регистрации пользователя по email c возможностью восстановления пароля. +- Интергировать проект с Identity API Stormpath +- Сделать отображение еды постранично, с поиском и сортировкой на стороне сервера. +- Перевод проекта на https +- Сделать desktop/mobile приложение, работающее по REST с нашим приложением. +- Локализация сообщений об ошибках, показ ошибок в форме редактирования таблицы в стиле JSP. +- Limit login attempts example +- Любая на ваше усмотрение. + +**Если у Вас появилось желание доработать приложение и распространить код для других участников- +связывайтесь со мной для ревью и его оформления.** + +## Все материалы проекта + +### Ресурсы по Проекту +- Плагин Markdown Preview для Хрома +- Уроки Bootstrap 3 +- Spring at tutorialspoint +- Articles in Spring +- Learn Spring on Baeldung +- Spring Framework + Reference Documentation +- Hibernate Reference Documentation +- Java Course (книга 2) +- Справочник «Паттерны проектирования» +- Catalog of Patterns of Enterprise Application Architecture + +# ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Короткий опрос для всех участников проекта + +### Приглашаю всех в группу выпускников 'Online Projects' в skype (перед добавлением залогинтесь в аккаунт skype). +Это общение по теме java, трудоустройство, зарплаты и пр. Добавиться можно по ссылке или напиши мне в скайп и я добавлю. From 66277b483e320e2f747e839aca019c0967814ec2 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 26 May 2016 21:36:53 +0500 Subject: [PATCH 080/166] add description of lesson --- doc/entrance.md | 106 +++++++++++++++++++++++ doc/graduation.md | 32 +++++++ doc/lesson01.md | 187 +++++++++++++++++++++++++++++++++++++++++ doc/lesson02.md | 191 +++++++++++++++++++++++++++++++++++++++++ doc/lesson03.md | 182 +++++++++++++++++++++++++++++++++++++++ doc/lesson04.md | 153 +++++++++++++++++++++++++++++++++ doc/lesson05.md | 124 +++++++++++++++++++++++++++ doc/lesson06.md | 201 ++++++++++++++++++++++++++++++++++++++++++++ doc/lesson07.md | 134 +++++++++++++++++++++++++++++ doc/lesson08.md | 148 ++++++++++++++++++++++++++++++++ doc/lesson09.md | 128 ++++++++++++++++++++++++++++ doc/lesson10.md | 105 +++++++++++++++++++++++ doc/lesson_final.md | 108 ++++++++++++++++++++++++ 13 files changed, 1799 insertions(+) create mode 100644 doc/entrance.md create mode 100644 doc/graduation.md create mode 100644 doc/lesson01.md create mode 100644 doc/lesson02.md create mode 100644 doc/lesson03.md create mode 100644 doc/lesson04.md create mode 100644 doc/lesson05.md create mode 100644 doc/lesson06.md create mode 100644 doc/lesson07.md create mode 100644 doc/lesson08.md create mode 100644 doc/lesson09.md create mode 100644 doc/lesson10.md create mode 100644 doc/lesson_final.md diff --git a/doc/entrance.md b/doc/entrance.md new file mode 100644 index 000000000000..8a2ea1e294eb --- /dev/null +++ b/doc/entrance.md @@ -0,0 +1,106 @@ +Java Enterprise Online Project +=============================== +Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: +Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. + +Вводное занятие +=============== +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Системы управления версиями. Git. + +- Система управления версиями. VCS/DVSC. +- Ресурсы: + - Интерактивная Git обучалка + - Основы Git + - Working with remote repositories + - Видео по обучению Git + - Как начать работать с GitHub: быстрый старт + - Справочник в графическом виде + - Git Overview + - Видеокурс по Git + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Работа с проектом (выполнять инструкции) +- **prepare-to-HW0.patch (скачать и положить в каталог вашего проекта)** +- Доступно о Java 8 Lambda + +## Инструкция по шагам (из видео): +- Установить ПО (git, JDK8, IntelliJ IDEA, Maven) +- Создать аккаунт на GitHub +- Сделать Fork ЭТОГО проекта (https://github.com/JavaOPs/topjava) +- Сделать локальный репозиторий проекта: +
git clone https://github.com/[Ваш аккаунт]/topjava.git
+- Открыть и настроить проект в IDEA +- По ходу видео сделать Apply Patch... скаченного патча Prepare_ to_ HW0.patch +- Закоммитить и запушить изменения (commit + push) +- Сделать ветку домашнего задания +- Выполнить задание и залить на GitHub (commit + push) +- Переключиться в основную ветку проекта master. + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW0 + +- Java 8 Date and Time API +- Time complexity +- Временная сложность алгоритма +- Вычислительная сложность +``` +Реализовать UserMealsUtil.getFilteredMealsWithExceeded: +- должны возвращаться только записи между startTime и endTime +- поле UserMealWithExceed.exceed должно показывать, + превышает ли сумма калорий за весь день параметра метода caloriesPerDay + +Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. + +- Проверте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) +- Оцените Time complexity вашего алгоритма (если он O(N*N)- попробуйте сделать O(N). +``` +Optional + +- Java 8: Lambda выражения +- Java 8: Потоки +- Pуководство по Java 8 Stream +- Лямбда-выражения в Java 8 +- Шпаргалка Java Stream API +- stream.map vs forEach + +``` +Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) +``` + +### ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Как брать диапазон дат? Какие брать дефолтные значения для null? + +Для диапазона нужно использовать уже готовый `TimeUtil.isBetween`. Как приложение должно себя вести можно проверить на demo. + +> Почему мы сначала суммируем калории, а потом фильтруем? Если записи за несколько лет то это лишняя работа. Не будет правильнее сначала фильтр, а потом все остальное? + +На вход в getFilteredWithExceeded подается список, уже отфильтрованный по датам. Будем делать это условием в SQL. + +> Лямбда выражения заменяют анонимные классы? + +Синтаксически лямбду можно преобразовать в анонимный класс (в IDEA Alt+Enter на лямбде и Replace lambda with anonymous class). +Наоборот- только SAM (Single Abstract Method) интерфейс можно преобразовать в lambda. В JVM вызов происходит через invokedynamic. + +> Как оценить временную сложность? Как определить: O(N), O(N*N) и O(N+N) ? + +Из wiki это зависимость объёма работы, которая выполняется некоторым алгоритмом, от размера входных данных. +O- это коэффициент. поэтому нет O(N+N), есть O(N). +Для нашего случая все просто- если вложенный цикл или стрим, то O(N*N), иначе O(N). + +> Какая сложность у map.containsKey(key)? не добрался до исходников. + +Вообще исходники есть: `java.util.HashMap.containsKey`. Устройство Map (и вообще коллекции)- одни из самых частых вопросов на собеседовании. +Читаем Структуры данных в картинках и Java собеседование. Коллекции. +При хорошем распределении ключей сложность `HashMap.containsKey - O(1), TreeMap.containsKey - O(log2 N), а LinkedList.contains- O(N)`. + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Типичные ошибки в домашнем задании: +- Объявление переменных + - объявляйте переменные непосредственно перед их использованием (не все вверху) + - если возможно, инициализируйте их сразу при объявлении. Если она неизменяемая, будет красиво сделать ее final. + - рассмотрите возможность сделать ее inline (вообще без переменной). Если логика несложная, становится лаконичнее. + - тип переменных-коллекций нужно объявлять интерфейсом: не HashMap, а Map + - имена переменных должны быть информативные и служить заменой комментариям + +- Оформление + - представьте, что это ваше тестовое задание на работу. Не должно быть лишних комментариев, TODO, .. + - комментариев должен быть минимум (самодокументируемый код). Если таки нужно - лучше на english. + - почаще пользуйтесь Ctrl+Alt+L - Reformat Code + - в `main()` можно выводить на `System.out`. Для этого подходит перопределение `toString` объектов вывода. diff --git a/doc/graduation.md b/doc/graduation.md new file mode 100644 index 000000000000..beeacd53ff41 --- /dev/null +++ b/doc/graduation.md @@ -0,0 +1,32 @@ +Java Enterprise Online Project +=============================== +Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: +Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. + +Graduation project +================= + +Design and implement a JSON API using Hibernate/Spring/SpringMVC (or Spring-Boot) **without frontend**. + +The task is: + +Build a voting system for deciding where to have lunch. + + * 2 types of users: admin and regular users + * Admin can input a restaurant and it's lunch menu of the day (2-5 items usually, just a dish name and price) + * Menu changes each day (admins do the updates) + * Users can vote 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 after 11:00 then it is too late, vote can't be changed + +Each restaurant provides 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 used by a frontend developer to build frontend on top of that. diff --git a/doc/lesson01.md b/doc/lesson01.md new file mode 100644 index 000000000000..e794ca3b788c --- /dev/null +++ b/doc/lesson01.md @@ -0,0 +1,187 @@ +# Онлайн проекта Topjava + +- **Напоминаю, что cвой код пишете только в ветках HWxx. Код в ветке MASTER трогать нельзя! +Модификация кода только через патчи в материалах урока (Apply Patch), иначе придется мержить код. +Все патчи объязательны и применяются по порядку. Если при применении патча предлагается мержд, cмотрите GIT Wiki, Patch не накатывается.** + +- **Делать Apply Patch лучше по одному непосредственно перед видео на эту тему - тогда при просмотре вы сразу сможете отслеживать изменения кода проекта.** После патча часто полезно запустить и посмотреть на работу приложения. + +- **Код проекта постоянно модифицируется, поэтому в видео вы можете увидеть старую версию кода. Иногда это способствует лучшему пониманию кода и вы видете, каким образом проект развивался** + +- **Не надо подробно читать все ссылки занятия, иначе времени не останется на ДЗ, что гораздо важнее. Пусть ссылки будут Вашим справочником, к которым вы в любое время можете обратиться.** + +- **Подсказка по работе с патчами (GIT Wiki)** + +## Материалы 1-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW0: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Optional: реализация getFilteredMealsWithExceeded через Stream API +- **1-1-HW0-stream.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Работа с git в IDEA. Реализация через цикл. +- **1-2-HW0-cycle.patch** + +> Изменения в проекте: `map.getOrDefault` земенил на `map.merge`, `for` заменены на `forEach` + +## Занятие 1: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Обзор используемых в проекте технологий. Интеграция ПО. +- Обзор популярности инструментов и технологий Java за 2014 г. +- Видео "Приложение Spring Pet Clinic" +- Приложение Spring Pet Clinic. +- Demo Spring Pet Clinic + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Maven. +- Среда сборки проектов Maven. +- The Central Repository +- Настройка пропертей Maven: кодировка, java version, зависимости, maven-compiler-plugin +- The Reactor. Snapshots +- Недостатки Maven. Другие инструменты сборки. +- Справочник: + - Автоматизация сборки проекта + - Home Page + - Maven: The Complete Reference + - Разработка ПО вместе с maven + - Build Lifecycle + - Dependency Mechanism + - Создание своих архетипов и каталогов в Maven + - Зависимости, профили + - Bintray: gateway to Maven Central + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. WAR. Веб-контейнер Tomcat. Сервлеты. +- **1-3-switch-to-war.patch** +> Сервлет добавляется в следующем патче, те в web.xml он будет подчеркиваться красным. + +- **1-4-add-servlet-api.patch** +- **1-5-forward-to-redirect.patch** + +- Перевод проекта на Web. +- Tomcat Home Page +- Сервлеты. +- Настройка и деплой в Tomcat. Tomcat manager. Remote debug. +- Запуск Tomcat из IDEA. Динамическое обновление без передеплоя. +- Redirect, Forward, Application context, Servlet context + - Томкат менеджер: http://localhost:8080/manager + - Наше приложение: http://localhost:8080/topjava + - Наш сервлет: http://localhost:8080/topjava/users + +- Справочник: + - Отладчик IntelliJ IDEA + - Yakov Fain: Intro to Java EE. Glassfish. Servlets (по русски) + - Yakov Fain: HTTP Sessions, Cookies, WAR deployments, JSP (по русски) + - Golovach Courses: Junior.February2014.Servlets + - Remotely debug on tomcat from IDEA + - Java Server Page + - Java Server Pages (JSP) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Логирование. +- **1-6-logging.patch** +> **установите переменную окружения на TOPJAVA_ROOT на корень проекта и перезапустите IDEA** + +> изменения в проекте: убрал `LoggerWrapper` и логирую напрямую в логгер SLF4J. При логгировании через вспомогательный класс, в логе теряется имя исходного класса. + +- Java Logging: история кошмара +- Ведение лога приложения +- Log4j, Logback +- Добавление зависимостей логирования в проект. Переменная окружения TOPJAVA_ROOT +- Конфигурирование логирования. Настройка Live Template. +- Тестирование логирования в сервлете. + +#### Проверочные вопросы: +- Что нужно изменить в `pom.xml`, чтобы перейти с logback на log4j ? + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Подсоединение к логгеру по JMX. Выбор работы. +- **1-7-remote-jmx.patch** + +> Для подключения к Remote Process (`localhost:1099`) положить `setenv.bat` в `$TOMCET_HOME/bin`. + +- Управление логированием по Java Management Extensions. + +### ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> Используются ли еще где-то в реальной разработке JSP, или это уже устаревшая технология? Заменит ли ее JSF (https://javatalks.ru/topics/38037)? + +JSF и JSP- разные ниши и задачи. +JSP- шаблонизатор, JSF- МVС фреймворк. Из моего опыта- с JSP сталкивался в 60% проектов. Его прямая замена: http://www.thymeleaf.org, но пока встречется достаточно редко. JSP не умирает, потому что просто и дешево. Кроме того включается в любой веб-контейнер (в Tomcat его реализация Jasper) + +JSF- sun-овский еще фреймворк, с которым я ни разу не сталкивался и особого желания нет. Вот он как раз, по моему мнению, активно замещается хотябы angular. + + +> А зачем мы использовали logback? Почему SLF4J нас не устроило? Почему реализация логирования не log4j? + +В `slf4j-api` это API. Там есть только пустая реализация `org.slf4j.helpers.NOPLogger` (можно посмотреть в исходниках). Logback для новых проектов стал стандарт. spring-petclinic и spring-boot используют его. http://logback.qos.ch/reasonsToSwitch.html + +--------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW01 (делаем ветку HW01 от последнего патча) + + По аналогии с Users добавить MealServlet и mealList.jsp. Время выводить без 'T'. + (пока фильтрацию не делаем и User к UserMeal не добавляем) + Задеплоить приложение (war) в Tomcat c applicationContext=topjava + (приложение должно быть доступно по http://localhost:8080/topjav) + Попробовать разные деплои в Tomcat, remote и local debug + + Сделать отображения списка еды в jsp, цвет записи в таблице зависит от параметра isExceeded (красный/зеленый). + Вариант реализации: + - из сервлета обращаетесь к реализации хранения еды в памяти; + - преобразуете результат в List; + - кладете список в запрос (request.setAttribute); + - делаете forward на jsp для отрисовки таблицы (при redirect аттрибуты теряются). + В jsp для цикла можно использовать JSTL tag forEach. + + Для подключения `JSTL` в `pom.xml` нужно добавить зависимость: + + + javax.servlet + jstl + 1.2 + + +- Ресурсы: + - Интернет-приложения на JAVA + - HTTP 1.0 vs 1.1 + - JSP + - JSTL для написания JSP страниц + - JSTL: Шаблоны для разработки веб-приложений в java + +Optional + + Сделать В ПАМЯТИ (Map) реализацию CRUD (create/read/update/delete) для списка еды с учетом многопоточности. + Хранение в памяти будет одна из наших реализаций (такжен будет jdbc, jpa и data-jpa), подумайте над ее названием. + Работать с ней через интерфейс, который не должен ничего знать о деталях реализации (Map, DB или что-то еще). + + В качестве ключа добавить id в UserMeal/ UserMealWithExceed. + Сделать форму редактирования в JSP: + AJAX/JavaScript использовать не надо, делаем через и doPost() в сервлете. + + - SIMPLE CRUD USING JSP, SERVLET + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Решение проблем + +- Если вы не попадаете на страничку/брекпойнт в сервлете: + - внимательно проверьте url и applicationContext (Application Context должен быть тот же, что и url приложения): https://github.com/JavaOPs/topjava/wiki/IDEA + - деплоить лучше как war exploded: просто нет упаковки в war и при нажатой кнопке `Update Resources on Frame Deactivation` можно обновляться css, html, jsp без передеплоя. При изменении web.xml, добавлении методов, классов необходим redeploy. + - посомтрите в task manager: возможно у вас запущено несколько JVM и они мешают друг другу. + +- Проблемы с кодировкой в POST. Возможное решение: +``` +protected void doPost(HttpServletRequest request, ...) { + request.setCharacterEncoding("UTF-8"); +``` + +- Пробелема с maven зависимостью: иногда помогает удаление закаченного артифакта из локального maven репозитория (посмотреть его расположение можно в Maven Settings) и Reimport (последняя и первая кнопка в Maven Projects). + +- Для работы с JSTL кроме добавления зависимости в `pom.xml` не забываем добавлять в шапку JSP: +``` +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +... +``` +- После выставления переменной окружения IDEA нужно рестартовать. Проверить, видит ли java переменную окуржения можно так: `System.getenv("TOPJAVA_ROOT")`. Еще вариант: добавить `-DTOPJAVA_ROOT=...` в опции запуска приложения, тогда она доступна из java как `System.getProperty("TOPJAVA_ROOT")`. + +---------------------------- + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Типичные ошибки +- Хранить нужно `UserMeal` и конвертировать ее в `UserMealWithExceed` когда отдаем список на отображение в JSP. Иначе при редактировании любой записи или изменении юзером своей нормы `caloriesPerDay` нужно будет пересчитывать все записи юзера. +- Стили `color` можно применять ко всей строке таблицы `tr`, а не каждой ячейке. +- DateTimeFormatter можно сделать один заранее (он потокобезопасный в отличии от SimpleDateFormatter), а не создавать новый при каждом запросе. +- Реализаций интерфейса хранения будет несколько. Нужно учитывать это в названии класса. +- Если в названии класса есть Meal, не нужно использовать слово meal в методах класса. diff --git a/doc/lesson02.md b/doc/lesson02.md new file mode 100644 index 000000000000..8a6df8fb0ce3 --- /dev/null +++ b/doc/lesson02.md @@ -0,0 +1,191 @@ +## Материалы 2-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW1: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. отображения списка еды в JSP +- **1-HW1.patch** + +> Изменения в UserMealsUtil: + - Сделал константу List MEAL_LIST + - Убрал из имен методов Meals + - Сделал вспомогательный метод getWithExceeded + +- MVC - Model View Controller + +> Форматирование даты поменял на `${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Optional: реализация CRUD +- **2-HW1-optional.patch** + +> Про repository будет подробно в видео "Слои приложения" + +- HTTP 1.0 vs 1.1 + +### ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Вопросы по HW1 + +> Зачем в InMemoryUserMealRepositoryImpl наполнять map с помощью нестатического блока инициализации, а не в конструкторе? + +Разницы нет. Разве что напомнить вам про эту конструкцию:) См. Малоизвестные особенности Java + +> Почему InMemoryUserMealRepositoryImplу y нас не singleton? + +По умолчанию бины Spring являются синглетонами (в его контексте). А все наши классы слоев приложения будут создаваться через Spring. + +> Зачем вместо `` писать `` ? + +Ссылки должны быть относительные, чтобы, если мы задеплоим приложение с applicationContext=topjava (`localhost:8080/topjava`) они не указывали на корень `localhost:8080`. Т.е. или `action="meals"` или `""`. +Второй правильнее, когда путь к сервету составной (например `localhost:8080/topjava/ajax/profile/meals`). У нас будет на эту тему видео и мы будем чинить относительные ссылки. + +## Занятие 2: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Библиотека vs Фреймворк. Стандартные библиотеки Apache Commons, Guava +- Apache Commons, Guava + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Слои приложения. Создание каркаса приложения. +- **3-app-layers.patch** + +> Про DTO еще можно будет посмотреть в видео Алименкова "Босиком по Граблям", когда мы добавим Hibernate + +- Паттерн "Слои приложения" +- Архитектурные + слои приложения в Spring +- Data Access Object +- Паттерны Repository и DAO +- Паттерн DTO +- Value Object и Data Transfer Object +- Should services always return DTOs, or can they also return domain models? + +Справочник: + - Забудьте о DAO, используйте Repository + - Difference between Active Record and DAO + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Обзор Spring Framework. Spring Context. +- **4-add-spring-context.patch** + + - Spring Framework + - Проекты Spring. + - Обзор Spring Framework + +- **5-add-dependency-injection.patch** + - Инверсия управления. + - IoC, DI, IoC-контейнер — Просто о простом + +- **6-add-annotation-processing.patch** + - Difference + between @Component, @Repository & @Service annotations in Spring + - Spring Auto Scanning Components + - Использование аннотации @Autowired + +- Справочник: + - DI/ Service Locator + - Introduction to the Spring IoC container + and beans + - Constructor против Setter Injection + - Getting Started + - Spring Framework Reference Documentation + - Spring на GitHub + - Spring Annotations + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Пояснения к HW2. Обработка Autowired + + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> какова цель деления приложения на слои? + +Управляемость проекта (особенно большого) повышается на порядок: +1. Обеспечивается меньшая связываемость. Допустим если мы меняем что то в контроллере, то сервис эти изменения не задевают. +2. Облегчается тестирование (мы будем тестировать слои сервисов и контроллеров отдельно) + +> DTO используются когда есть избыточность запросов, которую мы уменьшаем, собрав данные из разных бинов в один? Когда DTO необходимо использовать? + +(D)TO может быть как частью одного entiry (набор полей) так и набором нескольких entity. +В нашем проекте для данных, которые надо отдавать наружу и они отличаются от Entiy (хранимый бин), мы будем делаем (Data) Transfer Object и класть в отдельный пакет to. +У нас наружу будет отдаваться как UserMeals, так и UserMealsWithExceeded (для таблицы). Т.е. UserMealsWithExceeded у нас является Transfer Object и его надо пернести в пакет to, а для UserMeals мы отдельного TO создавать не будем. +На многих проектах (и собеседованиях) практикуют разделение на уровне maven модулей entity слоя от логики и соответствующей конвертацией ВСЕХ Entity в TO, даже если у них те же самые поля. +Хороший ответ когда TO объязательны есть на Stackoverflow: When to Use. + +> Что такое схема в spring-app.xml xsi:schemaLocation и зачем она нужна + +XML схема нужна для валидации xml, IDEA делает по ней атвозаполнение. + +> Почему контроллеры положили в папку web, а не в conrollers? + +Тоже самое что domain/model - просто разные названия. web думаю даже чаще называют (например в spring petclinic) + +> Что означает для Spring + + + + ? + +Можно сказать так: создай и занеси в свой контекст бин UserServiceImpl и заинжекть в его проперти из своего контекста бин mockUserRepository. + +> Как биндинг происходит для `@Autowired`? Как поступать, если у нас больше одной реализации `UserRepository`? + +`@Autowired` инжектит по типу (т.е. ижектит класс который наследует `UserRepository`). Обычно он один. Если у нас несколько реализаций, Spring не поднимится и поругается - No unique bean. В этом случае можно уточнить имя бина через @Qualifier. `@Qualifier` обычно добавляют только в случае нескольких реализаций. + +> Зачем мы наследуем NotFoundException от RuntimeException? + +Так с ним удобнее работать. И у нас нет никаких действий по восстановлению состояния приложения (no recoverable conditions): checked vs unchecked exception + +-------------------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW02 + + 1. Рефакторинг репозиториев: + - переименовать MockUserRepositoryImpl в InMemoryUserRepositoryImpl и имплементировать по аналогии с + InMemoryUserMealRepositoryImpl (список пользователей возвращать отсортированным по имени) + + - зарефакторить UserMealRepository/InMemoryUserMealRepositoryImpl: + - еда принадлежит пользователю + - список еды возвращать отсортированным по времени, последние записи наверху + - если еда отсутствует или чужая, возвращать null/false (см. UserRepository) + + 2. UserMealWithExceed переносим в пакет to (transfer objects), UserMeal extends BaseEntity + + 3. Сделать реализацию слоев приложения для функциональности "еда": + - API контроллера должна удовлетворять все потребности демо приложения и ничего лишнего + (см. http://topjava.herokuapp.com) + - после авторизации запрос попадает в контроллер, методы которого будут доступны снаружи по http, + т.е. в запросе может прийти id для еды, не принадлежащее авторизированному пользователю + (id авторизованного юзера попадает (сделаем позже) в LoggedUser.id(), см. ProfileRestController). + Нельзя позволять модифицировать/смотреть чужую еду: + - у еды есть УНИКАЛЬНОЕ id, по которому можно определить из базы, чья она; + - если еда не принадлежит авторизированному пользователю или отсутствует, то бросать NotFoundException. + + 4. Включить классы в контекст Spring и проверить из SpringMain вызов метода UserMealRestController + +Примечания: + + - LoggedUser известен только на слое web (UserMealService можно тестировать без подмены логики авторизации) + - Пока можно НЕ добавлять поля в UserMeal (добавим позже), + принимаем в методах сервиса и репозитория параметр userId: id владельца еды. + - В UserMealServiceImpl постараться сделать в каждом методе только одни запрос к UserMealRepository + - Не надо в названиях методов повторять названия класса, например Meal. + +Optional + + 1. В MealServlet сделать инициализацию Spring, достать UserMealRestController из контекста + и работать с едой через него (см. SpringMain. pom.xml НЕ менять, работаем со spring-context). + + 2. Добавить в mealList.jsp и MealServlet две отдельные фильтрации еды: по дате и по времени (см. демо) + + 3. Добавить выбор текущего залогиненного пользователя (имитация авторизации) + (например захардкодить 1,2 в index.html select и сделать LoggedUser.setId(userId) в UserServlet). + +--------------------- +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW02 (для проверки, сначала сделайте самостоятельно!) + +- `InMemoryUserRepositoryImpl.getByEmail` попробуйте сделать через `stream` +- `InMemoryUserRepositoryImpl.delete` попробуйте сделать за одно обращение к map +- UserMealRestController должен уметь обрабатывать запросы: + - Отдать свою еду (для отображения в таблице, формат `List`), запрос БЕЗ параметров + - Отдать свою еду, отфильтрованную по startDate, startTime, endDate, endTime + - Отдать/удалить свою еду по id, параметр запроса - id еды. + Если еда с этим id чужая или отсутствует - `NotFoundException` + - Сохранить/обновить еду, параметр запроса - UserMeal. Если обновляемая еда с этим id чужая или отсутствует - NotFoundException +- Т.к. контроллер позволяет управлять ТОЛЬКО своей едой, `userId` снаружи не приходит (см. `ProfileRestController`) +- Фильтрацию по датам правильнее опустить на уровень репозитория т.к. из базы будем брать сразу отфильтрованные по дням записи. Следите чтобы первый и последний день не были обрезаны, иначе сумма калорий будет неверная. +- Сервлет мы удалим, а контроллер останется, поэтому возвращать `List` надо из контроллера +- Закрывать springContext в сервлете грамотнее всего в `HttpServlet.destroy()`: если где-то в контексте Spring будет ленивая инициализация, метод-фабрика, не синглетон-scope, то контекст понадобится при работе приложения. У нас такого нет, но делать надо все грамотно. +- Проверьте корректную обработку пустых значений date и time в контроллере +- Если запрашивается список и он пустой, всегда возвращайте emptyList(). По нему можно легко итерироваться без риска NullPoinException. В java 8 появились вместо null есть возможность использовать Optional (на самом деле давно уже была в библиотеках типа Guava), они полезны для цепочек вычислений. Я сторонник применять их когда код становится проще и логичнее. diff --git a/doc/lesson03.md b/doc/lesson03.md new file mode 100644 index 000000000000..956f316a9a5f --- /dev/null +++ b/doc/lesson03.md @@ -0,0 +1,182 @@ +## Материалы 3-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW2 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW2 +> Изменения в проекте: + - В репозиториях по другому инстанциировал компараторы + - Пернес обработку пустых дат в `UserMealRestController.getBetween()` + - Зарефакторил `> TimeUtil.isBetween(T value, T start, T end)`. Дженерики означают, что мы принимаем экземпляры класса, который имплементит компаратор, + который умеет сравнивать T или суперклассы от T + +- **1-HW2-repository.patch** + +**Внимание: при удалении класса он остается скомпилированный у вас в target (и classpath). В этом случае (или вообще если непонятно почему проект глючит) сделаейт в maven clean.** + +- **2-HW2-meal-layers.patch** +- **3-HW2-optional-MealServlet.patch** +- **4-HW2-optional-filter.patch** +- **5-HW2-optional-select-user.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Вопросы по API и слоям приложения +- Should services always return DTOs, or can they also return domain models? +- Mapping Entity->DTO goes in which application layer: Controller or Service? + +## Занятие 3: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Коротко о жизненном цикле Spring контекста. +- **6-bean-life-cycle.patch** +- Spring изнутри. Этапы инициализации контекста. +- Ресурсы: + - Евгений Борисов. Spring, часть 1 + - Евгений Борисов. Spring, часть 2 + - Презентация Spring framework core + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Тестирование через JUnit. +> **ВНИМЕНИЕ: перед накаткой патча создайте каталог test (из корня проекта путь `\src\test`), иначе часть файлов попадет в `src\main`.** + +- **7-add-junit.patch** + +- Перенос mock реализации в test. +- JUnit 4 +- Тестирование в Java. JUnit + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Spring Test +- **8-add-spring-test.patch** +- Интеграция Spring и JUnit. +- Spring Testing + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Базы данных. Обзор NoSQL и Java persistence solution без ORM. +- PostgreSQL. +- NoSQL or RDBMS.Обзор NoSQL систем. CAP +- JDBC +- Обзор Java persistence solution без ORM: commons-dbutils, + Spring JdbcTemplate, + MyBatis, JDBI, jOOQ + +- Основы: + - Реляционная СУБД + - Реляционные базы + - Уроки по JDBC + - Postgres Guide + - PostgreSQL Tutorial + - Try SQL + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Настройка Database в IDEA. +- **9-add-postgresql.patch** +- Настройка Database в IDEA и запуск SQL. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Скрипты инициализации базы. Spring Jdbc Template. +- **10-populate-and-init-db.patch** + +- **11-impl-JdbcUserRepository.patch** +- Подключение Spring Jdbc. + +- Конфигурирование DataSource. Property Placeholder +- **Проверьте, что в контекст Spring проекта включены оба файла конфигурации**. +- Имплементация UserRepository через Spring Jdbc Template. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. Подготовка тестовых данных и тестирование UserService. +- **12-test-UserService.patch** +- Подготовка тестовых данных в UserServiceTest. Добавление TestUser и ModelMatcher +- Тестирование UserService. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 8. Логирование тестов. +- **13-test-logging.patch** + +- **14-fix-servlet.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 9. Ответы на Ваши вопросы +- Что такое REST? +- Зачем нужна сортировка еды? +- Можно ли обойтись без `MapSqlParameterSource`? +- Как происходит работа с DB в тестах? +- Как реализовывать RowMapper? +- Мои комментарии: решения проблем разработчиком. +- Нужен ли разработчику JavaScript? + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Какая разница между @BeforeClass and @Before? + +`@BeforeClass` выполняется один раз после загрузки класса (поэтому метод может быть только статический), `@Before` перед каждым тестом. Не ленитесь сходить в исходники по Ctrl+Enter и почитать javadoc. BTW: для чистоты экземпляр тестового класса пересоздается перед каждым тестом: http://stackoverflow.com/questions/6094081/junit-using-constructor-instead-of-before + +> Тесты в классе в каком-то определенном порядке выполняются ("сверху вниз" например)? + +Порядок по умолчанию неопределен, каждый тест должен быть автономен и не зависеть от других. См. также http://stackoverflow.com/questions/3693626/how-to-run-test-methods-in-specific-order-in-junit4 + +> Объязательно ли разворачивать postgreSQL? + +Желательно: хорошая и надежная DB:) Если совсем не хочется - можно работать со своей любимой RDBMS (поправить `initDB.sql`) или работать c heroku (креденшелы к нему есть в `postgres.properties`). На следующем уроке добавим HSQLDB, она не тербует установки. + +> Зачем начали индексацию с 100000? + +Тут уже нет "как принято". Так удобно вставлять в базу (если будет потребность) записи не мешая счетчику. + +> Разве не должно быть у каждой роли примари кей ID и форин кей user_id ? + +Отношение user <-> roles многие ко многим, делается это через таблицу-связку USER_ROLES. Но отдельной таблицы ROLES у нас нет, мы храним значение роли прямо в таблице-связке USER_ROLES. Есть еще варианты использовать тип enum у Postgres или хранить там INTEGER Roles.ordinal(). При этом нельзя будет менять порядок в enum Role и добавлять новые роли можно будет только в конец: http://stackoverflow.com/questions/6789342/jpa-enum-ordinal-vs-string + +> Из 5-го видео - "Логика в базе - большое зло". Можно чуть поподробней об этом? + +- Есть успешные проекты с логикой в базе. Те все относительно. +- Логика в базе - это процедуры и триггеры. Нет никакого ООП, переиспользовать код достаточно сложно, никагого рефакторинга, поиска по коду и других плюшек IDE. Нельзя делать всякие вещи типа кэширования, хранения в сесии - это все для логики на стороне java. Например json можно напрямую отдать в процедуру и там парсить и вставлять в таблицы или наоборот - собирать из таблиц и возвращать. +А затем потребуется некоторая логика на стороне приложения и все равно придется этот json дополнительно разпарсивать в java. +Я на таком проекте делал сепециальную миграцию, чтобы процедуры мигрировать не как sql скрипты, а каждую процедуру хранить как класс с историей изменений. Если логика: триггеры и простые процедуры записи-чтения, которые не требуют логики и периспользования кода или +небольшой проект - это допустимо, иначе проект становится очень трудно поддерживать + +> У JUnit есть ассерты и у спринга тоже. Можно ли обойтись без JUnit? + +Предусловия и JUnit-тесты совершенно разные вещи. Один другого не заменит. + +> Я так понял VARCHAR быстрее, чем TEXT, когда мы работаем с небольшими записями. Наши записи будут небольшими (255). Почему вы приняли решение перейти на TEXT? + +В отличие от MySql в Postgres VARCHAR и TEXT - тоже самое: http://stackoverflow.com/questions/4848964/postgresql-difference-between-text-and-varchar-character-varying + +> Зачем при создании таблицы мы создаем `CREATE UNIQUE INDEX` и `CREATE INDEX`. При каких запросах он будет использоваться? + +UNIQUE индекс нужен для обеcпечения уникальности, DB не даст сделать одинаковый индекс. Индексы используется для скорости выполнения запросов. Обычно они задействуются, когда в запросе есть условия, на которые сделан индекс. Узнать по конкретному запросу можно запросив план запроса: см. Оптимизация запросов. Основы EXPLAIN в PostgreSQL + +> А это нормально, что у нас в базе у meals есть userId, а в классе - нет? + +Что значит - "норамльно"? приложение работает. Ненормально, когда в приложении есть "лишний" код, который не используется. Для ORM он нам понадобится- мы ссылку на User добавим. + +> Почему мы использует 1 БД sequence на разные сущности? + +Мы будем использовать Hibernate, по умолчанию он делает глобальный sequence на все таблицы. В этом подходе есть как плюсы, так и минусы, из плюсов - удобно делать ссылки в коде и в таблицах на при наследовании и мапы в коде. В дополнение: Configure Hibernate to create separate sequence for each table by default. + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW03 +``` + - Понять, почему перестали работать SpringMain/ + InMemoryAdminRestControllerTest/ InMemoryAdminRestControllerSpringTest + - Дополнить скрипты создания и инициализации базы таблицой MEALS. Запустить скрипты на вашу базу (через Run) + - Реализовать через Spring JDBC Template JdbcUserMealRepositoryImpl + - сделать каждый метод за один SQL запрос + - userId в класс UserMeal вставлять НЕ надо + (для UI и REST это лишние данные, userId это id залогиненного пользователя) + - JbdcTemplate работает через сеттеры. Нужно их добавить в UserMeal + - Cписок еды должен быть отсортирован (тогда мы его сможем сравнивать с тестовыми данными). + Кроме того это требуется для UI и API: последняя еда наверху. + - postgres драйвер не понимает LocalDateTime, использовать преобразования: + Timestamp.valueOf(ldt) / timestamp.toLocalDateTime() + - Преобразовывать Timestamp -> LocalDateTime можно в геттерах/сеттерах UserMeal, + но лучше сделать свой RowMapper + - Проверить работу MealServelt, запустив приложение +``` +Optional + + - Сделать тестовые данные `MealTestData`, АНОЛОГИЧНЫЕ пропопулированным в populateDB.sql. + Тестовый класс-обертка к `UserMeal` не требуется, сравниваем данные через готовый MATCHER (toString) + - Сделать `UserMealServiceTest` из `UserMealService` (Ctrl+Shift+T и выбрать JUnit4) и реализовать тесты. + - Сделаеть тесты на чужих юзеров (delete, get, update) с тем чтобы получить `NotFoundException` + - Предложить решение, как почнинить SpringMain/ InMemory*Test + - Сделайте индекс к таблице Meals. + +- Postgres and Indexes on Foreign Keys and Primary Keys +- Postgres Guide: Indexes + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Ошибки в HW3: +- UserMealRowMapper многие делают отдельным классом. Зачем? + Изучаем вложенные,внутренние, локальные и анонимные классы. Наконец, делаем маппер как константу через лямду. + +- Проверте, что вернется при update чужой еды. +- `InMemory*Test` предполагает использовать нашу реализацию репозитория в памяти +- Порядок таблиц при DROP и DELETE важен, если они связаны fk. Проверьте что у ваз в ДЗ работает diff --git a/doc/lesson04.md b/doc/lesson04.md new file mode 100644 index 000000000000..51b8c51948f3 --- /dev/null +++ b/doc/lesson04.md @@ -0,0 +1,153 @@ +## Материалы 4-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW3 +> SpringMain/ InMemory*Test починим в видео 3, `5-create-mock-test-ctx.patch` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) JdbcUserMealRepositoryImpl + UserMealServiceTest +- POSTGRESQL: BETWEEN CONDITION +- **1-HW3.patch** + +> Новый Postgres драйвер поддерживает Java 8 Date and Time. Преобразования c Timestamp оказались уже не нужны. Мне кажется хороший пример того, что все надо проверять самим, а не верить на слово:) + +- **2-HW3-optional.patch** + +> В JUnit тестах используется кодировка по умолчанию и тесты, если их запускать через maven, не проходят. Решается кастомизацией плагина maven-surefire-plugin + +> В meals добавил составной индекс `INDEX meals_unique_user_datetime_idx ON meals(user_id, date_time)`: + - все запросы в таблицу meals у нас идут с `user_id` + - по полю `date_time` также есть запросы + мы по нему сортируем список результатов + те они- хорошие кандидаты для индексирования. На id как на primary key индекс создается автоматически. + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Вопросы по HW3 + +> Почему в meals дата и каллории у нас не NOT NULL? Это ведь обязательные для заполнения поля. + +Спасибо, недосмотр. Сделаем NOT NULL в HW04. + + +## Занятие 4: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Методы улучшения качества кода +- **3-improve-code.patch** +- Контрактное программирование, Программирование по контракту +- Comparison Preconditions in Java +- QAPlug vs FindBugs +- QAPlug tutorials +- Codacy Home + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Spring: инициализация и популирование DB +- **4-init-and-populate-db.patch** +> - `@Sql` в тестах заменяет `@Before public void setUp()`, те выполняется перед каждым тестом + +- Инициализация базы при старте приложения +- Spring Testing Annotations +- Абстракция Resource + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Починка SpringMain/ InMemory*Test: подмена контекста при тестировании +- **5-create-mock-test-ctx.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. ORM. Hibernate. JPA. +> ВНИМАНИЕ: патч меняет postgres.properties, в котором у вас свои креденшелы к базе. + +> Тесты и приложение ломаются. `UserMealServiceTest` починится после выполнения HW04 (`JpaUserMealRepositoryImpl`) + +- **6-add-jpa.patch** +> Upgrade Hibernate to 5.0.4 needed to add `javax.transaction.jta`, see 5.0.4 requires javax.transaction.SystemException +> Upgrade Hibernate Validator needed to add `javax.el-api`, see javax.validation.ValidationException + +- ORM. +- JPA и Hibernate в вопросах и ответах +- Hibernate: введение и написания Hello world приложения +- Mapping. Описания модели Hibernate (hbm.xml/annotation) +- Hibernate. Другие ORM: TopLink, ElipseLink, EBean (used in Playframework). +- JPA (wiki). JPA (english wiki). JPA Performance Benchmark +- Подключение к проекту Spring ORM и Hibernate +- Отображения наследования объектов на таблицы +- Стратегии генерации PK +- Добавление hibernate-validator. JSR-303 -> JSR-349 +- Описание связей в модели. Ленивая загрузка объекта. +- Конфигурирование JPA. Сканировние Entities. JPA definitions +- Spring expressions: выражения в конфигурации +- Создание JPA Facet. Назначение DataSource. +- Имплементация JpaUserRepository через EntityManagerFactory/ SessionFactory +- Использование TypedQuery и @NamedQuery. Назначение параметров по индексу и имени. +- HQL/ JPQL. +- JPA Criteria API. Unified Queries for Java +- Java 8 Time API Converter + +- **7-add-named-query-and-transaction.patch** +- Транзакция. ACID. Уровни изоляции транзакций. +- Подключаем транзакции. Spring Transaction Management +- readOnly и Propagation.SUPPORTS +- @Transactional в тестах. Настройка EntityManagerFactory + +- Справочник: + - Николай Алименков — Босиком по граблям Hibernate + - Spring transaction propagation tutorial + - Getting Started with JPA + - Validate by RegExp + - Java Persistence + - Hibernate + - Spring Framework transaction management + - Working with JPA Entity Objects + - Стратегии работы с транзакциями: Распространенные ошибки + - Принципы работы СУБД. MVCC + - MVCC + + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Добавляем поддержку HSQLDB + +- **8-add-hsqldb.patch** + +> ВНИМАНИЕ: патч меняет postgres.properties + +> IDEA может `${jdbc.initLocation}` подчеркивать красным - иногда тупит... + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> Есть несколько аналогичных "встроенных" баз данных. H2, HSQLDB, Derby, SQLite. Почему был выбран HSQLDB? + +Просто с ней приходилось работать. HSQLDB и H2 наиболее популярны, в новом курсе по spring-boot планирую использовать H2. + +> Чистого JPA не существует, т.е. это всего лишь интерфейс, спецификация? Говорим JPA, подразумеваем какой-то ORM фрэймворк? А что тогда используют чистый jdbc, Spring-jdbc, MyBatis? MyBatis не реализует JPA? + +JPA- это спецификация, API. Ее реализации- Hibernate, OpenJPA, EclipceLink. Spring-Jdbc, MyBatis, db-utils не реализуют JPA, это обертки к JDBC. Все ORM также реализованы поверх JDBC. + +> В зависимостях maven `hibernate-entitymanager` тянет за собой `jboss-logging`. Как будет происходить логгирование? + +How do you configure logging in Hibernate 4 to use SLF4J: в нашем проекте автоматически подхватывается `logback-classic`. + +-------------------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW04 + + Сделать UserMeal как Hibernate entity + Добавить поддержку LocalDateTime + Имплементировать и протестировать JpaUserMealRepositoryImpl + +- IDEA не понимает в @NamedQuery `.. m.dateTime BETWEEN ..`. На функциональность это не влияет. +- JPA support for Java 8 new date and time API +- What's new in Hibernate 5? + +Optional + + Добавить в тесты UserMealServiceTest функциональность @Rule: + - проверку Exception + - вывод в лог времени выполнения каждого теста + +- JUnit @Rules + +--------------------- +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW04 + +- Тк. JPQL работает с объектами мы не можем использовать userId для сохранения. Можно сделать например так: + + User ref = em.getReference(User.class, userId); + meal.setUser(ref); + + При этом от User нам нужет только id, т.е. реального запроса в базу за юзером не будет- проверьте по логам Hibernate. + +- В JPQL запросах можно писать: `m.user.id=:userId` + +- При реализации `JpaUserMealRepositoryImpl` предпочтительно не использовать try-catch в логике реализации. Но если очень хочется, то ловить только специфичекские эксепшены (пр. `NoResultException`), чтобы, например, при отсутствии коннекта к базе приложение отвечало адекватно. + +- Мы будем смотреть генерацию db скриптов из модели, для корректоной генерации нужно в `UserMeal` добавить `uniqueConstraints` diff --git a/doc/lesson05.md b/doc/lesson05.md new file mode 100644 index 000000000000..851f9df18853 --- /dev/null +++ b/doc/lesson05.md @@ -0,0 +1,124 @@ +## Материалы 5-го урока + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Правки в проекте +- **0-fix.patch** + +> Новый драйвер Postgres работает с Java 8 Time API, но не понимает год 0. + +> Поправили в создании meals поля NOT NULL + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW4 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Разбор вопросов +- Validate by RegExp +- Working with JPA Entity Objects +- Java Persistence/Relationships +- Использование ThreadLocal переменных +- Merge vs Persist +- Видео: работа в ZK с OpenJPA (в чем Hibernate хуже) +- Паттерн- открытие транзакции в фильтре и почему это bad-practice +- Sequence Strategies +- SequenceGenerator/IdentityGenerator in PostgreSql + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW4: JPA. @Rule +- **1-HW4.patch** +- **2-HW4-optional.patch** + +Изменения в проекте: + +- Add support for Java 8 date and time types (JSR-310) +- Stopwatch + +## Занятие 5: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Транзакции +- wiki Транзакция +- readOnly и Propagation.SUPPORTS +- Ресурсы: + - How does Spring @Transactional Really Work + - Стратегии работы с транзакциями: Распространенные ошибки + - Spring @Transactional - isolation, propagation + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Профили Maven и Spring +- **3-profiles-connection-pool.patch** + > Для переключения на HSQLDB необходимо: + > - поменять в окне Maven Projects профиль (Profiles) на `hsqldb` и сделать `Reimport All Maven Projects` (1я кнопка) + > - поменять в UserServiceTest/UserMealServiceTest `@ActiveProfiles(Profiles.HSQLDB)` + +- Using Spring Profiles in XML Config +> Галочка в XML профиле влияет только на отображение в Spring и никак на выполнение кода. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Пул коннектов + +- Выбор реализации пула коннектов: BoneCP, Commons Database Connection Pooling, HikariCP +- Tomcat pool + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Spring Data JPA +- **4-spring-data-jpa.patch** + +- Spring Data JPA +- Замена AbstractDAO: JPA Repositories +- Разрешение зависимостей: Maven BOM [Bill Of Materials] Dependency +- Делегирование. (Spring Data JPA) +- Query methods +- Spring Data – новый взгляд на persistence (JeeConf) + +- Ресурсы: + - Github repositories + - Spring Data JPA Tutorial + - Spring Data JPA with QueryDSL + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Spring кэш +- **5-spring-cache** + +- Кеширование в Spring Framework +- EHCACHE +- В случае проблем с резмещением кэш в `java.io.tmpdir`: http://stackoverflow.com/questions/1924136/environment-variable-to-control-java-io-tmpdir + +- Ресурсы: + - Spring cache Abstraction + - Распределённая система кеша ehcache + - Починка JUnit: один кэш на JVM + +-------------------- + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Домашнее задание HW05 + Имплементировать DataJpaUserMealRepositoryImpl + Разделить реализации Repository по профилям Spring: jdbc, jpa, datajpa + (общее в профилях можно объединять, например ) + Сделать тесты всех реализаций (jdbc, jpa, datajpa) через наследование (без дублирования), + общее из UserMealServiceTest и UserServiceTest вынеси в базовый класс. + Запустить все тесты: mvn test + или в IDEA Maven Lifecycle- test (кнопку skipTest отжать) + +Optional + + Починить MealServlet / SpringMain (добавить профили. + Попробуйте поднять Spring контекст без использования `spring.profiles.activ`). + + Сделать и протестировать в сервисах методы (можно сделать разные варианты, реализация только в DataJpa): + - достать по id пользователя вместе с его едой + - достать по id еду вместе с пользователем + Обращения к DB сделать в одной транзакции + + Почините JDBC реализацию для HSQLDB, можно также разнести ее по профилям. + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> В spring-petclinic DataJpa реализована без проксирования и без доп. классов. В таком виде как у них, spring data смотрится, конечно, намного лаконичней других реализаций, но у нас получилось вдвое больше кода, чем с тем же jpa или jdbc. Плюс только пожалуй в том, что query находятся прямо в репозитории, а не где-то там в другом пакете. Так что получается, spring data лучше подходит для простейших crud без всяких "фишек"? или в чем его достоинство для больших и сложных проектов? + +Достоинство DATA-JPA по сравнению например с JPA: есть типизация, готовые реализации типовых методов CRUD а также paging, data-common. Мы можем переключить реализацию JPA, например, на mongoDb (PagingAndSortingRepository, от которого наследуется JpaRepository находится в `spring-data-common`). +Соответственно его методы будет поддерживаться всеми реализациями spring-data-common, JPA одна из них и пр. Подробнее о них есть в видео Spring Data – новый взгляд на persistence. +Я сам ввел дополнительное проксирование для устранения минусов подхода DATA-JPA: невозможность дебага и привязка к интерфейсу JpaRepository. +Приложение становится больше, но выигрыш этого стоит. Можно не делать дополнительный класс и перенести его логику в слой сервиса. + +> Почему мы для InMemory не сдалели отдельного профиля? Почему их не удалить вообще? + +Реализация InMemory является примером как в test делать моки с подменой контекста. Для них сделали отдельный `mock.xml` и запускаемый проект ничего не должен о них знать. У нас учебный проект, в котором 4 реализации репозиториев, в реальном такого не будет. + +--------------------- +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW05 +- Для того, чтобы не запускались родительские классы тестов нужно сделать их `abstract` +- Для IDEA не забудте выставить в `spring-db.xml` справа вверху в Change Profiles... профили, например `datajpa, postgres` +- Общие части для всех в `spring-db.xml` можно оставить как есть без профилей, но до первого `Материалы 6-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW5 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW5: Spring Profiles. Spring Data JPA +- **01-HW5-data-jpa.patch** +- **02-HW5-profiles.patch** +> Для IDEA не забудте выставить Spring Profiles в `spring-db.xml`: нарпимер `datajpa, postgres` + +- **3-HW5-tests.patch** +> `DbTest` переименован в `AbstractServiceTest` и сюда перенесли `@ActiveProfiles(Profiles.POSTGRES)` + +> Т.к. HSQLDB не понимает LocalDateTime, разделил `JdbcUserMealRepositoryImpl` на `Java8JdbcUserMealRepositoryImpl` и `TimestampJdbcUserMealRepositoryImpl` +- Spring 4 Conditional. Зайдите в исходники @Profile и посмотрите его реализацию. + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW5: Optional +- **4-HW5-Optional-fix-servlet-main.patch** +- Spring Profiles +> Добавлены константы `Profiles.ACTIVE_DB`, `Profiles.DB_IMPLEMENTATION` + +- **5-HW5-Optional-fetch-join.patch** +- JPA JoinColumn vs mappedBy +- Unidirectional OneToMany +> Добавлены проверки и тесты на `NotFound` для `UserMealService.getWithUser` и `UserService.getWithMeals` + + +## Занятие 6: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Кэш Hibernate +- **6-hibernate-cache.patch** +> JdbcUserServiceTest поломася. Требуется починить в HW6 + +- Уровни кэширования Hibernate +- Hibernate Cache. Практика +- Hibernate - Caching +- Починка тестов: инвалидация кэша Hibernate +- Ресурсы: + - Подключение кэша Hibernate 2-го уровня + - JPA2 @Cacheable vs Hibernate @Cache + - Распределённая система кеша ehcache + - Cache mappings + - How does Hibernate Query Cache work + - Pitfalls of the Hibernate Second-Level / Query Caches + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Spring Web +- **7-spring-web.patch** + +- Добавляем в проект веб зависимости +- Поднятие контекста Spring в веб приложении. ServletContextListener. Задание дефолтного профиля. +- Получение контекста Spring в веб-контейнере +- Servlet Lifecycle + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. JPS, JSTL, internationalization +- **8-jsp-jstl-i18n.patch** +- jsp:include +- Имплементируем UserController. Паттерн MVC + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Динамическое изменение профиля при запуске. +> -Dspring.profiles.active="datajpa,postgres" + +- Set profiles in Spring 3.1 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Конфигурирование Tomcat через maven plugin. Jndi-lookup. +> `javax.el-api` scope изменил на `provided` (`el-api.jar` является частью Tomcat) + +Запуск из коммандной строки (`clean` нужен для удаления из сборки зависимости `javax.el-api`): + + mvn clean org.apache.tomcat.maven:tomcat7-maven-plugin:run + +- **9-tomcat-pool-jndi.patch** + +- Tomcat Maven Plugin +- Tomcat JNDI Resources + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Spring Web MVC +- **10-spring-webmvc.patch** + +- Spring Web MVC +- Паттерн Front Controller +- Добавляем DispatcherServlet и MVC application context. +- Иерархия контекстов в Spring Web MVC +- Сценарий обработки запроса. HandlerMappings +- View resolving: прячем jsp под WEB-INF. +- HandlerMapping: SimpleUrlHandlerMapping, BeanNameUrlHandlerMapping +- Маппинг ресурсов. +- Ресурсы: + - Spring MVC Tutorial + - Spring MVC hello world + - Web MVC framework + - Special bean types in the WebApplicationContext + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. Spring Internationalization +- **11-spring-i18n.patch** + +> Убедитесь что в настройках IDEA кодировка везде UTF-8 + +> Проверьте, что файлы локализации у вас в UTF-8 (в IDEA справа внизу в статусе есть кодировка и можно перекодировать). + +- Spring Internationalization or i18n +- Reloadable MessageSources +- nginx: Serving Static Content + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW06 + - Починить InMemory и Jdbc тесты + + - Добавить локализацию и jsp:include в meal*.jsp + + Починить работу meals: перенести функциональность MealServlet в контроллеры, + разнести запросы на update/delete/.. по разным методам + (можно делать по аналогии с ru.javawebinar.topjava.web.RootController#setUser, + аннотации на параметры и адаптеры для LocalDate\Time мы введем позже). + +Optional + + Добавить еще одну роль к админу (у него будет 2 роли: ROLE_USER, ROLE_ADMIN), + добавить проверку ролей в тесты на User, починить тесты Jpa (дублирование) + + Починить тесты на роли Jdbc: + добавить транзакционность (DataSourceTransactionManager) и доставание ролей в JdbcUserRepositoryImpl + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Кэш hibernate надстраивается над ehcache или он живет самостоятельно? + +- Understanding Hibernate Caching: +Hibernate supports following open-source cache implementations out-of-the-box: EHCache (Easy Hibernate Cache), OSCache (Open Symphony Cache), Swarm Cache, and JBoss Tree Cache. + +> Где конфигурится интернализация для jstl? Т.е. файл, где здаются app, app_ru.properties. Достаточно указать в страницах бандл и путь в ресурсы? + +`` означает что ресурсы будут искаться в `classpath:messages\app(_xx)/properties`: +Tag setBundle: fully-qualified resource name, which has the same form as a fully-qualified class name. +После сборки проекта maven их можно найти в target\classes или target\topjava\WEB-INF\classes. + + +> Отлично, что она все пишет на том языке, который пришел в хидере запроса. А если я хочу выбрать? + +Используется JSTL Format Tag fmt:setLocale. Мы переключимся на Spring i18n и переключение локалей будет реализовано в нем. + +> Мы создаем бин, где получаем dataSource по имени ``. +Но там не указан класс, как в других dataSource? Получается по имени jdbc/topjava нам уже отдает готовый обьект dataSource и мы как бы помещаем его в бин? + +Здесь используется namespace `jee:jndi-lookup`, который прячет под собой классы реализации. Сравните в http://habrahabr.ru/post/232381/ раздел Spring namespace configuration инициализацию базы через namespace jdbc и через классы. + +> В плагине прописан профиль `tomcat,datajpa`, а в web.xml `postgres,datajpa`. +Какой же реально отрабатывает? + +См видео урока "Динамическое изменение профиля при запуске". В плагине мы задаем параметры JVM запуска Tomcat + +> Почему мы не используем элемент `` в `spring-db.xml`? + +В проекте у нас сейчас 2 Spring контекста: `spring-mvc.xml (см. web.xml, DispatcherServlet)` и родительский `spring/spring-app.xml + spring/spring-db.xml (web.xml, contextConfigLocation)`. +Грубо: 2 мапы, причем для mvc доступно все что есть в родителе, но родитель не знает об mvc. Те spring-db.xml не является отдельным самоcтоятельным контекстом и достаточно того, что `` у нас есть в `spring-app.xml`. + +> В _ehcache.xml_ чем _ getAll()` в `UserServiceImpl`. +_ru.javawebinar.topjava.model.User_ - имя региона, которое использует Hibernate для кэширования содержимого таблицы _USERS_. +Мы можем оставить настройки по умолчанию, либо задать свои. + +> A `@NamedQuery` или `@Query` подвержены кешу запросов? Т.е. если мы поставим _USE_QUERY_CACHE_value="true" будет Hibernate их кешировать? + +Чтобы запрос кэшировался, кроме true в конфигурации нужно еще явно выставить запросу _setCacheable_ (http://vladmihalcea.com/2015/06/08/how-does-hibernate-query-cache-work/). По поводу кэширования @NamedQuery нашел _@QueryHint_: https://docs.jboss.org/jbossas/docs/Clustering_Guide/5/html/ch04s02s03.html + +> Почему messages мы кладем в config и используем system environment? разве так делают в реальном проекте? не будешь же вписывать на сервере эти переменные каждый раз, если проект куда-то будет переезжать. Можно по другому, кроме systemEnvironment['TOPJAVA_ROOT'] задать путь от корня проекта? + +1. messages нам нужны в runtime (при работе приложения). Проект к собранному и задеплоенному в Tomcat war отношения никакого уже не имеет и на этом сервере он обычно не находится. Если ресурсы нужны только при сборке и тестировании, то путь к корню для одномодульного maven проекта можно задать как ${project.basedir}, но для многомодульного проекта (а все реальные проекты многомодульные) это путь к корню своего модуля. +2. В "реальном приложении" делается совершенно по разному: + - нести с собой в класспасе, но ресурсы нельзя будет динамически (без передеплоя) обновлять + - класть в war (не в класспас) и обновлять в развернутом TOMCAT_HOME/webapps/[appname]/... + - класть в зафиксированное определенное место (нарпимер в home: ~ или в путь от корня /app/congig). Можно задавать фиксированный пусть в пропертях профиля maven и фильтровать ресурсы (maven resources), чтобы они попали в проперти проекта. + - делать через переменную окуржения, как у нас + - задавать в параметрах запуска JVM как системную переменную через -D.. + - располагать в преференсах (для unix это home, для windows- registry): http://java-course.ru/articles/preferences-api/ + - держать настройки в DB + + Часто в одном приложении используют несколько способов для разных видов конфигураций. + +--------------------- +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW06 +- Неверная кодировка UTF-8 с Spring обычно решается фильтром `CharacterEncodingFilter`: +``` + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + +``` +- Функциональность еды в контроллерах должна быть в пакете meal и сделайте как для User - наследование от абстрактного класса. +- Если неправильно формируется url относительно контекста приложения посмотрите http://stackoverflow.com/questions/4764405/how-to-use-relative-paths-without-including-the-context-root-name +- JdbcUserRepositoryImpl.update - роли также должны обновлялться +- JdbcUserRepositoryImpl.getAll - постараться реализовать так, чтобы для 1000 пользователей был меньше, чем 1001 запрос (1 или 2). +- Для контроллера action уже не нужен - запросы нужно подкорректировать diff --git a/doc/lesson07.md b/doc/lesson07.md new file mode 100644 index 000000000000..8fdc15144876 --- /dev/null +++ b/doc/lesson07.md @@ -0,0 +1,134 @@ +## Материалы 7-го урока + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Небольшие правки от Codacy +- **0-codacy-fix.patch** + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW6 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW6 +- **01-HW6-fix-tests.patch** +- **02-HW6-jsp-i18n.patch** +- Root path in JSP + +- **03-HW6-meals.patch** +- **04-HW6-fix-relative-url-utf8.patch** +- Relative paths in JSP +- Spring redirect: prefix + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW6 Optional +- **05-HW6-optional-add-role.patch** +- **06-HW6-optional-jdbc.patch** +- Локальные классы + +## Занятие 7: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Auto generate DDL. +- **07-generate-ddl.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Тестирование Spring MVC +- **08-controller-test.patch** + +> В MockMvc добавился CharacterEncodingFilter + +> Закомментировали генерацию DDL + +- Hamcrest +- Unit Testing of Spring MVC Controllers + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. REST контроллеры +- **09-rest-controller.patch** + +- JSON (JavaScript Object Notation +- Understanding REST +- JAX-RS vs Spring MVC +- Request mapping +- Ресурсы: + - RESTful API для сервера – делаем правильно (Часть 1) + - RESTful API для сервера – делаем правильно (Часть 2) + - И. Головач. RestAPI + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Тестирование REST контроллеров. Jackson. +- **10-rest-test-jackson.patch** + +- http://stackoverflow.com/questions/34721851/spring-4-2-3-and-fasterxml-jackson-2-7-0-are-incompatible +- https://github.com/FasterXML/jackson-databind + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. jackson-datatype-hibernate. Тестирование через матчеры. +> Устройство матчеров может показаться сложным. Как это работает можно разбираться, только если самому на их основе что-то имплементировать. Мы используем Spring MVC и Hibernate не заботясь о том, насколько они сложные в реализации. Использование матчеров также достаточно просто. + +- **11-jackson-hibernate.patch** + +- Jackson JSON Serializer & Deserializer +- Сериализации hibernate lazy-loading с помощью jackson-datatype-hibernate + +- **12-jackson-jsr310.patch** +> Переводим сериализацию-десеарелизацию LocalDateTime на jsr310 и стандартный формат ISO-8601 +- jackson-datatype-jsr310 + +- **13-test-with-matcher.patch** +- Тестируем Spring Rest контроллеры: проверка JSON-содержимого ответа через собственный ResultMatcher + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Тестирование через SoapUi. UTF-8 +- **14-soapui-utf8-converter.patch** +- Решение проблемы с UTF-8 в StringHttpMessageConverter +- SoapUi + +> Импортировать проект в SoapUi из config\Topjava-soapui-project.xml + +> Response смотреть в формате JSON. + +- Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text +- ResponseBody and UTF-8 + +- **15-test-fix.patch** +> Поменял `artifactId` на `jackson-datatype-hibernate5` (был `hibernate4`) + +> При запуске из Maven проваливались тесты на `AdminRestControllerTest`: тесты сервиса оставляли после себя базу и кэш не в исходном состоянии. +Сделал сброс кэша и инициализацию базы не до, а после тестов. + +> Убрал лишний `import` + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW07 + + Добавить тесты контроллеров: + UserMealControllerTest для /meals по аналогии с UserControllerTest + ResourceControllerTest для style.css (status и ContentType) + + Реализовать UserMealRestController и протестировать его через UserMealRestControllerTest + + В параметрах getBetween принимать LocalDateTime (конвертировать через Spring, см. ссылку ниже), + а передавать в тестах в формате ISO_LOCAL_DATE_TIME (например '2011-12-03T10:15:30'). + Следите чтобы url в тестах совпадал с параметрами в методе контроллера. + Можно добавить логирование для проверки маршрутизации. + +- @DATETIMEFORMAT WITH JAVA 8 DATE-TIME API + +Optional + + Заменить @DATETIMEFORMAT на свой LocalDateTime конвертор или форматтер. + + Протестировать UserMealRestController через SoapUi или curl. + +- Introduction to Spring Converters and Formatters +- Difference between Spring MVC formatters and converters +- Написание HTTP-запросов с помощью Curl +(для Windows можно использовать Git Bash) + + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> После патча 7_13 тест ProfileRestControllerTest.testGet нам оставляет поле meals:null хотя мы задали чтоб не сериализовались null поля. + Почему его оставляет сериализатор? (конечно если выставим NON_EMPTY то уберет) + +Там lazy коллекция, а не null. Думаю, что Hibernate4Module уже после логики NOT_NULL (JacksonObjectMapper) ее обнуляет. + +> В реализации Jdbc нет @Transactional. Но мы же отдельно сохраняем user и его роли в разные таблицы. Может случиться, что юзер сохраниться, а роли нет? Или наоборот? + +Просто результат этих операций не атомарный. Если случится, что между этими двумя этими записями например вклинится удаление юзера, то сохранение ролей провалится. Те очень редкая ситуация. + +> При выполнении тестов через MockMvc никаких изменений на базе не видно, почему оно не сохраняет? + +У нас в тестах контроллеров стоит над классом @Transactional - это означает, что тесты идут в транзакции и после каждого теста JUnit делает rollback базы. + +--------------------- +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW07 +- для тестирования в UserMealRestController списка UserMealWithExceeded сделайте ModelMatcher<UserMealWithExceed, String> +- Ошибка в тесте _Invalid read array from JSON_ обычно расшифровывается немного ниже: читайте внимательно. diff --git a/doc/lesson08.md b/doc/lesson08.md new file mode 100644 index 000000000000..45624e45066c --- /dev/null +++ b/doc/lesson08.md @@ -0,0 +1,148 @@ +## Материалы 8-го урока + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW7 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW7 +- **1-HW07-controller-test** +- **2-HW07-rest-controller.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW7_ Optional +- **3-HW07-formatter.patch** +- **4-HW07-soapui-curl.patch** +- IDEA: Tools->Test RESTful Web Service +- Postman + +## Занятие 8: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. WebJars. jQuery and JavaScript frameworks +- **5-webjars.patch** + +> УБРАЛ из проекта Dandelion обертку к datatables: +> - не встречал нигде, кроме Spring Pet Clinic; +> - привязан к старому (legacy) API datatables; +> - поддержка работы с datatables через Dandelion оказалось гораздо более трудоемкое, +> - чем работа с плагином напрямую. + +- Подключение веб ресурсов. WebJars. +- Introducing WebJars +- Document Object Model (DOM) +- What is the DOM? +- jQuery +- Is jquery a javascript library or framework +- DataTables +- Working with jQuery DataTables + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Bootstrap +- **6-bootstrap.patch** +> В патч картинки не берутся, положить вручную icon-meal.png в `\src\main\webapp\resources\images\icon-meal.png` + +- Подключаем Bootstrap. Форматируем JSP. +- Twitter Bootstrap Tutorial +- Bootstrap 3 Tutorial +- Видео: уроки по Bootstrap 3 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. AJAX. Datatables. jQuery +- **7-ajax-datatables.patch** +> JSP полезны, если надо с сервера отдать статический html с серверной логикой (условия, циклы), сформированный на основе модели. +Для динамической отрисовки таблицы мы будем использовать REST и JSON на 9м уроке (работа с datatables через Ajax). + +> HW8 Optional - перейти на новый dataTables API + +- AJAX. +- Событие $(document).ready. +- jQuery для всех. +- jQuery для начинающих. AJAX. +- jQuery для начинающих. Селекторы. +- jQuery API +- Редактирование таблицы на основе модального окна Bootstrap. +- Javascript плагины для Bootstrap +- DataTables API + + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Notifications +- **8-notification.patch** +- Обработка ajaxError. +- jQuery notification + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Добавление к REST контроллерам Spring Security +> Правка к видео: путь в intercept-url должен быть полный - pattern="/rest/admin/**" + +> В Spring Security 4.x по умолчанию включен csrf. Будем его проходить на 10м занятии. + +- **9-add-security.patch** + +- Spring Security + +- Протокол AAA +- Методы аутентификации. +- Basic access authentication + +- Использование ThreadLocal + +- Добавляем в проект spring-security и security filter +- Конфигурируем security context для ресурсов и REST +- Тестируем Security REST через SoapUI/ curl/ Postman/ Testing RESTful Web Services + Generate Authorization Header +``` +curl -v -H 'Authorization: Basic dXNlckB5YW5kZXgucnU6cGFzc3dvcmQ=' http://localhost:8080/topjava/rest/profile/meals +``` + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW08 + Перевести mealList на datatables (mealList.jsp, UserMealAjaxController). + + Реализовать добавление записи еды через модальное окно Bootstrap + и удаление/добавление еды по ajax (без редактирования). + + При вставке данных по AJAX пропадает все JSP форматирование, + чинить перерисовку НЕ надо: отрисовываем таблицу через jsp. + Следующий урок- будем делать datatable по AJAX и форматирование на стороне клиента. + +Optional. + + Реализовать enable/disable User через checkbox в userList.jsp с сохранением в DB + + Перевести работы фильтра на AJAX (при обновлении данных таблицы учитывать поля формы фильтрации). + Попробуйте после модификации таблицы (например добавлении записи) обновлять ее также с учетом фильтра. + + Перейти на новый dataTables API + +Ресурсы + +- Converting parameter names for 1.10 +- dataTable() vs. DataTable() + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> Что делает код? +``` +$('.delete').click(function () { + deleteRow($(this).attr("id")); +}); +``` + +На все элементы DOM с классом `delete` вешается обработчик события `click` который вызывает функцию `deleteRow`. Классы в HTML разделяются через пробел. + +> Тянет ли bootstrap за собой jQuery? + +Bootstrap зависит от jQuery + +> А где реально этот путь `classpath:/META-INF/resources/webjars`? Т.е. начиная от положения пакета, выходит? + +Внутри подключаемых webjars ресурсы лежат по пути `/META-INF/resources/webjars/...` Не поленитесь посмотреть на них через `Ctrl+Shift+N`. +Все подключаемые jar попадают в classpath и ресурсы доступны по этому пути. + +> У меня она лежит внутри `.m2\repository\org\webjars\`. С чем это может быть связано? + +Maven скачивает все депенденси в local repository, который по умолчанию находится в `~/.m2`. +Каталог по умолчанию можно переопределить в `APACHE-MAVEN-HOME\conf\settings.xml`, элемент `localRepository`. + +> WEBJARS лежат вообще в другом месте WEB-INF\lib*. Биндим mapping="/webjars/*" например на реальное положение jar в ware, откуда spring знает где искать наш jquery ? + +В war в `WEB-INF/lib/*` лежат все jar, которые попадают к classpath. Spring при обращении по url `/webjars/` ищет по пути биндинга `` +по всему classpath (то же самое как распаковать все jar в один каталог) в `META-INF/resources/webjars/`. В этом месте во всех jar, которые мы подключили из webjars лежат наши ресурсы. + +> Как можно в браузере сбросить введенный пароль базовой авторизации? + +Проще всего делать новый запрос в новой анонимной вкладке (`Ctrl+Shift+N` в Chrome) + +> Почему при запросе по rest с креденшелами admin возвращается еда user? + +Для запроса еды используется LoggedUser.id(), посмотрите что там. diff --git a/doc/lesson09.md b/doc/lesson09.md new file mode 100644 index 000000000000..7e9911dd9601 --- /dev/null +++ b/doc/lesson09.md @@ -0,0 +1,128 @@ +## Материалы 9-го урока + +- **Браузер кэширует javascript и css. Если изменения не работают, обновите приложение в браузере по Ctrl+F5** +- **При удалении файлов не забывайте делать clean: `mvn clean package`** + + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Правка кода +- **0-fix.patch** +- Поправил стиль и модификаторы доступа +- Поменял версию `datetimepicker` из-за проблем с DateFormatter +- Убрал `contentType` в REST тестах delete + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW8 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW8 +- **1-HW8.patch** + +> Поправка к видео: в гриде bootstrap 12 колонок. + +- Grid system +- Bootstrap description +- Bootstrap forms + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW8 Optional (enable/disable user) +- **2_HW8_enable_disable.patch** + +> Вместо использования id и селектора сделал обработчик `onclick` +- Введение в браузерные события + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. HW8 Optional (new API) +- **3_HW8_newApi.patch** + +> Новое API: поменялось как обновление таблицы, так и ее формат параметров. +- Converting parameter names for 1.10 + +## Занятие 9: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Spring Binding +- **4_binding.patch** + +> Move `ru.javawebinar.topjava.util.UserMealsUtil.DEFAULT_CALORIES_PER_DAY` to `ru.javawebinar.topjava.util.UserUtil` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Реализация update +- **5_update.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Spring Validation +- **6_validation.patch** +- Spring Validation. +- Bean Validation +- Валидация формы по AJAX. +- JSR-303, 349 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Перевод DataTables на Ajax +- **7-datatable-via-ajax.patch** +- DataTables Options + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Форма логина / логаут. +- **8-min-form-login.patch** +- Минимальный form-login +- Migrating <form-login> + + Вопросы: + Почему при логине как admin еда отдаются для user? + Почему при логине как user не отображается список пользователей? + Почему еда не редактируется и не удаляется? + +- **9-jsp-form-login.patch** +- Собственный form-login + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Реализация собственного провайдера авторицазии. +- **10_auth_via_user_service.patch** +> Изменения в проекте: вместо `LoggedUser implements UserDetails` сделал `LoggedUser extends org.springframework.security.core.userdetails.User` + +- UserDetailsService Implementations + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. Принцип работы Spring Security. Проксирование. +- Принцип работы Spring Security +- Типы проксирования +- Dynamic Proxy API +- Конфликт проксирования Data Repository +- Security фильтры + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 8. Spring Security Test +- **11-spring-security-test.patch** +> Поправил RootControllerTest. + +- Spring Security Test +- Интеграция со Spring MVC Test +- HttpBasic авторизация +- Тестирование контроллеров с помощью MockMvc (без spring-security-test) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 9. Cookie. Session. +- HTTP cookie +- Under what conditions is a JSESSIONID created? +- Tomcat Session Serialization + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW9 + + Реализовать для meal Binding/ Update/ Validation. Проверить работу при пустом значении Calories. + Перевести mealList.jsp на работу по ajax. Стиль записи таблицы сделать в зависимости от exceeded. + Починить meals тесты + +Optional + + Подключить datetime-picker + +- DateTimePicker jQuery plugin + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> В UserMealRestControllerTest нам нужны калории обязательно для LoggedUser? + +В тестах контроллера мы: +- заполняем правильно header креденшелами для базовой авторизации через userHttpBasic +- выполняем запрос +- поднятое тестами приложение авторизует юзера и заполняет LoggedUser +- выполняет запрос и возвращает ответ для залогиненного юзера +- авторизационный фильтр чистит свой контекст, LoggеdUser safeGet() возвратит null. + +Поэтому в тестах getCaloriesPerDay берите у тестового юзера, которым авторизовалась. + +> В `login.jsp` есть форма `<form:form action="spring_security_check" ..> Где такой url используется? + +Это стандартный url для авторизации в spring-security. Он его и обрабатывает. + +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW09 + +- Чтобы поле `UserMeal.dateTime` сбиндилось кроме аннотации нужно изменить в форме `mealList.jsp` `name="datetime"` на `"dateTime"` diff --git a/doc/lesson10.md b/doc/lesson10.md new file mode 100644 index 000000000000..33f54e630997 --- /dev/null +++ b/doc/lesson10.md @@ -0,0 +1,105 @@ +## Материалы 10-го урока + +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Правка кода +- **0-fix.patch** +- Добавил в fail детали `jqXHR.responseJSON` +- Упростил callback для `updateTableByData` +- Поправил html и import + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW9 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) HW9 +- **1_HW9_ajax.patch** + +> Добавил UserMealTo (раньше был вместе с entity) + +- **2_HW9_test.patch** +- **3_HW9_datetimepicker.patch** + +> Изменил формат ввода даты в форме без 'T' + + - DateTimePicker jQuery plugin + - Datetimepicker and ISO-8601 + +## Занятие 10: + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Spring Security Taglib. Method Security Expressions. +- **04_secure_tag_annotation.patch** +- Spring Security Taglib. +- Method Security и Method Security Expressions. Зависимость от контекста mvc/parent. + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Interceptors. Редактирование профиля. JSP tag files. +- **05_interceptor.patch** +- Добавляем профиль. Spring interceptors. +- **06_profile_jsptag.patch** + +> ProfileRestController.update(user) изменился на userTo (нельзя, например, изменять себе роли). + +- Делаем jsp tag для ввода поля формы. +- Spring MVC Form Validation + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Форма регистрации. +- **07_registration.patch** + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Обработка исключений в Spring. +- **08_not_found_404.patch** +- Используем HTTP status code +- RESTful resource not found +- REST API 404 +- **9_global_exception.patch** +- Global Exception Handling +- **10_controller_based_exception.patch** + +> Т.к Rest и Ajax контроллеры уже отнаследованы от абстрактных контроллеров, сделал "подмешивание" в них ExceptionInfoHandler с помощью default методов интерфейса. + +- Примесь (mixin) +- Mixin в Java 8 +- Сериализация Exception в JSON +- Exception Handling на уровне контроллера + + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. Encoding password +- **11_password_encoding.patch** +- Password Encoding + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. Межсайтовая подделка запроса (CSRF). +- **12_csrf.patch** +- Межсайтовая подделка запроса (CSRF) +- Using Spring Security CSRF Protection +- Ajax and JSON Requests +- Stateless CSRF protection +- Ресурсы: + - Spring Security 4 + CSRF + - Are JSON web services vulnerable to CSRF attacks + - Правило ограничения домена (SOP) + - Cross-origin resource sharing (CORS) + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW10 + Сделать валидацию в AdminAjaxController/UserMealAjaxController через ValidationException + (вернуть клиенту ErrorInfo и статус HttpStatus.UNPROCESSABLE_ENTITY) + + Сделать обработку ошибки при дублирования email (User with this email already present in application) для: + регистрации / редактирования профиля пользователя + добавления / редактирования пользователя в списке + + +Optional. + + Сделать в приложении выбор локали (см. http://topjava.herokuapp.com/) + + Починить UTF-8 в редактировании профиля и регистрации (в windows). Подумайте, почему они поломались. + +Ресурсы: +- Spring MVC internationalization sample +- Spring 4 MVC Internationalization + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> В чем отличие между аннотоацией `@PreAuthorize("hasRole('ROLE_ADMIN')")` и конфигурацией в jsp: ``, `` ? + +Анотация `@PreAuthorize` обрабатывается Spring анологично `@Transactional`, `@Cacheable` - класс проксируется и до-после вызова метода добавляется функциональность. +В данном случае перед вызовом метода проверяются роль залогиненного юзера. JSTL тэг `authorize` выполняет проверку условия в залогиненном юзере внутри jsp. + +> Какой из `SessionLocaleResolver` и `CookieLocaleResolver` а какой лучше использовать для локализации? + +Я взял `CookieLocaleResolver`. Он хранит информацию в куках, которые хранятся долго у клиента на компьютере. Локаль в сессии проподает вместе с логаутом. diff --git a/doc/lesson_final.md b/doc/lesson_final.md new file mode 100644 index 000000000000..c62b38533427 --- /dev/null +++ b/doc/lesson_final.md @@ -0,0 +1,108 @@ +## Материалы заключительного занятия + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Refactoring & Fix +- **0-refactoring-fix.patch** + - Добавились поля serialVersionUID + - Рефакторинг REST контроллеров и обработка ошибок по `@ControllerAdvice(annotations = RestController.class)` + - Правка UTF-8 в редактировании профиля и регистрации (порядок `encodingFilter` и `springSecurityFilterChain` фильтров) + - Удалился `contentType` для get в тестах REST контроллеров (`contentType` нужен только для `@RequestMapping(consumes=..`) + - Добавилась авторизация в `RootControllerTest` + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW10 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW10 +- **1-validation-exception.patch** + +> Добавилась локализация `duplicate_email` + +> Обработка валидации формы для Ajax контроллеров сделана через `org.springframework.validation.BindException` + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW10 Optional: change locale +- **2-change-locale.patch** + +> еще вариант смены локали в lang.jsp вместо javascript: `href=${requestScope['javax.servlet.forward.request_uri']}?lang=..` + +- spring-message vs fmt-message +- Bootstrap Navbar + +## Заключительное занятие + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Деплой приложения в Heroku. +- **3-heroku.patch** + +> В `DataJpaUserRepositoryImpl` добавилась защита от удаления-модификации Admin/User + +- Деплой Java Spring приложения в PaaS-платформу Heroku +``` +Config Vars + ERROR_PAGE_URL=... + TOPJAVA_ROOT=/app + +Datasources advanced + ssl=true + sslfactory=org.postgresql.ssl.NonValidatingFactory +``` +- Ресурсы: + - PaaS-платформа Heroku + - Конфигурирование приложения для запуска через Tomcat-based Java Web + - Конфигурирование DataSource profile для Heroku + - Интерграция с IDEA + - Find your Platform as a Service + - Getting Started with Java on Heroku + - Managing Your SSH Keys + - Deploy your application to Heroku + - Развертывание приложений Java с помощью PaaS от Heroku + - A Java Developer’s Guide to PaaS + - A Simple PaaS Comparison Guide (With the Java Dev in Mind) + - Java PaaS shootout + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание по проекту: + + Код для деплоя в Heroku был включен в проект, из за чего некоторые тесты перестали проходить. + Найти этот код и проверить что без него все работает. + + Сделать / обновить резюме + Отдать на ревью мне или группе + Заполнить форму "разработчик Java" + +- Составление резюме, подготовка к интервью, поиск работы +- Форма соискателя "Разработчик Java". + +## Возможные доработки приложения: +- Перевести UI на angular.js / Vaadin elements /GWT /GXT /Vaadin / ZK/ [Ваш любимый фреймворк].. +- Перевести проект на Spring Boot +- Перевести шаблоны с JSP на Thymeleaf +- Для редактирования паролей сделать отдельный интерфейс с запросом старого пароля и кнопку сброса пароля для администратора. +- Сделать авторизацию в приложение по OAuth 2.0 (Spring Security OAuth, +VK auth, github oauth, ...) +- Сделать подтверждение регистрации пользователя по email c возможностью восстановления пароля. +- Интергировать проект с Identity API Stormpath +- Сделать отображение еды постранично, с поиском и сортировкой на стороне сервера. +- Перевод проекта на https +- Сделать desktop/mobile приложение, работающее по REST с нашим приложением. +- Локализация сообщений об ошибках, показ ошибок в форме редактирования таблицы в стиле JSP. +- Limit login attempts example +- Любая на ваше усмотрение. + +**Если у Вас появилось желание доработать приложение и распространить код для других участников- +связывайтесь со мной для ревью и его оформления.** + +## Все материалы проекта + +### Ресурсы по Проекту +- Плагин Markdown Preview для Хрома +- Уроки Bootstrap 3 +- Spring at tutorialspoint +- Articles in Spring +- Learn Spring on Baeldung +- Spring Framework + Reference Documentation +- Hibernate Reference Documentation +- Java Course (книга 2) +- Справочник «Паттерны проектирования» +- Catalog of Patterns of Enterprise Application Architecture + +# ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Короткий опрос для всех участников проекта + +### Приглашаю всех в группу выпускников 'Online Projects' в skype (перед добавлением залогинтесь в аккаунт skype). +Это общение по теме java, трудоустройство, зарплаты и пр. Добавиться можно по ссылке или напиши мне в скайп и я добавлю. From f811a97bafc2aa395d3393f9b4aa83b0675da962 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Thu, 26 May 2016 21:51:25 +0500 Subject: [PATCH 081/166] fix bug - restore removed servlet --- .../javawebinar/topjava/web/MealServlet.java | 93 +++++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 42 +++++++++ 2 files changed, 135 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java 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..ccbd1f032007 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,93 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.util.TimeUtil; +import ru.javawebinar.topjava.web.meal.UserMealRestController; + +import javax.servlet.ServletConfig; +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.util.Objects; + +public class MealServlet extends HttpServlet { + private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); + + private UserMealRestController mealController; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + mealController = springContext.getBean(UserMealRestController.class); + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String action = request.getParameter("action"); + if (action == null) { + final UserMeal userMeal = new UserMeal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); + + if (request.getParameter("id").isEmpty()) { + LOG.info("Create {}", userMeal); + mealController.create(userMeal); + } else { + LOG.info("Update {}", userMeal); + mealController.update(userMeal, getId(request)); + } + response.sendRedirect("meals"); + + } else if (action.equals("filter")) { + LocalDate startDate = TimeUtil.parseLocalDate(resetParam("startDate", request)); + LocalDate endDate = TimeUtil.parseLocalDate(resetParam("endDate", request)); + LocalTime startTime = TimeUtil.parseLocalTime(resetParam("startTime", request)); + LocalTime endTime = TimeUtil.parseLocalTime(resetParam("endTime", request)); + request.setAttribute("mealList", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/mealList.jsp").forward(request, response); + } + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String action = request.getParameter("action"); + + if (action == null) { + LOG.info("getAll"); + request.setAttribute("mealList", mealController.getAll()); + request.getRequestDispatcher("mealList.jsp").forward(request, response); + } else if ("delete".equals(action)) { + int id = getId(request); + LOG.info("Delete {}", id); + mealController.delete(id); + response.sendRedirect("meals"); + } else { + final UserMeal meal = "create".equals(action) ? + new UserMeal(LocalDateTime.now(), "", 1000) : // create + mealController.get(getId(request)); // update + request.setAttribute("meal", meal); + request.getRequestDispatcher("mealEdit.jsp").forward(request, response); + } + } + + private String resetParam(String param, HttpServletRequest request) { + String value = request.getParameter(param); + request.setAttribute(param, value); + return value; + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id"), "parameter id must not be null"); + return Integer.valueOf(paramId); + } +} 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..4aa6c14ce7dc --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import javax.servlet.ServletConfig; +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); + + private AdminRestController adminController; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + adminController = springContext.getBean(AdminRestController.class); + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + int userId = Integer.valueOf(request.getParameter("userId")); + LoggedUser.setId(userId); + response.sendRedirect("meals"); + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + LOG.debug("getAll"); + request.setAttribute("userList", adminController.getAll()); + request.getRequestDispatcher("/userList.jsp").forward(request, response); +// response.sendRedirect("userList.jsp"); + } +} From 139d0e58f34a032dd8ede75892f9561717604647 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 19:14:31 +0500 Subject: [PATCH 082/166] 7-08-controller-test --- pom.xml | 14 ++++ src/main/resources/spring/spring-db.xml | 2 + .../topjava/web/AbstractControllerTest.java | 64 +++++++++++++++++++ .../topjava/web/user/UserControllerTest.java | 34 ++++++++++ 4 files changed, 114 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java diff --git a/pom.xml b/pom.xml index 51ef9c95038c..397dec903686 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ 5.2.4.Final + 1.3 2.10.1 @@ -132,6 +133,19 @@ junit ${junit.version} test + + + hamcrest-core + org.hamcrest + + + + + + org.hamcrest + hamcrest-all + ${hamcrest.version} + test javax.servlet diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index a7729a6479d5..df6fc52bb412 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -84,9 +84,11 @@ + diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java new file mode 100644 index 000000000000..ebf46a864743 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -0,0 +1,64 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; +import ru.javawebinar.topjava.service.UserService; + +import javax.annotation.PostConstruct; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.Profiles.POSTGRES; + +/** + * User: gkislin + * Date: 10.08.2014 + */ +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-mvc.xml", + "classpath:spring/spring-db.xml" +}) +@WebAppConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@Transactional +@ActiveProfiles({POSTGRES, DATAJPA}) +abstract public class AbstractControllerTest { + + private static final CharacterEncodingFilter CHARACTER_ENCODING_FILTER = new CharacterEncodingFilter(); + + static { + CHARACTER_ENCODING_FILTER.setEncoding("UTF-8"); + CHARACTER_ENCODING_FILTER.setForceEncoding(true); + } + + protected MockMvc mockMvc; + + @Autowired + protected UserService userService; + + @Autowired + private WebApplicationContext webApplicationContext; + + @PostConstruct + void postConstruct() { + mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .addFilter(CHARACTER_ENCODING_FILTER) + .build(); + } + + @Before + public void setUp() { + userService.evictCache(); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java new file mode 100644 index 000000000000..f4fb6840231a --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java @@ -0,0 +1,34 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.Test; +import ru.javawebinar.topjava.web.AbstractControllerTest; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static ru.javawebinar.topjava.UserTestData.USER; +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; + +/** + * GKislin + * 10.04.2015. + */ +public class UserControllerTest extends AbstractControllerTest { + + @Test + public void testUserList() throws Exception { + mockMvc.perform(get("/users")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(view().name("userList")) + .andExpect(forwardedUrl("/WEB-INF/jsp/userList.jsp")) + .andExpect(model().attribute("userList", hasSize(2))) + .andExpect(model().attribute("userList", hasItem( + allOf( + hasProperty("id", is(START_SEQ)), + hasProperty("name", is(USER.getName())) + ) + ))); + } +} \ No newline at end of file From a78035e77aec95e0bd7f4691ccc19d098b031bc3 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 20:43:15 +0500 Subject: [PATCH 083/166] 7-09-rest-controller --- .../topjava/web/user/AdminRestController.java | 37 +++++++++++++++---- .../web/user/ProfileRestController.java | 15 ++++++-- 2 files changed, 41 insertions(+), 11 deletions(-) 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 da439f8bf7ec..7e0257bd8e68 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,34 +1,55 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.User; +import java.net.URI; import java.util.List; -@Controller +@RestController +@RequestMapping(AdminRestController.REST_URL) public class AdminRestController extends AbstractUserController { + static final String REST_URL = "/rest/admin/users"; + @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { return super.getAll(); } - public User get(int id) { + @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public User get(@PathVariable("id") int id) { return super.get(id); } - public User create(User user) { - return super.create(user); + @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity createWithLocation(@RequestBody User user) { + User created = super.create(user); + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(REST_URL + "/{id}") + .buildAndExpand(created.getId()).toUri(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setLocation(uriOfNewResource); + + return new ResponseEntity<>(created, httpHeaders, HttpStatus.CREATED); } - public void delete(int id) { + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + public void delete(@PathVariable("id") int id) { super.delete(id); } - public void update(User user, int id) { + @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody User user, @PathVariable("id") int id) { super.update(user, id); } - public User getByMail(String email) { + @RequestMapping(value = "/by", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public User getByMail(@RequestParam("email") String email) { return super.getByMail(email); } } 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 478ed36307ba..83717c5d8d2d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,21 +1,30 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.model.User; -@Controller +@RestController +@RequestMapping(ProfileRestController.REST_URL) public class ProfileRestController extends AbstractUserController { + public static final String REST_URL = "/rest/profile"; + @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public User get() { return super.get(LoggedUser.id()); } + @RequestMapping(method = RequestMethod.DELETE) public void delete() { super.delete(LoggedUser.id()); } - public void update(User user) { + @RequestMapping(method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody User user) { super.update(user, LoggedUser.id()); } } \ No newline at end of file From 8b571a19c2c785f80aa0c12cb1e9019aedb08328 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 21:05:15 +0500 Subject: [PATCH 084/166] 7-10-rest-test-jackson --- pom.xml | 8 +++++++ .../javawebinar/topjava/model/BaseEntity.java | 5 ++++ .../ru/javawebinar/topjava/model/User.java | 2 ++ .../web/user/AdminRestControllerTest.java | 24 +++++++++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index 397dec903686..eb070000ed91 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ 1.3 + 2.7.3 2.10.1 @@ -141,6 +142,13 @@ + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-json.version} + + org.hamcrest hamcrest-all diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 756b446b0ed5..57d949611302 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,8 +1,13 @@ package ru.javawebinar.topjava.model; import javax.persistence.*; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; @MappedSuperclass @Access(AccessType.FIELD) +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public class BaseEntity { public static final int START_SEQ = 100000; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index ec0fab898280..2894baffbee9 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 com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Email; @@ -61,6 +62,7 @@ public class User extends NamedEntity { @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "user") @OrderBy("dateTime DESC") + @JsonIgnore protected List meals; public User() { diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java new file mode 100644 index 000000000000..686748b85646 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.Test; +import org.springframework.http.MediaType; +import ru.javawebinar.topjava.web.AbstractControllerTest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; + +public class AdminRestControllerTest extends AbstractControllerTest { + + public static final String REST_URL = AdminRestController.REST_URL + '/'; + + @Test + public void testGet() throws Exception { + mockMvc.perform(get(REST_URL + ADMIN_ID)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } +} \ No newline at end of file From 6052cf5b30e55424069de862d40266efb7708e70 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 21:56:55 +0500 Subject: [PATCH 085/166] 7-11-jackson-hibernate --- pom.xml | 5 +++ .../javawebinar/topjava/model/BaseEntity.java | 6 +-- .../ru/javawebinar/topjava/model/User.java | 3 +- .../ru/javawebinar/topjava/util/TimeUtil.java | 10 ++++- .../topjava/web/json/JacksonObjectMapper.java | 43 +++++++++++++++++++ .../web/json/JsonLocalDateTimeConverter.java | 42 ++++++++++++++++++ .../topjava/web/json/JsonUtil.java | 41 ++++++++++++++++++ src/main/resources/spring/spring-mvc.xml | 10 ++++- .../topjava/web/json/JsonUtilTest.java | 30 +++++++++++++ 9 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java diff --git a/pom.xml b/pom.xml index eb070000ed91..71ec0246ed83 100644 --- a/pom.xml +++ b/pom.xml @@ -148,6 +148,11 @@ jackson-databind ${jackson-json.version} + + com.fasterxml.jackson.datatype + jackson-datatype-hibernate4 + ${jackson-json.version} + org.hamcrest diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 57d949611302..94ee5e67f036 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,13 +1,9 @@ package ru.javawebinar.topjava.model; import javax.persistence.*; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; @MappedSuperclass @Access(AccessType.FIELD) -@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) +//@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public class BaseEntity { public static final int START_SEQ = 100000; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 2894baffbee9..16b9291ecaba 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,6 +1,5 @@ package ru.javawebinar.topjava.model; -import com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Email; @@ -62,7 +61,7 @@ public class User extends NamedEntity { @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "user") @OrderBy("dateTime DESC") - @JsonIgnore +// @JsonIgnore protected List meals; public User() { diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 50c06a798e51..893470d31bb0 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -30,4 +30,12 @@ public static LocalDate parseLocalDate(String str) { public static LocalTime parseLocalTime(String str) { return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); } -} + + public static LocalDateTime parseLocalDateTime(String str) { + return parseLocalDateTime(str, DATE_TIME_FORMATTER); + } + + public static LocalDateTime parseLocalDateTime(String str, DateTimeFormatter formatter) { + return StringUtils.isEmpty(str) ? LocalDateTime.now() : LocalDateTime.parse(str, formatter); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java new file mode 100644 index 000000000000..be9ef9679924 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.web.json; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module; + +import java.time.LocalDateTime; + +/** + * User: gkislin + * Date: 26.05.2014 + *

+ * Handling Hibernate lazy-loading + * + * @link https://github.com/FasterXML/jackson + * @link https://github.com/FasterXML/jackson-datatype-hibernate + * @link http://wiki.fasterxml.com/JacksonHowToCustomSerializers + */ +public class JacksonObjectMapper extends ObjectMapper { + + private static final ObjectMapper MAPPER = new JacksonObjectMapper(); + + public static ObjectMapper getMapper() { + return MAPPER; + } + + private JacksonObjectMapper() { + registerModule(new Hibernate4Module()); + + SimpleModule customModule = new SimpleModule("customModule"); + customModule.addSerializer(new JsonLocalDateTimeConverter.UserSettingSerializer()); + customModule.addDeserializer(LocalDateTime.class, new JsonLocalDateTimeConverter.UserSettingDeserializer()); + registerModule(customModule); + + setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); + setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + setSerializationInclusion(JsonInclude.Include.NON_NULL); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java b/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java new file mode 100644 index 000000000000..6447099392a9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.web.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import ru.javawebinar.topjava.util.TimeUtil; + +import java.io.IOException; +import java.time.LocalDateTime; + +/** + * GKislin + * 06.01.2015. + */ +public class JsonLocalDateTimeConverter { + public static class UserSettingSerializer extends JsonSerializer { + @Override + public void serialize(LocalDateTime ldt, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeString(TimeUtil.toString(ldt)); + } + + @Override + public Class handledType() { + return LocalDateTime.class; + } + } + + public static class UserSettingDeserializer extends JsonDeserializer { + @Override + public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctx) throws IOException { + return TimeUtil.parseLocalDateTime(jp.getText()); + } + + @Override + public Class handledType() { + return LocalDateTime.class; + } + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java new file mode 100644 index 000000000000..c15e266c4dfe --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java @@ -0,0 +1,41 @@ +package ru.javawebinar.topjava.web.json; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectReader; + +import java.io.IOException; +import java.util.List; + +import static ru.javawebinar.topjava.web.json.JacksonObjectMapper.getMapper; + +/** + * User: gkislin + * Date: 30.04.2014 + */ +public class JsonUtil { + + public static List readValues(String json, Class clazz) { + ObjectReader reader = getMapper().readerFor(clazz); + try { + return reader.readValues(json).readAll(); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid read array from JSON:\n'" + json + "'", e); + } + } + + public static T readValue(String json, Class clazz) { + try { + return getMapper().readValue(json, clazz); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid read from JSON:\n'" + json + "'", e); + } + } + + public static String writeValue(T obj) { + try { + return getMapper().writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e); + } + } +} diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 12ac233daa15..86a60d059a02 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -5,7 +5,15 @@ 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> - + + + + + + + + + diff --git a/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java new file mode 100644 index 000000000000..d1a7589c42ea --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java @@ -0,0 +1,30 @@ +package ru.javawebinar.topjava.web.json; + +import org.junit.Test; +import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.model.UserMeal; + +import java.util.List; + +/** + * GKislin + * 22.07.2015. + */ +public class JsonUtilTest { + + @Test + public void testReadWriteValue() throws Exception { + String json = JsonUtil.writeValue(MealTestData.ADMIN_MEAL); + System.out.println(json); + UserMeal userMeal = JsonUtil.readValue(json, UserMeal.class); + MealTestData.MATCHER.assertEquals(MealTestData.ADMIN_MEAL, userMeal); + } + + @Test + public void testReadWriteValues() throws Exception { + String json = JsonUtil.writeValue(MealTestData.USER_MEALS); + System.out.println(json); + List userMeals = JsonUtil.readValues(json, UserMeal.class); + MealTestData.MATCHER.assertCollectionEquals(MealTestData.USER_MEALS, userMeals); + } +} \ No newline at end of file From 608f6c9c50bfc7e61bb71f54a0b079fe506deaae Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 22:39:20 +0500 Subject: [PATCH 086/166] 7-12-jackson-jsr310 --- pom.xml | 5 +++++ .../topjava/web/json/JacksonObjectMapper.java | 11 ++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 71ec0246ed83..b01f54a4026a 100644 --- a/pom.xml +++ b/pom.xml @@ -153,6 +153,11 @@ jackson-datatype-hibernate4 ${jackson-json.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-json.version} + org.hamcrest diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java index be9ef9679924..6d1dbcd15a5b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -3,12 +3,11 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module; - -import java.time.LocalDateTime; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; /** * User: gkislin @@ -31,10 +30,8 @@ public static ObjectMapper getMapper() { private JacksonObjectMapper() { registerModule(new Hibernate4Module()); - SimpleModule customModule = new SimpleModule("customModule"); - customModule.addSerializer(new JsonLocalDateTimeConverter.UserSettingSerializer()); - customModule.addDeserializer(LocalDateTime.class, new JsonLocalDateTimeConverter.UserSettingDeserializer()); - registerModule(customModule); + registerModule(new JavaTimeModule()); + configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); From 83f5557bdaf125e212d3fe2cfc4a9457f7060f33 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 22:40:08 +0500 Subject: [PATCH 087/166] 7-13-test-with-matcher --- .../ru/javawebinar/topjava/MealTestData.java | 3 +- .../java/ru/javawebinar/topjava/TestUtil.java | 31 +++++++++ .../ru/javawebinar/topjava/UserTestData.java | 2 +- .../topjava/matcher/ModelMatcher.java | 54 ++++++++++++++- .../topjava/matcher/TestMatcher.java | 29 ++++++++ .../web/user/AdminRestControllerTest.java | 66 ++++++++++++++++++- .../web/user/ProfileRestControllerTest.java | 48 ++++++++++++++ 7 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/TestUtil.java create mode 100644 src/test/java/ru/javawebinar/topjava/matcher/TestMatcher.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index 4219b708464f..e7bcb899e889 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava; +import ru.javawebinar.topjava.TestUtil.ToStringModelMatcher; import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.UserMeal; @@ -13,7 +14,7 @@ public class MealTestData { - public static final ModelMatcher MATCHER = new ModelMatcher<>(UserMeal::toString); + public static final ModelMatcher MATCHER = new ToStringModelMatcher<>(UserMeal.class); public static final int MEAL1_ID = START_SEQ + 2; public static final int ADMIN_MEAL_ID = START_SEQ + 8; diff --git a/src/test/java/ru/javawebinar/topjava/TestUtil.java b/src/test/java/ru/javawebinar/topjava/TestUtil.java new file mode 100644 index 000000000000..bc9aa67c4897 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -0,0 +1,31 @@ +package ru.javawebinar.topjava; + +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.matcher.ModelMatcher; + +import java.io.UnsupportedEncodingException; + +/** + * GKislin + * 05.01.2015. + */ +public class TestUtil { + + public static ResultActions print(ResultActions action) throws UnsupportedEncodingException { + System.out.println(getContent(action)); + return action; + } + + public static String getContent(ResultActions action) throws UnsupportedEncodingException { + return action.andReturn().getResponse().getContentAsString(); + } + + /** + * Compare entities using toString + */ + public static class ToStringModelMatcher extends ModelMatcher { + public ToStringModelMatcher(Class entityClass) { + super(Object::toString, entityClass); + } + } +} diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 8949f9974a27..4a4bf6fc7414 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -18,7 +18,7 @@ public class UserTestData { public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN, Role.ROLE_USER); - public static final ModelMatcher MATCHER = new ModelMatcher<>(u -> ((u instanceof TestUser) ? (TestUser) u : new TestUser(u))); + public static final ModelMatcher MATCHER = new ModelMatcher<>(u -> ((u instanceof TestUser) ? (TestUser) u : new TestUser(u)), User.class); public static class TestUser extends User { diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index f2af6c1e1551..0edd800c59d5 100644 --- a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -1,20 +1,39 @@ package ru.javawebinar.topjava.matcher; import org.junit.Assert; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; +import ru.javawebinar.topjava.TestUtil; +import ru.javawebinar.topjava.web.json.JsonUtil; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; + /** * @param : entity * @param : testEntity, converter result for compare */ public class ModelMatcher { protected Function entityConverter; + protected Class entityClass; - public ModelMatcher(Function entityConverter) { + public ModelMatcher(Function entityConverter, Class entityClass) { this.entityConverter = entityConverter; + this.entityClass = entityClass; + } + + private T fromJsonValue(String json) { + return JsonUtil.readValue(json, entityClass); + } + + private Collection fromJsonValues(String json) { + return JsonUtil.readValues(json, entityClass); } public void assertEquals(T expected, T actual) { @@ -25,7 +44,38 @@ public void assertCollectionEquals(Collection expected, Collection actual) Assert.assertEquals(map(expected, entityConverter), map(actual, entityConverter)); } - public static Collection map(Collection collection, Function converter) { + public static List map(Collection collection, Function converter) { return collection.stream().map(converter).collect(Collectors.toList()); } + + public ResultMatcher contentMatcher(T expect) { + return content().string( + new TestMatcher(expect) { + @Override + protected boolean compare(T expected, String body) { + R actualForCompare = entityConverter.apply(fromJsonValue(body)); + R expectedForCompare = entityConverter.apply(expected); + return expectedForCompare.equals(actualForCompare); + } + }); + } + + public final ResultMatcher contentListMatcher(T... expected) { + return contentListMatcher(Arrays.asList(expected)); + } + + public final ResultMatcher contentListMatcher(List expected) { + return content().string(new TestMatcher>(expected) { + @Override + protected boolean compare(List expected, String actual) { + List actualList = map(fromJsonValues(actual), entityConverter); + List expectedList = map(expected, entityConverter); + return expectedList.equals(actualList); + } + }); + } + + public T fromJsonAction(ResultActions action) throws UnsupportedEncodingException { + return fromJsonValue(TestUtil.getContent(action)); + } } diff --git a/src/test/java/ru/javawebinar/topjava/matcher/TestMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/TestMatcher.java new file mode 100644 index 000000000000..32d599020146 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/matcher/TestMatcher.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava.matcher; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import ru.javawebinar.topjava.web.json.JsonUtil; + +/** + * GKislin + * 05.01.2015. + */ +abstract public class TestMatcher extends BaseMatcher { + protected T expected; + + public TestMatcher(T expected) { + this.expected = expected; + } + + @Override + public boolean matches(Object actual) { + return compare(expected, (String) actual); + } + + abstract protected boolean compare(T expected, String actual); + + @Override + public void describeTo(Description description) { + description.appendText(JsonUtil.writeValue(expected)); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 686748b85646..40dd88499da4 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -2,13 +2,21 @@ import org.junit.Test; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.TestUtil; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.web.AbstractControllerTest; +import ru.javawebinar.topjava.web.json.JsonUtil; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import java.util.Arrays; +import java.util.Collections; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.*; public class AdminRestControllerTest extends AbstractControllerTest { @@ -19,6 +27,58 @@ public void testGet() throws Exception { mockMvc.perform(get(REST_URL + ADMIN_ID)) .andExpect(status().isOk()) .andDo(print()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER.contentMatcher(ADMIN)); + } + + @Test + public void testGetByEmail() throws Exception { + mockMvc.perform(get(REST_URL + "by?email=" + USER.getEmail())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER.contentMatcher(USER)); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + USER_ID).contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); + } + + @Test + public void testUpdate() throws Exception { + User updated = new User(USER); + updated.setName("UpdatedName"); + updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); + mockMvc.perform(put(REST_URL + USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated))) + .andExpect(status().isOk()); + + MATCHER.assertEquals(updated, userService.get(USER_ID)); + } + + @Test + public void testCreate() throws Exception { + TestUser expected = new TestUser("New", "new@gmail.com", "newPass", Role.ROLE_USER, Role.ROLE_ADMIN); + ResultActions action = mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(expected.asUser()))).andExpect(status().isCreated()); + + User returned = MATCHER.fromJsonAction(action); + expected.setId(returned.getId()); + + MATCHER.assertEquals(expected, returned); + MATCHER.assertCollectionEquals(Arrays.asList(ADMIN, expected, USER), userService.getAll()); + } + + @Test + public void testGetAll() throws Exception { + TestUtil.print(mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER.contentListMatcher(ADMIN, USER))); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java new file mode 100644 index 000000000000..1754e5c1bdd2 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -0,0 +1,48 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.Test; +import org.springframework.http.MediaType; +import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.TestUtil; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.web.AbstractControllerTest; +import ru.javawebinar.topjava.web.json.JsonUtil; + +import java.util.Collections; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; + +public class ProfileRestControllerTest extends AbstractControllerTest { + + @Test + public void testGet() throws Exception { + TestUtil.print(mockMvc.perform(get(REST_URL)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER.contentMatcher(USER))); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); + } + + @Test + public void testUpdate() throws Exception { + User updated = new User(LoggedUser.id(), "newName", "newEmail", "newPassword", Role.ROLE_USER); + mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated))) + .andDo(print()) + .andExpect(status().isOk()); + + MATCHER.assertEquals(updated, new User(userService.getByEmail("newEmail"))); + } +} \ No newline at end of file From 5b82be0908991804c85b07c2fd285a0d4b04d548 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 22:50:21 +0500 Subject: [PATCH 088/166] 7-12-jackson-jsr310 --- .../web/json/JsonLocalDateTimeConverter.java | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java b/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java deleted file mode 100644 index 6447099392a9..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.javawebinar.topjava.web.json; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import ru.javawebinar.topjava.util.TimeUtil; - -import java.io.IOException; -import java.time.LocalDateTime; - -/** - * GKislin - * 06.01.2015. - */ -public class JsonLocalDateTimeConverter { - public static class UserSettingSerializer extends JsonSerializer { - @Override - public void serialize(LocalDateTime ldt, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeString(TimeUtil.toString(ldt)); - } - - @Override - public Class handledType() { - return LocalDateTime.class; - } - } - - public static class UserSettingDeserializer extends JsonDeserializer { - @Override - public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctx) throws IOException { - return TimeUtil.parseLocalDateTime(jp.getText()); - } - - @Override - public Class handledType() { - return LocalDateTime.class; - } - } -} From 5538ef9f160b67e453956a317e6b589cdf28ee04 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 22:52:07 +0500 Subject: [PATCH 089/166] 7-14 --- config/Topjava-soapui-project.xml | 17 +++++++++++++++++ .../topjava/web/user/ProfileRestController.java | 5 +++++ src/main/resources/spring/spring-mvc.xml | 8 ++++++++ 3 files changed, 30 insertions(+) create mode 100644 config/Topjava-soapui-project.xml diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml new file mode 100644 index 000000000000..44ad8436a5cc --- /dev/null +++ b/config/Topjava-soapui-project.xml @@ -0,0 +1,17 @@ + +http://localhost:8080text/html;charset=utf-8500 401htmlapplication/json200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/usersuser@yandex.rupasswordNo AuthorizationBasicNo Authorizationapplication/jsontext/html;charset=utf-8500htmlapplication/json201user:Response<xml-fragment/>http://localhost:8080{ + "name": "New2", + "email": "new2@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] +}http://localhost/topjava/rest/admin/usersNo AuthorizationBasicNo Authorizationtext/html;charset=utf-8500htmlapplication/json200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/users/100000No AuthorizationBasicNo Authorizationtext/html;charset=utf-8405 500htmlapplication/json200data200data200data<xml-fragment/>http://localhost:8080{ + "name": "UserUpdated", + "email": "user@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] +}http://localhost/topjava/rest/admin/users/100000BasicBasicGlobal HTTP Settingstext/html;charset=utf-8500htmlapplication/json200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profileBasicBasicGlobal HTTP Settingsapplication/jsontext/html;charset=utf-8500 405htmlapplication/json201user:Response200data200data200data200data200data<xml-fragment/>http://localhost:8080{ + "name": "New777", + "email": "new777@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] +}http://localhost/topjava/rest/profileNo AuthorizationBasicNo Authorization200datatext/html;charset=utf-8500htmlapplication/json200data<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profileNo AuthorizationBasicNo Authorization \ 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 index 83717c5d8d2d..50fc327068e8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -27,4 +27,9 @@ public void delete() { public void update(@RequestBody User user) { super.update(user, LoggedUser.id()); } + + @RequestMapping(value = "/text", method = RequestMethod.GET) + public String testUTF() { + return "Русский текст"; + } } \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 86a60d059a02..a770bdfe84dd 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -12,6 +12,14 @@ + + + + text/plain;charset=UTF-8 + text/html;charset=UTF-8 + + + From 662f7b4b5f0d3b7bb474005214bf4c837eeb18d5 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 22:53:44 +0500 Subject: [PATCH 090/166] 7-15 --- pom.xml | 2 +- src/main/java/ru/javawebinar/topjava/model/User.java | 2 +- .../javawebinar/topjava/web/json/JacksonObjectMapper.java | 5 ++--- src/test/java/ru/javawebinar/topjava/MealTestData.java | 1 - .../topjava/service/AbstractJpaUserServiceTest.java | 8 ++++---- .../javawebinar/topjava/service/AbstractServiceTest.java | 2 +- .../topjava/service/AbstractUserServiceTest.java | 6 +++--- .../topjava/service/datajpa/DataJpaUserServiceTest.java | 1 - .../topjava/web/user/AdminRestControllerTest.java | 2 +- .../topjava/web/user/ProfileRestControllerTest.java | 5 +++-- 10 files changed, 16 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index b01f54a4026a..8eb4176aa2c7 100644 --- a/pom.xml +++ b/pom.xml @@ -150,7 +150,7 @@ com.fasterxml.jackson.datatype - jackson-datatype-hibernate4 + jackson-datatype-hibernate5 ${jackson-json.version} diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 16b9291ecaba..f626f787d3e1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -77,7 +77,7 @@ public User(Integer id, String name, String email, String password, int calories this.password = password; this.caloriesPerDay = caloriesPerDay; this.enabled = enabled; - this.roles = roles; + this.roles = EnumSet.copyOf(roles); } public String getEmail() { diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java index 6d1dbcd15a5b..cdd7fcdd4d3b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -5,8 +5,7 @@ import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module; +import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; /** @@ -28,7 +27,7 @@ public static ObjectMapper getMapper() { } private JacksonObjectMapper() { - registerModule(new Hibernate4Module()); + registerModule(new Hibernate5Module()); registerModule(new JavaTimeModule()); configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index e7bcb899e889..ca06257a016d 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -4,7 +4,6 @@ import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.UserMeal; -import java.time.LocalDateTime; import java.time.Month; import java.util.Arrays; import java.util.List; diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java index eb8f8e9ea70e..a84a5d19ad20 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java @@ -1,6 +1,6 @@ package ru.javawebinar.topjava.service; -import org.junit.Before; +import org.junit.After; import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.repository.JpaUtil; @@ -13,9 +13,9 @@ abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest @Autowired private JpaUtil jpaUtil; - @Before - public void setUp() throws Exception { - super.setUp(); + @After + public void tearDown() throws Exception { + super.tearDown(); jpaUtil.clear2ndLevelHibernateCache(); } } diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index 6548d1e8d644..a7895b7ea002 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -26,7 +26,7 @@ }) @RunWith(SpringJUnit4ClassRunner.class) @ActiveProfiles(ACTIVE_DB) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8"), executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) abstract public class AbstractServiceTest { protected final Logger log = LoggerFactory.getLogger(getClass()); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 0252e6ddff4b..54fdfcb329ad 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -1,6 +1,6 @@ package ru.javawebinar.topjava.service; -import org.junit.Before; +import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; @@ -19,8 +19,8 @@ abstract public class AbstractUserServiceTest extends AbstractServiceTest { @Autowired protected UserService service; - @Before - public void setUp() throws Exception { + @After + public void tearDown() throws Exception { service.evictCache(); } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index a14ad645320d..5d67dce09e59 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -1,7 +1,6 @@ package ru.javawebinar.topjava.service.datajpa; import org.junit.Test; -import org.springframework.dao.DataAccessException; import org.springframework.test.context.ActiveProfiles; import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.model.User; diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 40dd88499da4..3b48ae259bf5 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -49,7 +49,7 @@ public void testDelete() throws Exception { @Test public void testUpdate() throws Exception { - User updated = new User(USER); + User updated = USER; updated.setName("UpdatedName"); updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); mockMvc.perform(put(REST_URL + USER_ID) diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index 1754e5c1bdd2..3f611fb12d3f 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -37,12 +37,13 @@ public void testDelete() throws Exception { @Test public void testUpdate() throws Exception { - User updated = new User(LoggedUser.id(), "newName", "newEmail", "newPassword", Role.ROLE_USER); + User updated = new User(LoggedUser.id(), "newName", "newEmail@ya.ru", "newPassword", Role.ROLE_USER); mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) .content(JsonUtil.writeValue(updated))) .andDo(print()) .andExpect(status().isOk()); - MATCHER.assertEquals(updated, new User(userService.getByEmail("newEmail"))); + MATCHER.assertEquals(updated, new User(userService.getByEmail("newEmail@ya.ru"))); + MATCHER.assertEquals(updated, userService.getByEmail("newEmail")); } } \ No newline at end of file From 8ec3cc32b9630b2a2432827fe6c28f4a90dc4ef0 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 23:20:11 +0500 Subject: [PATCH 091/166] test Rest API with applicatian SoapUI and save result into config dir --- config/Topjava-soapui-project.xml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml index 44ad8436a5cc..d8feb156c4c6 100644 --- a/config/Topjava-soapui-project.xml +++ b/config/Topjava-soapui-project.xml @@ -1,17 +1,2 @@ -http://localhost:8080text/html;charset=utf-8500 401htmlapplication/json200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/usersuser@yandex.rupasswordNo AuthorizationBasicNo Authorizationapplication/jsontext/html;charset=utf-8500htmlapplication/json201user:Response<xml-fragment/>http://localhost:8080{ - "name": "New2", - "email": "new2@yandex.ru", - "password": "passwordNew", - "roles": ["ROLE_USER"] -}http://localhost/topjava/rest/admin/usersNo AuthorizationBasicNo Authorizationtext/html;charset=utf-8500htmlapplication/json200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/users/100000No AuthorizationBasicNo Authorizationtext/html;charset=utf-8405 500htmlapplication/json200data200data200data<xml-fragment/>http://localhost:8080{ - "name": "UserUpdated", - "email": "user@yandex.ru", - "password": "passwordNew", - "roles": ["ROLE_USER"] -}http://localhost/topjava/rest/admin/users/100000BasicBasicGlobal HTTP Settingstext/html;charset=utf-8500htmlapplication/json200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profileBasicBasicGlobal HTTP Settingsapplication/jsontext/html;charset=utf-8500 405htmlapplication/json201user:Response200data200data200data200data200data<xml-fragment/>http://localhost:8080{ - "name": "New777", - "email": "new777@yandex.ru", - "password": "passwordNew", - "roles": ["ROLE_USER"] -}http://localhost/topjava/rest/profileNo AuthorizationBasicNo Authorization200datatext/html;charset=utf-8500htmlapplication/json200data<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profileNo AuthorizationBasicNo Authorization \ No newline at end of file +http://localhost:8081text/html;charset=utf-8404htmlapplication/jsontext/html;charset=UTF-8200htmlapplication/json;charset=UTF-8200Response<xml-fragment/>http://localhost:8081http://localhost/topjava/rest/admin/usersNo Authorizationhttp://localhost:8081 \ No newline at end of file From 27b4afffbc39c21909f4ad69b4531651dd3f1676 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 29 May 2016 23:22:41 +0500 Subject: [PATCH 092/166] change ProfileRestControllerTest --- .../javawebinar/topjava/web/user/ProfileRestControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index 3f611fb12d3f..72bf53f4cd8a 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -43,7 +43,7 @@ public void testUpdate() throws Exception { .andDo(print()) .andExpect(status().isOk()); - MATCHER.assertEquals(updated, new User(userService.getByEmail("newEmail@ya.ru"))); + MATCHER.assertEquals(updated, userService.getByEmail("newEmail@ya.ru")); MATCHER.assertEquals(updated, userService.getByEmail("newEmail")); } } \ No newline at end of file From c3d433babc3a7629ce26c0a12c498468c21c05dd Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 7 Jun 2016 23:00:41 +0500 Subject: [PATCH 093/166] 8-01-HW07-controller-test --- src/main/resources/logback.xml | 2 ++ .../topjava/web/ResourceControllerTest.java | 24 +++++++++++++ .../web/meal/UserMealControllerTest.java | 34 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 6d8c66b4dc17..3e3cd2275648 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -24,6 +24,8 @@ + + diff --git a/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java new file mode 100644 index 000000000000..3d2585a50973 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Test; +import org.springframework.http.MediaType; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * GKislin + * 10.04.2015. + */ +public class ResourceControllerTest extends AbstractControllerTest { + + @Test + public void testResources() throws Exception { + mockMvc.perform(get("/resources/css/style.css")) + .andDo(print()) + .andExpect(content().contentType(MediaType.valueOf("text/css"))) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java new file mode 100644 index 000000000000..3c0e72aabe55 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java @@ -0,0 +1,34 @@ +package ru.javawebinar.topjava.web.meal; + +import org.junit.Test; +import ru.javawebinar.topjava.web.AbstractControllerTest; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static ru.javawebinar.topjava.MealTestData.MEAL1; +import static ru.javawebinar.topjava.MealTestData.MEAL1_ID; + +/** + * GKislin + * 10.04.2015. + */ +public class UserMealControllerTest extends AbstractControllerTest { + + @Test + public void testMealList() throws Exception { + mockMvc.perform(get("/meals")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(view().name("mealList")) + .andExpect(forwardedUrl("/WEB-INF/jsp/mealList.jsp")) + .andExpect(model().attribute("mealList", hasSize(6))) + .andExpect(model().attribute("mealList", hasItem( + allOf( + hasProperty("id", is(MEAL1_ID)), + hasProperty("description", is(MEAL1.getDescription())) + ) + ))); + } +} \ No newline at end of file From 6e8109eff7b756dc78360688f54870044d64bb25 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 7 Jun 2016 23:10:06 +0500 Subject: [PATCH 094/166] 8-02-HW07-rest-controller --- .../topjava/to/UserMealWithExceed.java | 12 +-- .../web/meal/UserMealRestController.java | 60 ++++++++++++- .../ru/javawebinar/topjava/MealTestData.java | 2 + .../ru/javawebinar/topjava/SpringMain.java | 3 +- .../web/meal/UserMealRestControllerTest.java | 90 +++++++++++++++++++ 5 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java index 6d2848bf254a..299ba365ccd6 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.to; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.time.LocalDateTime; public class UserMealWithExceed { @@ -13,11 +15,11 @@ public class UserMealWithExceed { private final boolean exceed; - public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { - this(null, dateTime, description, calories, exceed); - } - - public UserMealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { + public UserMealWithExceed(@JsonProperty("id") Integer id, + @JsonProperty("dateTime") LocalDateTime dateTime, + @JsonProperty("description") String description, + @JsonProperty("calories") int calories, + @JsonProperty("exceed") boolean exceed) { this.id = id; this.dateTime = dateTime; this.description = description; diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index 1bb949f62f61..9f658afd16c1 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -1,6 +1,20 @@ +import java.net.URI; +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping(value = UserMealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) package ru.javawebinar.topjava.web.meal; -import org.springframework.stereotype.Controller; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.to.UserMealWithExceed; import ru.javawebinar.topjava.service.UserMealServiceImpl; import ru.javawebinar.topjava.to.UserMealWithExceed; import ru.javawebinar.topjava.util.TimeUtil; @@ -8,4 +22,46 @@ @Controller public class UserMealRestController extends AbstractUserMealController { -} \ No newline at end of file + public static final String REST_URL = "/rest/profile/meals"; + + @RequestMapping(value = "/{id}", method = RequestMethod.GET) + public UserMeal get(@PathVariable("id") int id) { + return super.get(id); + } + + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @RequestMapping(method = RequestMethod.GET) + public List getAll() { + return super.getAll(); + } + + @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody UserMeal meal, @PathVariable("id") int id) { + super.update(meal, id); + } + + @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity createWithLocation(@RequestBody UserMeal meal) { + UserMeal created = super.create(meal); + + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(REST_URL + "/{id}") + .buildAndExpand(created.getId()).toUri(); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setLocation(uriOfNewResource); + + return new ResponseEntity<>(created, httpHeaders, HttpStatus.CREATED); + } + + @RequestMapping(value = "/between", method = RequestMethod.GET) + public List getBetween( + @RequestParam(value = "startDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDateTime, + @RequestParam(value = "endDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDateTime) { + return super.getBetween(startDateTime.toLocalDate(), startDateTime.toLocalTime(), endDateTime.toLocalDate(), endDateTime.toLocalTime()); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index ca06257a016d..4487aeaec703 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -3,6 +3,7 @@ import ru.javawebinar.topjava.TestUtil.ToStringModelMatcher; import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.to.UserMealWithExceed; import java.time.Month; import java.util.Arrays; @@ -14,6 +15,7 @@ public class MealTestData { public static final ModelMatcher MATCHER = new ToStringModelMatcher<>(UserMeal.class); + public static final ModelMatcher MATCHER_WITH_EXCEED = new ToStringModelMatcher<>(UserMealWithExceed.class); public static final int MEAL1_ID = START_SEQ + 2; public static final int ADMIN_MEAL_ID = START_SEQ + 8; diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 206507aa8aa7..91fbf63fdc2f 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -5,6 +5,7 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserMealWithExceed; import ru.javawebinar.topjava.web.meal.AbstractUserMealController; +import ru.javawebinar.topjava.web.meal.UserMealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; import java.time.LocalDate; @@ -27,7 +28,7 @@ public static void main(String[] args) { System.out.println(adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN))); System.out.println(); - AbstractUserMealController mealController = appCtx.getBean(AbstractUserMealController.class); + AbstractUserMealController mealController = appCtx.getBean(UserMealRestController.class); List filteredMealsWithExceeded = mealController.getBetween( LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java new file mode 100644 index 000000000000..8333b46e9697 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java @@ -0,0 +1,90 @@ +package ru.javawebinar.topjava.web.meal; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.LoggedUser; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.service.UserMealService; +import ru.javawebinar.topjava.util.UserMealsUtil; +import ru.javawebinar.topjava.web.AbstractControllerTest; +import ru.javawebinar.topjava.web.json.JsonUtil; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; + +public class UserMealRestControllerTest extends AbstractControllerTest { + + public static final String REST_URL = UserMealRestController.REST_URL + '/'; + + @Autowired + private UserMealService service; + + @Test + public void testGet() throws Exception { + mockMvc.perform(get(REST_URL + MEAL1_ID)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER.contentMatcher(MEAL1)); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(START_SEQ)); + } + + @Test + public void testUpdate() throws Exception { + UserMeal updated = getUpdated(); + + mockMvc.perform(put(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated))) + .andExpect(status().isOk()); + + assertEquals(updated, service.get(MEAL1_ID, START_SEQ)); + } + + @Test + public void testCreate() throws Exception { + UserMeal created = getCreated(); + ResultActions action = mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(created))); + + UserMeal returned = MATCHER.fromJsonAction(action); + created.setId(returned.getId()); + + MATCHER.assertEquals(created, returned); + MATCHER.assertCollectionEquals(Arrays.asList(created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), service.getAll(START_SEQ)); + } + + @Test + public void testGetAll() throws Exception { + mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher(UserMealsUtil.getWithExceeded(USER_MEALS, LoggedUser.getCaloriesPerDay()))); + } + + @Test + public void testGetBetween() throws Exception { + mockMvc.perform(get(REST_URL + "between?startDateTime=2015-05-30T07:00&endDateTime=2015-05-31T11:00:00")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( + UserMealsUtil.createWithExceed(MEAL4, true), + UserMealsUtil.createWithExceed(MEAL1, false))); + } +} \ No newline at end of file From 18946121528514ef305d123b2e89b63c3b466670 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 7 Jun 2016 23:12:53 +0500 Subject: [PATCH 095/166] fix bug in UserMealRestController --- .../topjava/web/meal/UserMealRestController.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index 9f658afd16c1..1389f46f385b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -1,11 +1,10 @@ +package ru.javawebinar.topjava.web.meal; + + import java.net.URI; import java.time.LocalDateTime; import java.util.List; -@RestController -@RequestMapping(value = UserMealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) -package ru.javawebinar.topjava.web.meal; - import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -15,12 +14,9 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.to.UserMealWithExceed; -import ru.javawebinar.topjava.service.UserMealServiceImpl; -import ru.javawebinar.topjava.to.UserMealWithExceed; -import ru.javawebinar.topjava.util.TimeUtil; -import ru.javawebinar.topjava.util.UserMealsUtil; -@Controller +@RestController +@RequestMapping(value = UserMealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) public class UserMealRestController extends AbstractUserMealController { public static final String REST_URL = "/rest/profile/meals"; From c723a8c457494b8d073ed2c2870ad9e8a8646772 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 7 Jun 2016 23:14:51 +0500 Subject: [PATCH 096/166] 8-03-HW07-formatter --- .../util/converter/LocalDateFormatter.java | 26 +++++++++++++++++++ .../util/converter/LocalTimeFormatter.java | 26 +++++++++++++++++++ .../web/meal/UserMealRestController.java | 19 +++++++++----- .../topjava/web/user/AdminRestController.java | 7 ++--- src/main/resources/spring/spring-mvc.xml | 11 +++++++- .../web/meal/UserMealRestControllerTest.java | 19 ++++++++++++++ 6 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/converter/LocalDateFormatter.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/converter/LocalTimeFormatter.java diff --git a/src/main/java/ru/javawebinar/topjava/util/converter/LocalDateFormatter.java b/src/main/java/ru/javawebinar/topjava/util/converter/LocalDateFormatter.java new file mode 100644 index 000000000000..9d125a809f82 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/converter/LocalDateFormatter.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.util.converter; + +import org.springframework.format.Formatter; +import ru.javawebinar.topjava.util.TimeUtil; + +import java.text.ParseException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +/** + * GKislin + * 15.04.2015. + */ +public class LocalDateFormatter implements Formatter { + + @Override + public LocalDate parse(String text, Locale locale) throws ParseException { + return TimeUtil.parseLocalDate(text); + } + + @Override + public String print(LocalDate lt, Locale locale) { + return lt.format(DateTimeFormatter.ISO_LOCAL_DATE); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/converter/LocalTimeFormatter.java b/src/main/java/ru/javawebinar/topjava/util/converter/LocalTimeFormatter.java new file mode 100644 index 000000000000..c25a71db553e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/converter/LocalTimeFormatter.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.util.converter; + +import org.springframework.format.Formatter; +import ru.javawebinar.topjava.util.TimeUtil; + +import java.text.ParseException; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +/** + * GKislin + * 15.04.2015. + */ +public class LocalTimeFormatter implements Formatter { + + @Override + public LocalTime parse(String text, Locale locale) throws ParseException { + return TimeUtil.parseLocalTime(text); + } + + @Override + public String print(LocalTime lt, Locale locale) { + return lt.format(DateTimeFormatter.ISO_LOCAL_TIME); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index 1389f46f385b..18b515123ba2 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -2,18 +2,19 @@ import java.net.URI; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.to.UserMealWithExceed; +import ru.javawebinar.topjava.util.TimeUtil; @RestController @RequestMapping(value = UserMealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) @@ -48,10 +49,7 @@ public ResponseEntity createWithLocation(@RequestBody UserMeal meal) { .path(REST_URL + "/{id}") .buildAndExpand(created.getId()).toUri(); - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setLocation(uriOfNewResource); - - return new ResponseEntity<>(created, httpHeaders, HttpStatus.CREATED); + return ResponseEntity.created(uriOfNewResource).body(created); } @RequestMapping(value = "/between", method = RequestMethod.GET) @@ -60,4 +58,13 @@ public List getBetween( @RequestParam(value = "endDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDateTime) { return super.getBetween(startDateTime.toLocalDate(), startDateTime.toLocalTime(), endDateTime.toLocalDate(), endDateTime.toLocalTime()); } + + @RequestMapping(value = "/filter", method = RequestMethod.GET) + public List getBetween( + @RequestParam(value = "startDate", required = false) LocalDate startDate, @RequestParam(value = "startTime", required = false) LocalTime startTime, + @RequestParam(value = "endDate", required = false) LocalDate endDate, @RequestParam(value = "endTime", required = false) LocalTime endTime) { + return super.getBetween( + startDate != null ? startDate : TimeUtil.MIN_DATE, startTime != null ? startTime : LocalTime.MIN, + endDate != null ? endDate : TimeUtil.MAX_DATE, endTime != null ? endTime : LocalTime.MAX); + } } 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 7e0257bd8e68..b4900143c70b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,7 +1,5 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -29,13 +27,12 @@ public User get(@PathVariable("id") int id) { @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity createWithLocation(@RequestBody User user) { User created = super.create(user); + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() .path(REST_URL + "/{id}") .buildAndExpand(created.getId()).toUri(); - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setLocation(uriOfNewResource); - return new ResponseEntity<>(created, httpHeaders, HttpStatus.CREATED); + return ResponseEntity.created(uriOfNewResource).body(created); } @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index a770bdfe84dd..dc25177311f6 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -7,7 +7,7 @@ - + @@ -23,6 +23,15 @@ + + + + + + + + + diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java index 8333b46e9697..124a7f147b83 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java @@ -87,4 +87,23 @@ public void testGetBetween() throws Exception { UserMealsUtil.createWithExceed(MEAL4, true), UserMealsUtil.createWithExceed(MEAL1, false))); } + + @Test + public void testFilter() throws Exception { + mockMvc.perform(get(REST_URL + "filter?startDate=2015-05-30&startTime=07:00&endDate=2015-05-31&endTime=11:00")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( + UserMealsUtil.createWithExceed(MEAL4, true), + UserMealsUtil.createWithExceed(MEAL1, false))); + } + + @Test + public void testFilterAll() throws Exception { + mockMvc.perform(get(REST_URL + "filter?startDate=&endTime=")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( + UserMealsUtil.getWithExceeded(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), LoggedUser.getCaloriesPerDay()))); + } } \ No newline at end of file From 1faaf5243d4762465cb4a759f0fcf025369ecc95 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 7 Jun 2016 23:29:05 +0500 Subject: [PATCH 097/166] 8-05-webjars --- pom.xml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pom.xml b/pom.xml index 8eb4176aa2c7..94e58f71bc1e 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,13 @@ 1.3 2.7.3 2.10.1 + + + 3.3.6 + 2.2.3 + 2.5.1 + 2.3.8 + 1.10.11 @@ -142,6 +149,33 @@ + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + org.webjars + jquery + ${webjars-jquery.version} + + + org.webjars + datetimepicker + ${webjars-datetimepicker.version} + + + org.webjars.bower + noty + ${webjars-noty.version} + + + org.webjars + datatables + ${webjars-datatables.version} + + com.fasterxml.jackson.core From 45fc0c92834d1cf69d7e4ed6602457a7f2665cc7 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Tue, 7 Jun 2016 23:37:07 +0500 Subject: [PATCH 098/166] 8-06-bootstrap --- config/messages/app.properties | 1 + config/messages/app_ru.properties | 1 + src/main/resources/spring/spring-mvc.xml | 3 + .../WEB-INF/jsp/fragments/bodyHeader.jsp | 16 ++++- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 8 ++- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 3 + src/main/webapp/WEB-INF/jsp/index.jsp | 30 +++++---- src/main/webapp/WEB-INF/jsp/userList.jsp | 65 ++++++++++++------- 8 files changed, 86 insertions(+), 41 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index c6cc565f0ab9..4d85a5c2f69f 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -4,6 +4,7 @@ app.login=Login as users.title=User list users.edit=Edit user +users.add=Add user meals.title=Meals list meals.edit=Edit meal diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 7a7d45b8fb06..71f99cea84df 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -4,6 +4,7 @@ app.login=Зайти как users.title=Список пользователей users.edit=Редактировать пользователя +users.add=Добавить пользователя meals.title=Список еды meals.edit=Редактировать еду diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index dc25177311f6..d090e078c7ec 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -37,6 +37,9 @@ + + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 833a84d0b2a4..c1a68e5d526c 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,4 +1,18 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> -

+ diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp index 997a1677296c..171837386e40 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,4 +1,8 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -
-
+ + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 90f86d23e672..eff894abbee0 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -8,5 +8,8 @@ <fmt:message key="app.title"/> ${pageContext.request.requestURL} + + + diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp index 0208cf97703b..b5cb5636d33c 100644 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -6,19 +6,23 @@ -
-
- : - - -
    -
  • -
  • -
-
+
+
+

+ +
+ : + + +

    +
  • +
  • +
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index e3b6ed640305..7870f02ad5b2 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -7,31 +7,46 @@ -
-

-
- - - - - - - - - - - - - - - - - - - -
NameEmailRolesActiveRegistered
${user.email}${user.roles}<%=user.isEnabled()%> -
-
+
+
+
+

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameEmailRolesActiveRegistered
${user.email}${user.roles} + checked/> + EditDelete
+
+
+
+
From dca398c74d12fabc5cf76a26d3e853a8bad70bef Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 8 Jun 2016 00:04:28 +0500 Subject: [PATCH 099/166] 8-07-ajax-datatables --- .../web/meal/AbstractUserMealController.java | 4 +- .../topjava/web/user/AdminAjaxController.java | 40 +++++++ src/main/webapp/WEB-INF/jsp/userList.jsp | 112 +++++++++++++++++- .../webapp/resources/js/datatablesUtil.js | 49 ++++++++ 4 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java create mode 100644 src/main/webapp/resources/js/datatablesUtil.js diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java index c045bc7e6428..487bcbf3ae09 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java @@ -3,7 +3,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.service.UserMealService; @@ -19,8 +18,7 @@ * GKislin * 06.03.2015. */ -@Controller -public class AbstractUserMealController { +public abstract class AbstractUserMealController { private static final Logger LOG = LoggerFactory.getLogger(AbstractUserMealController.class); @Autowired diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java new file mode 100644 index 000000000000..80829dc621d3 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -0,0 +1,40 @@ +package ru.javawebinar.topjava.web.user; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +/** + * User: grigory.kislin + */ +@RestController +@RequestMapping("/ajax/admin/users") +public class AdminAjaxController extends AbstractUserController { + + @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + return super.getAll(); + } + + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @RequestMapping(method = RequestMethod.POST) + public void createOrUpdate(@RequestParam("id") int id, + @RequestParam("name") String name, + @RequestParam("email") String email, + @RequestParam("password") String password) { + + User user = new User(id, name, email, password, Role.ROLE_USER); + if (id == 0) { + super.create(user); + } else { + super.update(user, id); + } + } +} diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index 7870f02ad5b2..2aa01f788536 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -4,6 +4,9 @@ + + + @@ -13,9 +16,9 @@

- + - +
@@ -35,11 +38,11 @@ - - + +
Name${user.roles} checked/> + checked id="${user.id}"/> EditDeleteEditDelete
@@ -48,5 +51,102 @@
+ + - + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js new file mode 100644 index 000000000000..e0feb0a23bf6 --- /dev/null +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -0,0 +1,49 @@ +function makeEditable() { + $('#add').click(function () { + $('#id').val(0); + $('#editRow').modal(); + }); + + $('.delete').click(function () { + deleteRow($(this).attr("id")); + }); + + $('#detailsForm').submit(function () { + save(); + return false; + }); +} + +function deleteRow(id) { + $.ajax({ + url: ajaxUrl + id, + type: 'DELETE', + success: function () { + updateTable(); + } + }); +} + +function updateTable() { + $.get(ajaxUrl, function (data) { + datatableApi.fnClearTable(); + $.each(data, function (key, item) { + datatableApi.fnAddData(item); + }); + datatableApi.fnDraw(); + }); +} + +function save() { + var form = $('#detailsForm'); + debugger; + $.ajax({ + type: "POST", + url: ajaxUrl, + data: form.serialize(), + success: function () { + $('#editRow').modal('hide'); + updateTable(); + } + }); +} \ No newline at end of file From 9886a24099adf05bea5fb3863487a2441d2b3716 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 8 Jun 2016 00:05:07 +0500 Subject: [PATCH 100/166] add image --- src/main/webapp/resources/images/icon-meal.png | Bin 0 -> 1898 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/webapp/resources/images/icon-meal.png diff --git a/src/main/webapp/resources/images/icon-meal.png b/src/main/webapp/resources/images/icon-meal.png new file mode 100644 index 0000000000000000000000000000000000000000..b4fc54ad01294abb103d9238c12b1826a9240dfd GIT binary patch literal 1898 zcmV-w2bK7VP)PS&+k>eS5@~O7WLfN z#z>r1)zQ7@JLlXpd}L<$FKLxT1mO7i`0((sD2l48LI|y1MB+H^bUOWhKS`1&t~@IA z1b~^D88e&NXf)c{*(uAi)iCqP{SZP3!OTRY*Xx}A^;#TU>54SHnYXW#g&y6RSh99^HTs$tZ#2`udlC*h;z;d0}zWj3TB}R zFn|flWT07)I)DiT%x1GFidI)w^E?kB5K$Y-9N@(I#>U3x=4P6v0K6d;BWwP3q;Kx$ z_a^B<71hQG`=w-hwO4#}p&VpZ25mf4sA(GS{pHJ-yWQ>y2H8Vl=B=%*jg5^g%R(>^ zpsL+&Chb!3l!F8yFZyq(Lj5wj%J{QS=1FV`-RXv|gz1JiLuH$O`+tkPjc%tTC8 zFh=;G>b&~)rPnv3Ze$)5$8pm%o12^M8_WSlqmlRCIcLNj^|#;a|88fwA6w$=xgmY{ z3@O6{xx>xRAXX4z=JADR>E-A6R2)3HpZxQ}ojuAO`G7P{_xJaw(g~evn6dlC zz2#Zsoia0v$b$zDh=_^k=;$bf;GAP-3;wnBGbR$umb>)D6^v`t0WV%eKS2{4Wl=yZli zQ3XsrIx+ew0zib-1)S^QbOHd(2!?BCD8SOl{xn)BYg1tn0Wg_N*vzV`a?TO4WBc1d zR;d)!V2&CN+Nj4+MfR07QZ`!jIp;_5>jO?C-d9fIKE* zu-I^;zq+&~{XOY%# z5#czF=Rpv~PA_$85Q)&-ghCj?bBuPEs6ZGZPzDp6%=1G+xi%NO7D!e2q_RG|zaIE4tDSi`uGlo$I_gH^9nL%#7Qm zwy>};o6WrUPPl}7=~DWQ)4QejGoqQdqOrEijv55RG{6antne1h4gzPPym{gH%CI-} zh+{V%kK;IQ>CAj(647`(-rL(-UVf&o8)A;F{=@y^)=no$GbJ3?U|1qpi10xPAv8v@ zz){Gm34shDAOyfb1Qrm7fW`-5YB|H<;o)E~NYk{<`O}$Qi%}HC?(S}yru}~3 zIcGo!W-x#-pHg5VCNi_KEDsJ2h-fevsOqEEp9DC8X(Ky0I4FujRns(`7d6kT&U;_i z^>jKd%aVwCy Date: Wed, 8 Jun 2016 00:12:30 +0500 Subject: [PATCH 101/166] 8-08-notification --- .../webapp/resources/js/datatablesUtil.js | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index e0feb0a23bf6..1373dae47fce 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -12,6 +12,10 @@ function makeEditable() { save(); return false; }); + + $(document).ajaxError(function (event, jqXHR, options, jsExc) { + failNoty(event, jqXHR, options, jsExc); + }); } function deleteRow(id) { @@ -20,6 +24,7 @@ function deleteRow(id) { type: 'DELETE', success: function () { updateTable(); + successNoty('Deleted'); } }); } @@ -44,6 +49,35 @@ function save() { success: function () { $('#editRow').modal('hide'); updateTable(); + successNoty('Saved'); } }); -} \ No newline at end of file +} + +var failedNote; + +function closeNoty() { + if (failedNote) { + failedNote.close(); + failedNote = undefined; + } +} + +function successNoty(text) { + closeNoty(); + noty({ + text: text, + type: 'success', + layout: 'bottomRight', + timeout: true + }); +} + +function failNoty(event, jqXHR, options, jsExc) { + closeNoty(); + failedNote = noty({ + text: 'Failed: ' + jqXHR.statusText + "
", + type: 'error', + layout: 'bottomRight' + }); +} From d099f5224545471b634bac10917e3dcfffb88f3b Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Wed, 8 Jun 2016 00:24:29 +0500 Subject: [PATCH 102/166] 8-09-add-security --- pom.xml | 14 ++++++++++ src/main/resources/logback.xml | 1 + src/main/resources/spring/spring-app.xml | 1 + src/main/resources/spring/spring-security.xml | 27 +++++++++++++++++++ src/main/webapp/WEB-INF/web.xml | 10 +++++++ 5 files changed, 53 insertions(+) create mode 100644 src/main/resources/spring/spring-security.xml diff --git a/pom.xml b/pom.xml index 94e58f71bc1e..4ea38107a689 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,8 @@ 1.9.4.RELEASE 4.2.5.RELEASE + 4.0.4.RELEASE + 7.0.57 @@ -149,6 +151,18 @@
+ + + org.springframework.security + spring-security-web + ${spring-security.version} + + + org.springframework.security + spring-security-config + ${spring-security.version} + + org.webjars diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 3e3cd2275648..a767a026c744 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -25,6 +25,7 @@ + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index c3e7587cfdea..9f6ede6c29db 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -12,6 +12,7 @@ --> + diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml new file mode 100644 index 000000000000..feb591e856b6 --- /dev/null +++ b/src/main/resources/spring/spring-security.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index abc0d013bcfc..05945432bf59 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -36,6 +36,16 @@ / + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + encodingFilter org.springframework.web.filter.CharacterEncodingFilter From 39d1821858ec000c8e6b5ffc166db6288986b88a Mon Sep 17 00:00:00 2001 From: ilnur Serbaev Date: Wed, 8 Jun 2016 21:50:10 +0600 Subject: [PATCH 103/166] Create systemEnviromentCentOS --- systemEnviromentCentOS | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 systemEnviromentCentOS diff --git a/systemEnviromentCentOS b/systemEnviromentCentOS new file mode 100644 index 000000000000..f6c5bebd3caa --- /dev/null +++ b/systemEnviromentCentOS @@ -0,0 +1,17 @@ +1. +Create a file called topjava.sh under /etc/profile.d/ directory. + vi /etc/profile.d/topjava.sh + +2. +Add the following contents: + #!/bin/bash + TOPJAVA_ROOT=/home/ilnur/repo/topjava + PATH=$TOPJAVA_ROOT/bin:$PATH + export PATH TOPJAVA_ROOT + export CLASSPATH=. + +3.Save and close the file. +4.Make it executable using the following command. + chmod +x /etc/profile.d/topjava.sh +5.Then, set the environment variables permanently by running the following command: + source /etc/profile.d/topjava.sh From 086a5008ed4dda094b0dc7a123216fbdabd5de4f Mon Sep 17 00:00:00 2001 From: ilnur Serbaev Date: Thu, 9 Jun 2016 14:55:20 +0500 Subject: [PATCH 104/166] Update systemEnviromentCentOS --- systemEnviromentCentOS | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/systemEnviromentCentOS b/systemEnviromentCentOS index f6c5bebd3caa..ecd0a64895c3 100644 --- a/systemEnviromentCentOS +++ b/systemEnviromentCentOS @@ -10,7 +10,10 @@ Add the following contents: export PATH TOPJAVA_ROOT export CLASSPATH=. -3.Save and close the file. +3.Save and close the file. + :w + :q + 4.Make it executable using the following command. chmod +x /etc/profile.d/topjava.sh 5.Then, set the environment variables permanently by running the following command: From 886c7825a6ae6cad9fa274a670fe56b0a169ae05 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 12:00:26 +0500 Subject: [PATCH 105/166] 9-00-fix --- pom.xml | 6 +++--- .../topjava/repository/jdbc/JdbcUserRepositoryImpl.java | 8 ++++---- .../topjava/web/meal/UserMealRestController.java | 5 +---- .../topjava/web/user/AbstractUserController.java | 4 ++-- .../javawebinar/topjava/web/user/AdminRestController.java | 2 +- src/main/webapp/WEB-INF/jsp/userList.jsp | 4 ++-- .../javawebinar/topjava/web/AbstractControllerTest.java | 2 +- .../topjava/web/meal/UserMealRestControllerTest.java | 4 ++-- .../topjava/web/user/AdminRestControllerTest.java | 4 ++-- .../topjava/web/user/ProfileRestControllerTest.java | 2 +- 10 files changed, 19 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 4ea38107a689..256436335061 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 @@ -43,7 +43,7 @@ 3.3.6 2.2.3 - 2.5.1 + 2.4.7 2.3.8 1.10.11 @@ -175,7 +175,7 @@ ${webjars-jquery.version} - org.webjars + org.webjars.bower datetimepicker ${webjars-datetimepicker.version} diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java index 7840410ae2ed..5d9a6813ada9 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -94,14 +94,14 @@ public List getAll() { List users = jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); class UserRole { - UserRole(Role role, int userId) { + final private Role role; + final private int userId; + + private UserRole(Role role, int userId) { this.role = role; this.userId = userId; } - final private Role role; - final private int userId; - public Role getRole() { return role; } diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java index 18b515123ba2..eb45e3997bc0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java @@ -14,7 +14,6 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.to.UserMealWithExceed; -import ru.javawebinar.topjava.util.TimeUtil; @RestController @RequestMapping(value = UserMealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) @@ -63,8 +62,6 @@ public List getBetween( public List getBetween( @RequestParam(value = "startDate", required = false) LocalDate startDate, @RequestParam(value = "startTime", required = false) LocalTime startTime, @RequestParam(value = "endDate", required = false) LocalDate endDate, @RequestParam(value = "endTime", required = false) LocalTime endTime) { - return super.getBetween( - startDate != null ? startDate : TimeUtil.MIN_DATE, startTime != null ? startTime : LocalTime.MIN, - endDate != null ? endDate : TimeUtil.MAX_DATE, endTime != null ? endTime : LocalTime.MAX); + return super.getBetween(startDate, startTime, endDate, endTime); } } 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 23cc33c46fc9..438d29f86408 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -1,15 +1,15 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.beans.factory.annotation.Autowired; 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; public abstract class AbstractUserController { - protected final Logger log = LoggerFactory.getLogger(getClass()); + private final Logger log = LoggerFactory.getLogger(getClass()); @Autowired private UserService service; 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 b4900143c70b..411ed71cfe80 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -12,7 +12,7 @@ @RestController @RequestMapping(AdminRestController.REST_URL) public class AdminRestController extends AbstractUserController { - static final String REST_URL = "/rest/admin/users"; + public static final String REST_URL = "/rest/admin/users"; @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index 2aa01f788536..7f31c90c428d 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -5,7 +5,7 @@ - + @@ -100,7 +100,7 @@ - + diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index ebf46a864743..9099f765c296 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -50,7 +50,7 @@ abstract public class AbstractControllerTest { private WebApplicationContext webApplicationContext; @PostConstruct - void postConstruct() { + private void postConstruct() { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .addFilter(CHARACTER_ENCODING_FILTER) diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java index 124a7f147b83..3d525fd0d662 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java @@ -23,7 +23,7 @@ public class UserMealRestControllerTest extends AbstractControllerTest { - public static final String REST_URL = UserMealRestController.REST_URL + '/'; + private static final String REST_URL = UserMealRestController.REST_URL + '/'; @Autowired private UserMealService service; @@ -39,7 +39,7 @@ public void testGet() throws Exception { @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON)) + mockMvc.perform(delete(REST_URL + MEAL1_ID)) .andExpect(status().isOk()); MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(START_SEQ)); } diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 3b48ae259bf5..71f9d0b32f2d 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -20,7 +20,7 @@ public class AdminRestControllerTest extends AbstractControllerTest { - public static final String REST_URL = AdminRestController.REST_URL + '/'; + private static final String REST_URL = AdminRestController.REST_URL + '/'; @Test public void testGet() throws Exception { @@ -41,7 +41,7 @@ public void testGetByEmail() throws Exception { @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL + USER_ID).contentType(MediaType.APPLICATION_JSON)) + mockMvc.perform(delete(REST_URL + USER_ID)) .andDo(print()) .andExpect(status().isOk()); MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index 72bf53f4cd8a..068445707264 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -30,7 +30,7 @@ public void testGet() throws Exception { @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL).contentType(MediaType.APPLICATION_JSON)) + mockMvc.perform(delete(REST_URL)) .andExpect(status().isOk()); MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); } From fc1e36563f7e414ad179570c4de0b0132f913f41 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 12:10:50 +0500 Subject: [PATCH 106/166] 9-01-HW8 --- config/messages/app.properties | 1 + config/messages/app_ru.properties | 1 + .../topjava/web/RootController.java | 7 + .../web/meal/JspUserMealController.java | 84 ------- .../web/meal/UserMealAjaxController.java | 51 ++++ src/main/webapp/WEB-INF/jsp/mealEdit.jsp | 33 --- src/main/webapp/WEB-INF/jsp/mealList.jsp | 232 ++++++++++++++---- src/main/webapp/WEB-INF/jsp/userList.jsp | 14 +- src/main/webapp/resources/css/style.css | 25 +- .../webapp/resources/js/datatablesUtil.js | 13 +- 10 files changed, 257 insertions(+), 204 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/UserMealAjaxController.java delete mode 100644 src/main/webapp/WEB-INF/jsp/mealEdit.jsp diff --git a/config/messages/app.properties b/config/messages/app.properties index 4d85a5c2f69f..c141beb0e890 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -8,5 +8,6 @@ users.add=Add user meals.title=Meals list meals.edit=Edit meal +meals.add=Add meal common.select=Select \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 71f99cea84df..cbadeb9c6ebe 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -8,5 +8,6 @@ users.add=Добавить пользователя meals.title=Список еды meals.edit=Редактировать еду +meals.add=Добавить еду common.select=Выбрать \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 0046617052f6..e9277e8e5814 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -8,6 +8,7 @@ import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.service.UserMealService; import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.util.UserMealsUtil; import javax.servlet.http.HttpServletRequest; @@ -34,6 +35,12 @@ public String userList(Model model) { return "userList"; } + @RequestMapping(value = "/meals", method = RequestMethod.GET) + public String mealList(Model model) { + model.addAttribute("mealList", UserMealsUtil.getWithExceeded(mealService.getAll(LoggedUser.id()), LoggedUser.getCaloriesPerDay())); + return "mealList"; + } + @RequestMapping(value = "/users", method = RequestMethod.POST) public String setUser(HttpServletRequest request) { int userId = Integer.valueOf(request.getParameter("userId")); diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java deleted file mode 100644 index 148810c687db..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java +++ /dev/null @@ -1,84 +0,0 @@ -package ru.javawebinar.topjava.web.meal; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.util.TimeUtil; - -import javax.servlet.http.HttpServletRequest; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Objects; - -/** - * GKislin - * 06.03.2015. - */ -@Controller -@RequestMapping(value = "/meals") -public class JspUserMealController extends AbstractUserMealController { - - @RequestMapping(method = RequestMethod.GET) - public String mealList(Model model) { - model.addAttribute("mealList", super.getAll()); - return "mealList"; - } - - @RequestMapping(value = "/delete", method = RequestMethod.GET) - public String delete(HttpServletRequest request) { - super.delete(getId(request)); - return "redirect:/meals"; - } - - @RequestMapping(value = "/update", method = RequestMethod.GET) - public String editForUpdate(HttpServletRequest request, Model model) { - model.addAttribute("meal", super.get(getId(request))); - return "mealEdit"; - } - - @RequestMapping(value = "/create", method = RequestMethod.GET) - public String editForCreate(Model model) { - model.addAttribute("meal", new UserMeal(LocalDateTime.now(), "", 1000)); - return "mealEdit"; - } - - @RequestMapping(method = RequestMethod.POST) - public String updateOrCreate(HttpServletRequest request) { - String id = request.getParameter("id"); - UserMeal userMeal = new UserMeal(id.isEmpty() ? null : Integer.valueOf(id), - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.valueOf(request.getParameter("calories"))); - - if (userMeal.isNew()) { - super.create(userMeal); - } else { - super.update(userMeal, userMeal.getId()); - } - return "redirect:/meals"; - } - - @RequestMapping(value = "/filter", method = RequestMethod.POST) - public String getBetween(HttpServletRequest request, Model model) { - LocalDate startDate = TimeUtil.parseLocalDate(resetParam("startDate", request)); - LocalDate endDate = TimeUtil.parseLocalDate(resetParam("endDate", request)); - LocalTime startTime = TimeUtil.parseLocalTime(resetParam("startTime", request)); - LocalTime endTime = TimeUtil.parseLocalTime(resetParam("endTime", request)); - model.addAttribute("mealList", super.getBetween(startDate, startTime, endDate, endTime)); - return "mealList"; - } - - private String resetParam(String param, HttpServletRequest request) { - String value = request.getParameter(param); - request.setAttribute(param, value); - return value; - } - - private int getId(HttpServletRequest request) { - String paramId = Objects.requireNonNull(request.getParameter("id")); - return Integer.valueOf(paramId); - } -} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealAjaxController.java new file mode 100644 index 000000000000..75fd57fbd050 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealAjaxController.java @@ -0,0 +1,51 @@ +package ru.javawebinar.topjava.web.meal; + +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.to.UserMealWithExceed; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +/** + * GKislin + * 06.03.2015. + */ +@RestController +@RequestMapping(value = "/ajax/profile/meals") +public class UserMealAjaxController extends AbstractUserMealController { + + @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + return super.getAll(); + } + + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @RequestMapping(method = RequestMethod.POST) + public void updateOrCreate(@RequestParam("id") int id, + @RequestParam("datetime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime, + @RequestParam("description") String description, + @RequestParam("calories") int calories) { + UserMeal meal = new UserMeal(id, dateTime, description, calories); + if (id == 0) { + super.create(meal); + } else { + super.update(meal, id); + } + } + + @RequestMapping(value = "/filter", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public List getBetween( + @RequestParam(value = "startDate", required = false) LocalDate startDate, @RequestParam(value = "startTime", required = false) LocalTime startTime, + @RequestParam(value = "endDate", required = false) LocalDate endDate, @RequestParam(value = "endTime", required = false) LocalTime endTime) { + return super.getBetween(startDate, startTime, endDate, endTime); + } +} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/mealEdit.jsp b/src/main/webapp/WEB-INF/jsp/mealEdit.jsp deleted file mode 100644 index 3df0c26c6dc9..000000000000 --- a/src/main/webapp/WEB-INF/jsp/mealEdit.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ 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" %> - - - - -
-

Home

-

-
- -
- -
-
DateTime:
-
-
-
-
Description:
-
-
-
-
Calories:
-
-
- - -
-
- - - diff --git a/src/main/webapp/WEB-INF/jsp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp index a29dcedd0645..b75075a2bd5a 100644 --- a/src/main/webapp/WEB-INF/jsp/mealList.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp @@ -4,59 +4,189 @@ <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + -
- <%--http://stackoverflow.com/questions/10327390/how-should-i-get-root-folder-path-in-jsp-page--%> -

Home

-

-
-
-
From Date:
-
-
-
-
To Date:
-
-
-
-
From Time:
-
-
-
-
To Time:
-
-
- -
-
- Add Meal -
- - - - - - - - - - - - - - - - - - - - -
DateDescriptionCalories
- <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> - <%=TimeUtil.toString(meal.getDateTime())%> - ${meal.description}${meal.calories}UpdateDelete
+ +
+
+
+

+ +
+
+
+ + +
+ +
+ + + +
+ +
+
+
+ + +
+ +
+ + + +
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
DateDescriptionCalories
+ <%----%> + <%----%> + <%=TimeUtil.toString(meal.getDateTime())%> + ${meal.description}${meal.calories}EditDelete
+
+
+
+
+ + - + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index 7f31c90c428d..a024f7ef2887 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -5,7 +5,6 @@ - @@ -100,7 +99,6 @@ - @@ -109,11 +107,17 @@ var ajaxUrl = 'ajax/admin/users/'; var datatableApi; + function updateTable() { + $.get(ajaxUrl, function (data) { + updateTableByData(data); + }); + } + // $(document).ready(function () { $(function () { datatableApi = $('#datatable').dataTable({ "bPaginate": false, - "bInfo": false, + "bInfo": true, "aoColumns": [ { "mData": "name" @@ -131,11 +135,11 @@ "mData": "registered" }, { - "sDefaultContent": "", + "sDefaultContent": "Edit", "bSortable": false }, { - "sDefaultContent": "", + "sDefaultContent": "Delete", "bSortable": false } ], diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index d4807fb38efb..2c6f9fc9969d 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -1,20 +1,3 @@ -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; -} - .normal { color: green; } @@ -23,10 +6,6 @@ dd { color: red; } -header, footer { - background: none repeat scroll 0 0 #A6C9E2; - color: #2E6E9E; - font-size: 20px; - padding: 5px 20px; - margin: 6px 0; +.footer { + padding-bottom: 15px; } diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 1373dae47fce..efd103599ce4 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -29,19 +29,16 @@ function deleteRow(id) { }); } -function updateTable() { - $.get(ajaxUrl, function (data) { - datatableApi.fnClearTable(); - $.each(data, function (key, item) { - datatableApi.fnAddData(item); - }); - datatableApi.fnDraw(); +function updateTableByData(data) { + datatableApi.fnClearTable(); + $.each(data, function (key, item) { + datatableApi.fnAddData(item); }); + datatableApi.fnDraw(); } function save() { var form = $('#detailsForm'); - debugger; $.ajax({ type: "POST", url: ajaxUrl, From a41b7de66b1c3fe821ecb27cb030e90a81503323 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 12:12:56 +0500 Subject: [PATCH 107/166] 9-02-HW8-enable-disable --- .../topjava/service/UserService.java | 2 ++ .../topjava/service/UserServiceImpl.java | 10 +++++++ .../web/user/AbstractUserController.java | 5 ++++ .../topjava/web/user/AdminAjaxController.java | 5 ++++ src/main/webapp/WEB-INF/jsp/mealList.jsp | 6 ++--- src/main/webapp/WEB-INF/jsp/userList.jsp | 14 +++++++--- .../webapp/resources/js/datatablesUtil.js | 27 ++++++++++++------- 7 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 286842b698a8..ac165f0d5030 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -22,5 +22,7 @@ public interface UserService { void evictCache(); + void enable(int id, boolean enable); + User getWithMeals(int id); } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 00488c762e65..24ae8f237d38 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -4,6 +4,7 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.ExceptionUtil; @@ -52,6 +53,15 @@ public void update(User user) { public void evictCache() { } + @CacheEvict(value = "users", allEntries = true) + @Override + @Transactional + public void enable(int id, boolean enabled) { + User user = get(id); + user.setEnabled(enabled); + repository.save(user); + } + @Override public User getWithMeals(int id) { return ExceptionUtil.check(repository.getWithMeals(id), id); 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 438d29f86408..16579ac00bb8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -45,4 +45,9 @@ public User getByMail(String email) { log.info("getByEmail " + email); return service.getByEmail(email); } + + public void enable(int id, boolean enabled) { + log.info((enabled ? "enable " : "disable ") + id); + service.enable(id, enabled); + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index 80829dc621d3..12148208eb16 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -37,4 +37,9 @@ public void createOrUpdate(@RequestParam("id") int id, super.update(user, id); } } + + @RequestMapping(value = "/{id}", method = RequestMethod.POST) + public void enabled(@PathVariable("id") int id, @RequestParam("enabled") boolean enabled) { + super.enable(id, enabled); + } } diff --git a/src/main/webapp/WEB-INF/jsp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp index b75075a2bd5a..2e52387b56c7 100644 --- a/src/main/webapp/WEB-INF/jsp/mealList.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp @@ -49,7 +49,7 @@ - + @@ -70,8 +70,8 @@ - - + +
${meal.description} ${meal.calories}EditDeleteEditDelete
diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index a024f7ef2887..16b92d8a330d 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -15,7 +15,7 @@

- + @@ -37,11 +37,12 @@ - - + +
${user.roles} checked id="${user.id}"/> + checked + onclick="enable($(this), ${user.id})"/> EditDeleteEditDelete
@@ -151,6 +152,11 @@ ] }); makeEditable(); + $(':checkbox').each(function () { + if (!$(this).is(":checked")) { + $(this).closest('tr').css("text-decoration", "line-through"); + } + }); }); \ No newline at end of file diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index efd103599ce4..17046938f917 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -1,13 +1,4 @@ function makeEditable() { - $('#add').click(function () { - $('#id').val(0); - $('#editRow').modal(); - }); - - $('.delete').click(function () { - deleteRow($(this).attr("id")); - }); - $('#detailsForm').submit(function () { save(); return false; @@ -18,6 +9,11 @@ function makeEditable() { }); } +function add() { + $('#id').val(0); + $('#editRow').modal(); +} + function deleteRow(id) { $.ajax({ url: ajaxUrl + id, @@ -29,6 +25,19 @@ function deleteRow(id) { }); } +function enable(chkbox, id) { + var enabled = chkbox.is(":checked"); + chkbox.closest('tr').css("text-decoration", enabled ? "none" : "line-through"); + $.ajax({ + url: ajaxUrl + id, + type: 'POST', + data: 'enabled=' + enabled, + success: function () { + successNoty(enabled ? 'Enabled' : 'Disabled'); + } + }); +} + function updateTableByData(data) { datatableApi.fnClearTable(); $.each(data, function (key, item) { From 8589f674e57a60603a3a57ecce23e5567c0c3077 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 12:20:00 +0500 Subject: [PATCH 108/166] 9-03-HW8-newApi --- src/main/webapp/WEB-INF/jsp/mealList.jsp | 24 ++++++++-------- src/main/webapp/WEB-INF/jsp/userList.jsp | 28 +++++++++---------- .../webapp/resources/js/datatablesUtil.js | 6 +--- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/main/webapp/WEB-INF/jsp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp index 2e52387b56c7..10d2a3db0da5 100644 --- a/src/main/webapp/WEB-INF/jsp/mealList.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp @@ -151,30 +151,30 @@ } $(function () { - datatableApi = $('#datatable').dataTable( + datatableApi = $('#datatable').DataTable( { - "bPaginate": false, - "bInfo": true, - "aoColumns": [ + "paging": false, + "info": true, + "columns": [ { - "mData": "dateTime" + "data": "dateTime" }, { - "mData": "description" + "data": "description" }, { - "mData": "calories" + "data": "calories" }, { - "sDefaultContent": "Edit", - "bSortable": false + "defaultContent": "Edit", + "orderable": false }, { - "sDefaultContent": "Delete", - "bSortable": false + "defaultContent": "Delete", + "orderable": false } ], - "aaSorting": [ + "order": [ [ 0, "desc" diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index 16b92d8a330d..6f22d3480e1c 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -116,35 +116,35 @@ // $(document).ready(function () { $(function () { - datatableApi = $('#datatable').dataTable({ - "bPaginate": false, - "bInfo": true, - "aoColumns": [ + datatableApi = $('#datatable').DataTable({ + "paging": false, + "info": true, + "columns": [ { - "mData": "name" + "data": "name" }, { - "mData": "email" + "data": "email" }, { - "mData": "roles" + "data": "roles" }, { - "mData": "enabled" + "data": "enabled" }, { - "mData": "registered" + "data": "registered" }, { - "sDefaultContent": "Edit", - "bSortable": false + "defaultContent": "Edit", + "orderable": false }, { - "sDefaultContent": "Delete", - "bSortable": false + "defaultContent": "Delete", + "orderable": false } ], - "aaSorting": [ + "order": [ [ 0, "asc" diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 17046938f917..67165b8a353c 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -39,11 +39,7 @@ function enable(chkbox, id) { } function updateTableByData(data) { - datatableApi.fnClearTable(); - $.each(data, function (key, item) { - datatableApi.fnAddData(item); - }); - datatableApi.fnDraw(); + datatableApi.clear().rows.add(data).draw(); } function save() { From a2887ef9906e4308ed85ae36b0d1b45705254e67 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 12:28:52 +0500 Subject: [PATCH 109/166] 9-04-binding --- .../ru/javawebinar/topjava/LoggedUser.java | 5 +- .../ru/javawebinar/topjava/model/User.java | 30 ++++----- .../ru/javawebinar/topjava/to/UserTo.java | 66 +++++++++++++++++++ .../topjava/util/UserMealsUtil.java | 6 +- .../ru/javawebinar/topjava/util/UserUtil.java | 16 +++++ .../topjava/web/user/AdminAjaxController.java | 16 ++--- .../ru/javawebinar/topjava/UserTestData.java | 4 +- 7 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/to/UserTo.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/UserUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java index 65cd3a023472..0d9c0d13f425 100644 --- a/src/main/java/ru/javawebinar/topjava/LoggedUser.java +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -1,8 +1,7 @@ package ru.javawebinar.topjava; -import ru.javawebinar.topjava.util.UserMealsUtil; - import ru.javawebinar.topjava.model.BaseEntity; +import ru.javawebinar.topjava.util.UserUtil; public class LoggedUser { private static int id = BaseEntity.START_SEQ; @@ -19,6 +18,6 @@ public static void setId(int id) { } public static int getCaloriesPerDay() { - return UserMealsUtil.DEFAULT_CALORIES_PER_DAY; + return UserUtil.DEFAULT_CALORIES_PER_DAY; } } diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index f626f787d3e1..d1302efb087c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -5,7 +5,7 @@ import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; -import ru.javawebinar.topjava.util.UserMealsUtil; +import ru.javawebinar.topjava.util.UserUtil; import javax.persistence.*; import javax.validation.constraints.Digits; @@ -57,7 +57,7 @@ public class User extends NamedEntity { @Column(name = "calories_per_day", columnDefinition = "default 2000") @Digits(fraction = 0, integer = 4) - protected int caloriesPerDay = UserMealsUtil.DEFAULT_CALORIES_PER_DAY; + protected int caloriesPerDay = UserUtil.DEFAULT_CALORIES_PER_DAY; @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "user") @OrderBy("dateTime DESC") @@ -68,7 +68,7 @@ public User() { } public User(Integer id, String name, String email, String password, Role role, Role... roles) { - this(id, name, email, password, UserMealsUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + this(id, name, email, password, UserUtil.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) { @@ -88,10 +88,6 @@ public void setEmail(String email) { this.email = email; } - public void setPassword(String password) { - this.password = password; - } - public Date getRegistered() { return registered; } @@ -100,10 +96,6 @@ public void setRegistered(Date registered) { this.registered = registered; } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - public int getCaloriesPerDay() { return caloriesPerDay; } @@ -116,20 +108,28 @@ public boolean isEnabled() { return enabled; } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public Set getRoles() { return roles; } + public void setRoles(List roles) { + this.roles = EnumSet.copyOf(roles); + } + public String getPassword() { return password; } - public List getMeals() { - return meals; + public void setPassword(String password) { + this.password = password; } - public void setRoles(List roles) { - this.roles = EnumSet.copyOf(roles); + public List getMeals() { + return meals; } @Override 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..41f74b5dc8f6 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -0,0 +1,66 @@ +package ru.javawebinar.topjava.to; + +public class UserTo { + protected int id; + + protected String name; + + protected String email; + + protected String password; + + public UserTo() { + } + + public UserTo(int id, String name, String email, String password) { + this.id = id; + this.name = name; + this.email = email; + this.password = password; + } + + public void setId(int id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = (id == null ? 0 : id); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public String toString() { + return "UserTo{" + + "id=" + id + + ", name='" + name + '\'' + + ", email='" + email + '\'' + + '}'; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index a788db999ca5..c9da78f456a3 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -20,13 +20,11 @@ public class UserMealsUtil { new UserMeal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) ); - public static final int DEFAULT_CALORIES_PER_DAY = 2000; - public static void main(String[] args) { - List filteredMealsWithExceeded = getFilteredWithExceeded(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY); + List filteredMealsWithExceeded = getFilteredWithExceeded(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), UserUtil.DEFAULT_CALORIES_PER_DAY); filteredMealsWithExceeded.forEach(System.out::println); - System.out.println(getFilteredWithExceededByCycle(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY)); + System.out.println(getFilteredWithExceededByCycle(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), UserUtil.DEFAULT_CALORIES_PER_DAY)); } public static List getWithExceeded(Collection mealList, int caloriesPerDay) { diff --git a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java new file mode 100644 index 000000000000..35bd146c5802 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java @@ -0,0 +1,16 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.UserTo; + +/** + * GKislin + */ +public class UserUtil { + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + + public static User createFromTo(UserTo newUser) { + return new User(null, newUser.getName(), newUser.getEmail().toLowerCase(), newUser.getPassword(), Role.ROLE_USER); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index 12148208eb16..15fcfb3c0d43 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -2,8 +2,9 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.UserTo; +import ru.javawebinar.topjava.util.UserUtil; import java.util.List; @@ -25,16 +26,9 @@ public void delete(@PathVariable("id") int id) { } @RequestMapping(method = RequestMethod.POST) - public void createOrUpdate(@RequestParam("id") int id, - @RequestParam("name") String name, - @RequestParam("email") String email, - @RequestParam("password") String password) { - - User user = new User(id, name, email, password, Role.ROLE_USER); - if (id == 0) { - super.create(user); - } else { - super.update(user, id); + public void createOrUpdate(UserTo userTo) { + if (userTo.getId() == 0) { + super.create(UserUtil.createFromTo(userTo)); } } diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 4a4bf6fc7414..186b02f7ea14 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -3,7 +3,7 @@ import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.util.UserMealsUtil; +import ru.javawebinar.topjava.util.UserUtil; import java.util.EnumSet; import java.util.Objects; @@ -27,7 +27,7 @@ public TestUser(User u) { } public TestUser(String name, String email, String password, Role role, Role... roles) { - this(null, name, email, password, UserMealsUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + this(null, name, email, password, UserUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); } public TestUser(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { From 7576c9b9512b546ccae881837d44bb5a7d784fb9 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 12:49:00 +0500 Subject: [PATCH 110/166] 9-05-update --- .../topjava/service/UserService.java | 3 +++ .../topjava/service/UserServiceImpl.java | 17 +++++++++++++++++ .../ru/javawebinar/topjava/util/UserUtil.java | 7 +++++++ .../web/user/AbstractUserController.java | 6 ++++++ .../topjava/web/user/AdminAjaxController.java | 7 +++++++ src/main/webapp/WEB-INF/jsp/userList.jsp | 2 +- src/main/webapp/resources/js/datatablesUtil.js | 17 +++++++++++++++-- 7 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index ac165f0d5030..252369e30567 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -2,6 +2,7 @@ import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.util.List; @@ -16,6 +17,8 @@ public interface UserService { User getByEmail(String email) throws NotFoundException; + void update(UserTo user); + List getAll(); void update(User user); diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 24ae8f237d38..ecb69b91f851 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -7,6 +7,8 @@ import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.to.UserTo; +import ru.javawebinar.topjava.util.UserUtil; import ru.javawebinar.topjava.util.exception.ExceptionUtil; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -20,34 +22,49 @@ public class UserServiceImpl implements UserService { private UserRepository repository; @CacheEvict(value = "users", allEntries = true) + @Override public User save(User user) { return repository.save(user); } @CacheEvict(value = "users", allEntries = true) + @Override public void delete(int id) { ExceptionUtil.check(repository.delete(id), id); } + @Override public User get(int id) throws NotFoundException { return ExceptionUtil.check(repository.get(id), id); } + @Override public User getByEmail(String email) throws NotFoundException { Objects.requireNonNull(email, "Email must not be empty"); return ExceptionUtil.check(repository.getByEmail(email), "email=" + email); } @Cacheable("users") + @Override public List getAll() { return repository.getAll(); } @CacheEvict(value = "users", allEntries = true) + @Override public void update(User user) { repository.save(user); } + @CacheEvict(value = "users", allEntries = true) + @Transactional + @Override + public void update(UserTo userTo) { + User user = get(userTo.getId()); + repository.save(UserUtil.updateFromTo(user, userTo)); + } + + @CacheEvict(value = "users", allEntries = true) @Override public void evictCache() { diff --git a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java index 35bd146c5802..4ac14bf258d0 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java @@ -13,4 +13,11 @@ public class UserUtil { public static User createFromTo(UserTo newUser) { return new User(null, newUser.getName(), newUser.getEmail().toLowerCase(), newUser.getPassword(), Role.ROLE_USER); } + + public static User updateFromTo(User user, UserTo userTo) { + user.setName(userTo.getName()); + user.setEmail(userTo.getEmail().toLowerCase()); + user.setPassword(userTo.getPassword()); + return user; + } } 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 16579ac00bb8..a2881433930f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.to.UserTo; import java.util.List; @@ -41,6 +42,11 @@ public void update(User user, int id) { service.update(user); } + public void update(UserTo userTo) { + log.info("update " + userTo); + service.update(userTo); + } + public User getByMail(String email) { log.info("getByEmail " + email); return service.getByEmail(email); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index 15fcfb3c0d43..07651c601621 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -20,6 +20,11 @@ public List getAll() { return super.getAll(); } + @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public User get(@PathVariable("id") int id) { + return super.get(id); + } + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public void delete(@PathVariable("id") int id) { super.delete(id); @@ -29,6 +34,8 @@ public void delete(@PathVariable("id") int id) { public void createOrUpdate(UserTo userTo) { if (userTo.getId() == 0) { super.create(UserUtil.createFromTo(userTo)); + } else { + super.update(userTo); } } diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index 6f22d3480e1c..c87a43e483a3 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -41,7 +41,7 @@ onclick="enable($(this), ${user.id})"/> - Edit + Edit Delete diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 67165b8a353c..43e51cc111b0 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -1,5 +1,9 @@ +var form; + function makeEditable() { - $('#detailsForm').submit(function () { + form = $('#detailsForm'); + + form.submit(function () { save(); return false; }); @@ -10,10 +14,20 @@ function makeEditable() { } function add() { + form.find(":input").val(""); $('#id').val(0); $('#editRow').modal(); } +function updateRow(id) { + $.get(ajaxUrl + id, function (data) { + $.each(data, function (key, value) { + form.find("input[name='" + key + "']").val(value); + }); + $('#editRow').modal(); + }); +} + function deleteRow(id) { $.ajax({ url: ajaxUrl + id, @@ -43,7 +57,6 @@ function updateTableByData(data) { } function save() { - var form = $('#detailsForm'); $.ajax({ type: "POST", url: ajaxUrl, From 4cf7cbf51c45c7529a947fa751cce40b9c5c80ad Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 13:03:41 +0500 Subject: [PATCH 111/166] 9-06-validation --- src/main/java/ru/javawebinar/topjava/to/UserTo.java | 9 +++++++++ .../topjava/web/user/AdminAjaxController.java | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index 41f74b5dc8f6..b0609cae9e05 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -1,12 +1,21 @@ package ru.javawebinar.topjava.to; +import org.hibernate.validator.constraints.Email; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.Size; + public class UserTo { protected int id; + @NotEmpty protected String name; + @Email + @NotEmpty protected String email; + @Size(min = 5, max = 64, message = " must between 5 and 64 characters") protected String password; public UserTo() { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index 07651c601621..797a26219f2f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -1,11 +1,15 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; +import javax.validation.Valid; import java.util.List; /** @@ -31,12 +35,18 @@ public void delete(@PathVariable("id") int id) { } @RequestMapping(method = RequestMethod.POST) - public void createOrUpdate(UserTo userTo) { + public ResponseEntity createOrUpdate(@Valid UserTo userTo, BindingResult result) { + if (result.hasErrors()) { + StringBuilder sb = new StringBuilder(); + result.getFieldErrors().forEach(fe -> sb.append(fe.getField()).append(" ").append(fe.getDefaultMessage()).append("
")); + return new ResponseEntity<>(sb.toString(), HttpStatus.UNPROCESSABLE_ENTITY); + } if (userTo.getId() == 0) { super.create(UserUtil.createFromTo(userTo)); } else { super.update(userTo); } + return new ResponseEntity<>(HttpStatus.OK); } @RequestMapping(value = "/{id}", method = RequestMethod.POST) From 604c4680f4eefd2ca41bcc955234eaa20dc195f6 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 13:11:32 +0500 Subject: [PATCH 112/166] 9-07-datatable-via-ajax --- .../topjava/web/RootController.java | 6 +- src/main/webapp/WEB-INF/jsp/userList.jsp | 74 +----------------- .../webapp/resources/js/datatablesUtil.js | 14 ++++ .../webapp/resources/js/userDatatables.js | 77 +++++++++++++++++++ ...ollerTest.java => RootControllerTest.java} | 21 +++-- 5 files changed, 102 insertions(+), 90 deletions(-) create mode 100644 src/main/webapp/resources/js/userDatatables.js rename src/test/java/ru/javawebinar/topjava/web/user/{UserControllerTest.java => RootControllerTest.java} (55%) diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index e9277e8e5814..22b643f974c7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.service.UserMealService; -import ru.javawebinar.topjava.service.UserService; import ru.javawebinar.topjava.util.UserMealsUtil; import javax.servlet.http.HttpServletRequest; @@ -18,8 +17,6 @@ */ @Controller public class RootController { - @Autowired - private UserService userService; @Autowired private UserMealService mealService; @@ -30,8 +27,7 @@ public String root() { } @RequestMapping(value = "/users", method = RequestMethod.GET) - public String userList(Model model) { - model.addAttribute("userList", userService.getAll()); + public String userList() { return "userList"; } diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp index c87a43e483a3..d2273cb37195 100644 --- a/src/main/webapp/WEB-INF/jsp/userList.jsp +++ b/src/main/webapp/WEB-INF/jsp/userList.jsp @@ -29,22 +29,6 @@ - - - - - ${user.email} - ${user.roles} - - checked - onclick="enable($(this), ${user.id})"/> - - - Edit - Delete - -
@@ -103,60 +87,4 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 43e51cc111b0..7630a51fcc60 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -96,3 +96,17 @@ function failNoty(event, jqXHR, options, jsExc) { layout: 'bottomRight' }); } + +function renderEditBtn(data, type, row) { + if (type == 'display') { + return 'Edit'; + } + return data; +} + +function renderDeleteBtn(data, type, row) { + if (type == 'display') { + return 'Delete'; + } + return data; +} \ No newline at end of file diff --git a/src/main/webapp/resources/js/userDatatables.js b/src/main/webapp/resources/js/userDatatables.js new file mode 100644 index 000000000000..29d1489e7399 --- /dev/null +++ b/src/main/webapp/resources/js/userDatatables.js @@ -0,0 +1,77 @@ +var ajaxUrl = 'ajax/admin/users/'; +var datatableApi; + +function updateTable() { + $.get(ajaxUrl, function (data) { + updateTableByData(data); + }); +} + +$(function () { + datatableApi = $('#datatable').DataTable({ + "ajax": { + "url": ajaxUrl, + "dataSrc": "" + }, + "paging": false, + "info": true, + "columns": [ + { + "data": "name" + }, + { + "data": "email", + "render": function (data, type, row) { + if (type == 'display') { + return '' + data + ''; + } + return data; + } + }, + { + "data": "roles" + }, + { + "data": "enabled", + "render": function (data, type, row) { + if (type == 'display') { + return ''; + } + return data; + } + }, + { + "data": "registered", + "render": function (date, type, row) { + if (type == 'display') { + var dateObject = new Date(date); + return '' + dateObject.toISOString().substring(0, 10) + ''; + } + return date; + } + }, + { + "orderable": false, + "defaultContent": "", + "render": renderEditBtn + }, + { + "orderable": false, + "defaultContent": "", + "render": renderDeleteBtn + } + ], + "order": [ + [ + 0, + "asc" + ] + ], + "createdRow": function (row, data, dataIndex) { + if (!data.enabled) { + $(row).css("text-decoration", "line-through"); + } + }, + "initComplete": makeEditable + }); +}); \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java similarity index 55% rename from src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java rename to src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java index f4fb6840231a..9570c93a6a74 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java @@ -3,18 +3,15 @@ import org.junit.Test; import ru.javawebinar.topjava.web.AbstractControllerTest; -import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static ru.javawebinar.topjava.UserTestData.USER; -import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; /** * GKislin * 10.04.2015. */ -public class UserControllerTest extends AbstractControllerTest { +public class RootControllerTest extends AbstractControllerTest { @Test public void testUserList() throws Exception { @@ -22,13 +19,13 @@ public void testUserList() throws Exception { .andDo(print()) .andExpect(status().isOk()) .andExpect(view().name("userList")) - .andExpect(forwardedUrl("/WEB-INF/jsp/userList.jsp")) - .andExpect(model().attribute("userList", hasSize(2))) - .andExpect(model().attribute("userList", hasItem( - allOf( - hasProperty("id", is(START_SEQ)), - hasProperty("name", is(USER.getName())) - ) - ))); + .andExpect(forwardedUrl("/WEB-INF/jsp/userList.jsp")); +// .andExpect(model().attribute("userList", hasSize(2))) +// .andExpect(model().attribute("userList", hasItem( +// allOf( +// hasProperty("id", is(START_SEQ)), +// hasProperty("name", is(USER.getName())) +// ) +// ))); } } \ No newline at end of file From 2d91d0edc1da46e75935e9adece21bfef7d995c4 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 18:08:12 +0500 Subject: [PATCH 113/166] 9-08-min-form-login --- src/main/resources/spring/spring-security.xml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml index feb591e856b6..7142265a19c1 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -1,6 +1,6 @@ - + + + + + + + + From cb194f597899e4044760e18c48343f9d90998c24 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Sun, 12 Jun 2016 18:09:56 +0500 Subject: [PATCH 114/166] 9-09-jsp-form-login --- config/messages/app.properties | 3 +- config/messages/app_ru.properties | 3 +- .../topjava/web/RootController.java | 19 +++-- src/main/resources/spring/spring-mvc.xml | 9 ++- src/main/resources/spring/spring-security.xml | 8 +- .../WEB-INF/jsp/fragments/bodyHeader.jsp | 2 +- src/main/webapp/WEB-INF/jsp/index.jsp | 28 ------- src/main/webapp/WEB-INF/jsp/login.jsp | 80 +++++++++++++++++++ src/main/webapp/resources/css/style.css | 19 +++++ src/main/webapp/test.html | 17 ++++ 10 files changed, 144 insertions(+), 44 deletions(-) delete mode 100644 src/main/webapp/WEB-INF/jsp/index.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/login.jsp create mode 100644 src/main/webapp/test.html diff --git a/config/messages/app.properties b/config/messages/app.properties index c141beb0e890..59d8a5bd861b 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -1,6 +1,7 @@ app.title=Calories management app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application -app.login=Login as +app.login=Login +app.logout=Logout users.title=User list users.edit=Edit user diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index cbadeb9c6ebe..ed7938e6d37f 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -1,6 +1,7 @@ app.title=Подсчет калорий app.footer=Приложение по вебинару Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery -app.login=Зайти как +app.login=Войти +app.logout=Выйти users.title=Список пользователей users.edit=Редактировать пользователя diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 22b643f974c7..d468a83d6e2f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -3,14 +3,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.service.UserMealService; import ru.javawebinar.topjava.util.UserMealsUtil; -import javax.servlet.http.HttpServletRequest; - /** * User: gkislin * Date: 22.08.2014 @@ -23,7 +23,7 @@ public class RootController { @RequestMapping(value = "/", method = RequestMethod.GET) public String root() { - return "index"; + return "redirect:meals"; } @RequestMapping(value = "/users", method = RequestMethod.GET) @@ -37,10 +37,13 @@ public String mealList(Model model) { return "mealList"; } - @RequestMapping(value = "/users", method = RequestMethod.POST) - public String setUser(HttpServletRequest request) { - int userId = Integer.valueOf(request.getParameter("userId")); - LoggedUser.setId(userId); - return "redirect:meals"; + @RequestMapping(value = "/login", method = RequestMethod.GET) + public String login(ModelMap model, + @RequestParam(value = "error", required = false) boolean error, + @RequestParam(value = "message", required = false) String message) { + + model.put("error", error); + model.put("message", message); + return "login"; } } diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index d090e078c7ec..04b6ad321dc3 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -1,12 +1,15 @@ - + + + diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml index 7142265a19c1..2397b44c2934 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -17,13 +17,17 @@ + - - + + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index c1a68e5d526c..3035f2a6a6df 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -11,7 +11,7 @@ diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp deleted file mode 100644 index b5cb5636d33c..000000000000 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ /dev/null @@ -1,28 +0,0 @@ -<%@ 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" %> - - - - - -
-
-

- -

- : - - -
    -
  • -
  • -
-
-
- - - diff --git a/src/main/webapp/WEB-INF/jsp/login.jsp b/src/main/webapp/WEB-INF/jsp/login.jsp new file mode 100644 index 000000000000..d3953f4a6bcd --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/login.jsp @@ -0,0 +1,80 @@ +<%@ 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" %> + + + + + + +
+
+ +
+ ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message} +
+
+ +
+ +
+
+

+ +

User login: user@yandex.ru / password

+ +

Admin login: admin@gmail.com / admin

+ +

Стек технологий: Spring Security, + Spring MVC, + Spring Data JPA, + Spring Security + Test, + Hibernate ORM, + Hibernate Validator, + SLF4J, + Json Jackson, + JSP, + JSTL, + Apache Tomcat, + WebJars, + DataTables plugin, + Ehcache, + PostgreSQL, + JUnit, + Hamcrest, + jQuery, + jQuery notification, + Bootstrap.

+
+
+
+
+    Java Enterprise проект с + регистрацией/авторизацией и интерфейсом на основе ролей (USER, ADMIN). + Администратор может создавать/редактировать/удалять/пользователей, а пользователь - управлять своим + профилем и данными (день, еда, калории) через UI (по AJAX) и по REST интерфейсу с базовой авторизацией. + Возможна фильтрация данных по датам и времени, при этом цвет записи таблицы еды зависит от того, превышает ли + сумма + калорий за день норму (редактируемый параметр в профиле пользователя). + Весь REST интерфейс покрывается JUnit тестами, используя Spring MVC Test и Spring Security Test. +
+
+ + + \ No newline at end of file diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index 2c6f9fc9969d..703d68c2d465 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -9,3 +9,22 @@ .footer { padding-bottom: 15px; } + +.error, .message { + padding: 10px; + margin-bottom: 20px; + border-radius: 4px; + font-size: 16px; +} + +.error { + color: #a94442; + background-color: #f2dede; + border: 1px solid #ebccd1; +} + +.message { + color: #2f9635; + background-color: #c6fbc2; + border: 1px solid #9feba6; +} \ No newline at end of file diff --git a/src/main/webapp/test.html b/src/main/webapp/test.html new file mode 100644 index 000000000000..c42abe696a16 --- /dev/null +++ b/src/main/webapp/test.html @@ -0,0 +1,17 @@ + + + + + +
+
+
+
User list:
+
users
+
Meal list:
+
meals
+
+
+
+ + \ No newline at end of file From 8b1bfd9712431a4da9a4513ed98ac6469088f5f7 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 13 Jun 2016 01:09:25 +0500 Subject: [PATCH 115/166] 9-10-auth-via-user-service --- .../ru/javawebinar/topjava/LoggedUser.java | 44 +++++++++++++++---- .../ru/javawebinar/topjava/model/Role.java | 13 ++++-- .../topjava/service/UserServiceImpl.java | 16 ++++++- .../ru/javawebinar/topjava/to/UserTo.java | 18 +++++++- .../ru/javawebinar/topjava/util/UserUtil.java | 4 ++ src/main/resources/spring/spring-security.xml | 18 +++++--- 6 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java index 0d9c0d13f425..8b3f76be5ea8 100644 --- a/src/main/java/ru/javawebinar/topjava/LoggedUser.java +++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java @@ -1,23 +1,49 @@ package ru.javawebinar.topjava; -import ru.javawebinar.topjava.model.BaseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; -public class LoggedUser { - private static int id = BaseEntity.START_SEQ; +import static java.util.Objects.requireNonNull; +public class LoggedUser extends org.springframework.security.core.userdetails.User { + private final UserTo userTo; - private LoggedUser() { + public LoggedUser(User user) { + super(user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, user.getRoles()); + this.userTo = UserUtil.asTo(user); } - public static int id() { - return id; + public static LoggedUser safeGet() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) { + return null; + } + Object user = auth.getPrincipal(); + return (user instanceof LoggedUser) ? (LoggedUser) user : null; + } + + public static LoggedUser get() { + LoggedUser user = safeGet(); + requireNonNull(user, "No authorized user found"); + return user; } - public static void setId(int id) { - LoggedUser.id = id; + public static int id() { + return get().userTo.getId(); } public static int getCaloriesPerDay() { - return UserUtil.DEFAULT_CALORIES_PER_DAY; + return get().userTo.getCaloriesPerDay(); + } + + public UserTo getUserTo() { + return userTo; + } + + @Override + public String toString() { + return userTo.toString(); } } diff --git a/src/main/java/ru/javawebinar/topjava/model/Role.java b/src/main/java/ru/javawebinar/topjava/model/Role.java index 616f2cc9ee66..e7c8739712d1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Role.java +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -1,6 +1,13 @@ package ru.javawebinar.topjava.model; -public enum Role { +import org.springframework.security.core.GrantedAuthority; + +public enum Role implements GrantedAuthority { ROLE_USER, - ROLE_ADMIN -} + ROLE_ADMIN; + + @Override + public String getAuthority() { + return name(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index ecb69b91f851..0ce373bd617a 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -3,8 +3,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.to.UserTo; @@ -15,8 +18,8 @@ import java.util.List; import java.util.Objects; -@Service -public class UserServiceImpl implements UserService { +@Service("userService") +public class UserServiceImpl implements UserService, UserDetailsService { @Autowired private UserRepository repository; @@ -79,6 +82,15 @@ public void enable(int id, boolean enabled) { repository.save(user); } + @Override + public LoggedUser loadUserByUsername(String email) throws UsernameNotFoundException { + User u = repository.getByEmail(email); + if (u == null) { + throw new UsernameNotFoundException("User " + email + " is not found"); + } + return new LoggedUser(u); + } + @Override public User getWithMeals(int id) { return ExceptionUtil.check(repository.getWithMeals(id), id); diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index b0609cae9e05..3429c5fe64e6 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -2,10 +2,14 @@ import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; +import org.hibernate.validator.constraints.Range; +import ru.javawebinar.topjava.util.UserUtil; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import java.io.Serializable; -public class UserTo { +public class UserTo implements Serializable { protected int id; @NotEmpty @@ -18,14 +22,19 @@ public class UserTo { @Size(min = 5, max = 64, message = " must between 5 and 64 characters") protected String password; + @Range(min = 100, max = 5000) + @NotNull(message = " must not be empty") + protected Integer caloriesPerDay = UserUtil.DEFAULT_CALORIES_PER_DAY; + public UserTo() { } - public UserTo(int id, String name, String email, String password) { + public UserTo(int id, String name, String email, String password, int caloriesPerDay) { this.id = id; this.name = name; this.email = email; this.password = password; + this.caloriesPerDay = caloriesPerDay; } public void setId(int id) { @@ -64,12 +73,17 @@ public void setEmail(String email) { this.email = email; } + public int getCaloriesPerDay() { + return caloriesPerDay; + } + @Override public String toString() { return "UserTo{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + + ", caloriesPerDay='" + caloriesPerDay + '\'' + '}'; } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java index 4ac14bf258d0..9d1ddda2c755 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java @@ -14,6 +14,10 @@ public static User createFromTo(UserTo newUser) { return new User(null, newUser.getName(), newUser.getEmail().toLowerCase(), newUser.getPassword(), Role.ROLE_USER); } + public static UserTo asTo(User user) { + return new UserTo(user.getId(), user.getName(), user.getEmail(), user.getPassword(), user.getCaloriesPerDay()); + } + public static User updateFromTo(User user, UserTo userTo) { user.setName(userTo.getName()); user.setEmail(userTo.getEmail().toLowerCase()); diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml index 2397b44c2934..c5c261eeccda 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -29,11 +29,19 @@ - - - - - + + +
\ No newline at end of file From 10c0c6d0d5ec2f0597733f2ce682ea9cb72746fd Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 13 Jun 2016 01:21:32 +0500 Subject: [PATCH 116/166] 9-11-spring-security-test --- pom.xml | 5 ++++ .../java/ru/javawebinar/topjava/TestUtil.java | 7 +++++ .../topjava/web/AbstractControllerTest.java | 2 ++ .../web/user/AdminRestControllerTest.java | 29 +++++++++++++++---- .../web/user/ProfileRestControllerTest.java | 16 +++++++--- .../topjava/web/user/RootControllerTest.java | 8 ++--- 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 256436335061..5ce38b236d35 100644 --- a/pom.xml +++ b/pom.xml @@ -288,6 +288,11 @@ 2.2.5 provided + + org.springframework.security + spring-security-test + ${spring-security.version} + diff --git a/src/test/java/ru/javawebinar/topjava/TestUtil.java b/src/test/java/ru/javawebinar/topjava/TestUtil.java index bc9aa67c4897..4bcbc18caca8 100644 --- a/src/test/java/ru/javawebinar/topjava/TestUtil.java +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -1,7 +1,10 @@ package ru.javawebinar.topjava; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.RequestPostProcessor; import ru.javawebinar.topjava.matcher.ModelMatcher; +import ru.javawebinar.topjava.model.User; import java.io.UnsupportedEncodingException; @@ -20,6 +23,10 @@ public static String getContent(ResultActions action) throws UnsupportedEncoding return action.andReturn().getResponse().getContentAsString(); } + public static RequestPostProcessor userHttpBasic(User user) { + return SecurityMockMvcRequestPostProcessors.httpBasic(user.getEmail(), user.getPassword()); + } + /** * Compare entities using toString */ diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index 9099f765c296..0d1ba33b4526 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -16,6 +16,7 @@ import javax.annotation.PostConstruct; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static ru.javawebinar.topjava.Profiles.DATAJPA; import static ru.javawebinar.topjava.Profiles.POSTGRES; @@ -54,6 +55,7 @@ private void postConstruct() { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .addFilter(CHARACTER_ENCODING_FILTER) + .apply(springSecurity()) .build(); } diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 71f9d0b32f2d..f36785717d4a 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -16,6 +16,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; public class AdminRestControllerTest extends AbstractControllerTest { @@ -24,7 +25,8 @@ public class AdminRestControllerTest extends AbstractControllerTest { @Test public void testGet() throws Exception { - mockMvc.perform(get(REST_URL + ADMIN_ID)) + mockMvc.perform(get(REST_URL + ADMIN_ID) + .with(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andDo(print()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) @@ -33,27 +35,42 @@ public void testGet() throws Exception { @Test public void testGetByEmail() throws Exception { - mockMvc.perform(get(REST_URL + "by?email=" + USER.getEmail())) + mockMvc.perform(get(REST_URL + "by?email=" + ADMIN.getEmail()) + .with(TestUtil.userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(MATCHER.contentMatcher(USER)); + .andExpect(MATCHER.contentMatcher(ADMIN)); } @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL + USER_ID)) + mockMvc.perform(delete(REST_URL + USER_ID) + .with(TestUtil.userHttpBasic(ADMIN))) .andDo(print()) .andExpect(status().isOk()); MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); } @Test + public void testGetUnauth() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testGetForbidden() throws Exception { + mockMvc.perform(get(REST_URL) + .with(TestUtil.userHttpBasic(USER))) + .andExpect(status().isForbidden()); + } + public void testUpdate() throws Exception { User updated = USER; updated.setName("UpdatedName"); updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); mockMvc.perform(put(REST_URL + USER_ID) .contentType(MediaType.APPLICATION_JSON) + .with(TestUtil.userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(updated))) .andExpect(status().isOk()); @@ -65,6 +82,7 @@ public void testCreate() throws Exception { TestUser expected = new TestUser("New", "new@gmail.com", "newPass", Role.ROLE_USER, Role.ROLE_ADMIN); ResultActions action = mockMvc.perform(post(REST_URL) .contentType(MediaType.APPLICATION_JSON) + .with(TestUtil.userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(expected.asUser()))).andExpect(status().isCreated()); User returned = MATCHER.fromJsonAction(action); @@ -76,7 +94,8 @@ public void testCreate() throws Exception { @Test public void testGetAll() throws Exception { - TestUtil.print(mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON)) + TestUtil.print(mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(TestUtil.userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(MATCHER.contentListMatcher(ADMIN, USER))); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index 068445707264..cd5093ec5795 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -2,7 +2,6 @@ import org.junit.Test; import org.springframework.http.MediaType; -import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.TestUtil; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; @@ -22,23 +21,32 @@ public class ProfileRestControllerTest extends AbstractControllerTest { @Test public void testGet() throws Exception { - TestUtil.print(mockMvc.perform(get(REST_URL)) + TestUtil.print(mockMvc.perform(get(REST_URL) + .with(TestUtil.userHttpBasic(USER))) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(MATCHER.contentMatcher(USER))); } + @Test + public void testGetUnauth() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isUnauthorized()); + } + @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL)) + mockMvc.perform(delete(REST_URL) + .with(TestUtil.userHttpBasic(USER))) .andExpect(status().isOk()); MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); } @Test public void testUpdate() throws Exception { - User updated = new User(LoggedUser.id(), "newName", "newEmail@ya.ru", "newPassword", Role.ROLE_USER); + User updated = new User(USER_ID, "newName", "newEmail@ya.ru", "newPassword", Role.ROLE_USER); mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(TestUtil.userHttpBasic(USER)) .content(JsonUtil.writeValue(updated))) .andDo(print()) .andExpect(status().isOk()); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java index 9570c93a6a74..0b1994828702 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java @@ -5,7 +5,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * GKislin @@ -17,9 +18,8 @@ public class RootControllerTest extends AbstractControllerTest { public void testUserList() throws Exception { mockMvc.perform(get("/users")) .andDo(print()) - .andExpect(status().isOk()) - .andExpect(view().name("userList")) - .andExpect(forwardedUrl("/WEB-INF/jsp/userList.jsp")); + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost/login")); // .andExpect(model().attribute("userList", hasSize(2))) // .andExpect(model().attribute("userList", hasItem( // allOf( From 004c41d78538aa7d8d5a4b69957981548ea0b97d Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 13 Jun 2016 01:40:20 +0500 Subject: [PATCH 117/166] fix bug in method doPost of UserServlet after change authentication --- src/main/java/ru/javawebinar/topjava/web/UserServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 4aa6c14ce7dc..1da052706bd8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -29,7 +29,7 @@ public void init(ServletConfig config) throws ServletException { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int userId = Integer.valueOf(request.getParameter("userId")); - LoggedUser.setId(userId); + LoggedUser.get().getUserTo().setId(userId); response.sendRedirect("meals"); } From 091ca040f8598af169151f0db5337aa269030b3d Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 13 Jun 2016 01:53:30 +0500 Subject: [PATCH 118/166] 10-00-fix --- src/main/webapp/WEB-INF/jsp/mealList.jsp | 5 +---- src/main/webapp/WEB-INF/jsp/userList.jsp | 3 ++- src/main/webapp/resources/js/datatablesUtil.js | 2 +- src/main/webapp/resources/js/userDatatables.js | 4 +--- .../topjava/web/user/AdminRestControllerTest.java | 12 ++++++------ 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/webapp/WEB-INF/jsp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp index 10d2a3db0da5..f56b269a5daf 100644 --- a/src/main/webapp/WEB-INF/jsp/mealList.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp @@ -79,7 +79,6 @@ -
@@ -93,11 +78,11 @@
- +
- +
@@ -133,57 +118,5 @@ - + \ No newline at end of file diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js new file mode 100644 index 000000000000..9b52fef66f20 --- /dev/null +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -0,0 +1,67 @@ +var ajaxUrl = 'ajax/profile/meals/'; +var datatableApi; + +function updateTable() { + $.ajax({ + type: "POST", + url: ajaxUrl + 'filter', + data: $('#filter').serialize(), + success: updateTableByData + }); + return false; +} + +$(function () { + datatableApi = $('#datatable').DataTable({ + "ajax": { + "url": ajaxUrl, + "dataSrc": "" + }, + "paging": false, + "info": true, + "columns": [ + { + "data": "dateTime", + "render": function (date, type, row) { + if (type == 'display') { + return date.replace('T', ' ').substr(0, 16); + } + return date; + } + }, + { + "data": "description" + }, + { + "data": "calories" + }, + { + "defaultContent": "", + "orderable": false, + "render": renderEditBtn + }, + { + "defaultContent": "", + "orderable": false, + "render": renderDeleteBtn + + } + ], + "order": [ + [ + 0, + "desc" + ] + ], + "createdRow": function (row, data, dataIndex) { + $(row).addClass(data.exceed ? 'exceeded' : 'normal'); + }, + "initComplete": function () { + $('#filter').submit(function () { + updateTable(); + return false; + }); + makeEditable(); + } + }); +}); From 435782f0445c8c154a4415c6cc17c6ff5d7590e7 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 13 Jun 2016 01:54:59 +0500 Subject: [PATCH 120/166] 10-02-HW9-test --- .../web/meal/UserMealControllerTest.java | 34 --------------- .../web/meal/UserMealRestControllerTest.java | 42 +++++++++++++------ .../topjava/web/user/RootControllerTest.java | 26 ++++++++---- 3 files changed, 48 insertions(+), 54 deletions(-) delete mode 100644 src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java deleted file mode 100644 index 3c0e72aabe55..000000000000 --- a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealControllerTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.javawebinar.topjava.web.meal; - -import org.junit.Test; -import ru.javawebinar.topjava.web.AbstractControllerTest; - -import static org.hamcrest.Matchers.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static ru.javawebinar.topjava.MealTestData.MEAL1; -import static ru.javawebinar.topjava.MealTestData.MEAL1_ID; - -/** - * GKislin - * 10.04.2015. - */ -public class UserMealControllerTest extends AbstractControllerTest { - - @Test - public void testMealList() throws Exception { - mockMvc.perform(get("/meals")) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(view().name("mealList")) - .andExpect(forwardedUrl("/WEB-INF/jsp/mealList.jsp")) - .andExpect(model().attribute("mealList", hasSize(6))) - .andExpect(model().attribute("mealList", hasItem( - allOf( - hasProperty("id", is(MEAL1_ID)), - hasProperty("description", is(MEAL1.getDescription())) - ) - ))); - } -} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java index 3d525fd0d662..ec82ee9655f6 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/UserMealRestControllerTest.java @@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; -import ru.javawebinar.topjava.LoggedUser; import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.service.UserMealService; import ru.javawebinar.topjava.util.UserMealsUtil; @@ -19,6 +18,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.MealTestData.MATCHER; +import static ru.javawebinar.topjava.TestUtil.userHttpBasic; +import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; public class UserMealRestControllerTest extends AbstractControllerTest { @@ -30,16 +32,24 @@ public class UserMealRestControllerTest extends AbstractControllerTest { @Test public void testGet() throws Exception { - mockMvc.perform(get(REST_URL + MEAL1_ID)) + mockMvc.perform(get(REST_URL + ADMIN_MEAL_ID) + .with(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andDo(print()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(MATCHER.contentMatcher(MEAL1)); + .andExpect(MATCHER.contentMatcher(ADMIN_MEAL)); + } + + @Test + public void testGetUnauth() throws Exception { + mockMvc.perform(get(REST_URL + MEAL1_ID)) + .andExpect(status().isUnauthorized()); } @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL + MEAL1_ID)) + mockMvc.perform(delete(REST_URL + MEAL1_ID) + .with(userHttpBasic(USER))) .andExpect(status().isOk()); MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(START_SEQ)); } @@ -49,7 +59,8 @@ public void testUpdate() throws Exception { UserMeal updated = getUpdated(); mockMvc.perform(put(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON) - .content(JsonUtil.writeValue(updated))) + .content(JsonUtil.writeValue(updated)) + .with(userHttpBasic(USER))) .andExpect(status().isOk()); assertEquals(updated, service.get(MEAL1_ID, START_SEQ)); @@ -60,27 +71,30 @@ public void testCreate() throws Exception { UserMeal created = getCreated(); ResultActions action = mockMvc.perform(post(REST_URL) .contentType(MediaType.APPLICATION_JSON) - .content(JsonUtil.writeValue(created))); + .content(JsonUtil.writeValue(created)) + .with(userHttpBasic(ADMIN))); UserMeal returned = MATCHER.fromJsonAction(action); created.setId(returned.getId()); MATCHER.assertEquals(created, returned); - MATCHER.assertCollectionEquals(Arrays.asList(created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), service.getAll(START_SEQ)); + MATCHER.assertCollectionEquals(Arrays.asList(ADMIN_MEAL2, created, ADMIN_MEAL), service.getAll(ADMIN_ID)); } @Test public void testGetAll() throws Exception { - mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON)) + mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(MATCHER_WITH_EXCEED.contentListMatcher(UserMealsUtil.getWithExceeded(USER_MEALS, LoggedUser.getCaloriesPerDay()))); + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher(UserMealsUtil.getWithExceeded(USER_MEALS, USER.getCaloriesPerDay()))); } @Test public void testGetBetween() throws Exception { - mockMvc.perform(get(REST_URL + "between?startDateTime=2015-05-30T07:00&endDateTime=2015-05-31T11:00:00")) + mockMvc.perform(get(REST_URL + "between?startDateTime=2015-05-30T07:00&endDateTime=2015-05-31T11:00:00") + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( @@ -90,7 +104,8 @@ public void testGetBetween() throws Exception { @Test public void testFilter() throws Exception { - mockMvc.perform(get(REST_URL + "filter?startDate=2015-05-30&startTime=07:00&endDate=2015-05-31&endTime=11:00")) + mockMvc.perform(get(REST_URL + "filter?startDate=2015-05-30&startTime=07:00&endDate=2015-05-31&endTime=11:00") + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( @@ -100,10 +115,11 @@ public void testFilter() throws Exception { @Test public void testFilterAll() throws Exception { - mockMvc.perform(get(REST_URL + "filter?startDate=&endTime=")) + mockMvc.perform(get(REST_URL + "filter?startDate=&endTime=") + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( - UserMealsUtil.getWithExceeded(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), LoggedUser.getCaloriesPerDay()))); + UserMealsUtil.getWithExceeded(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), USER.getCaloriesPerDay()))); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java index 0b1994828702..9ca6fb381390 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java @@ -3,10 +3,12 @@ import org.junit.Test; import ru.javawebinar.topjava.web.AbstractControllerTest; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.UserTestData.USER; /** * GKislin @@ -20,12 +22,22 @@ public void testUserList() throws Exception { .andDo(print()) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost/login")); -// .andExpect(model().attribute("userList", hasSize(2))) -// .andExpect(model().attribute("userList", hasItem( -// allOf( -// hasProperty("id", is(START_SEQ)), -// hasProperty("name", is(USER.getName())) -// ) -// ))); } + + @Test + public void testMealList() throws Exception { + mockMvc.perform(get("/meals")) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost/login")); + } + + @Test + public void testRoot() throws Exception { + mockMvc.perform(formLogin("/spring_security_check").user(USER.getEmail()).password(USER.getPassword())) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/meals")); + } + } \ No newline at end of file From 24c10e0f182261adc3411a6e342164d25159e9f6 Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 13 Jun 2016 01:56:07 +0500 Subject: [PATCH 121/166] 10-03-HW9-datetimepicker --- .../ru/javawebinar/topjava/to/UserMealTo.java | 2 +- src/main/webapp/WEB-INF/jsp/mealList.jsp | 11 +++--- .../webapp/resources/js/datatablesUtil.js | 4 +- .../webapp/resources/js/mealDatatables.js | 38 +++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/to/UserMealTo.java b/src/main/java/ru/javawebinar/topjava/to/UserMealTo.java index e14338b091f7..a4470f8cb8ea 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserMealTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserMealTo.java @@ -15,7 +15,7 @@ public class UserMealTo { private int id; @NotNull - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime dateTime; @NotEmpty diff --git a/src/main/webapp/WEB-INF/jsp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp index d5ecf10e8a5a..3524873b27e2 100644 --- a/src/main/webapp/WEB-INF/jsp/mealList.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp @@ -20,26 +20,26 @@
- +
- +
- +
- +
@@ -81,8 +81,7 @@
- +
diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 4e3b3dc7c767..158e3fb56780 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -22,7 +22,9 @@ function add() { function updateRow(id) { $.get(ajaxUrl + id, function (data) { $.each(data, function (key, value) { - form.find("input[name='" + key + "']").val(value); + form.find("input[name='" + key + "']").val( + key === "dateTime" ? value.replace('T', ' ').substr(0, 16) : value + ); }); $('#editRow').modal(); }); diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index 9b52fef66f20..9346e141f3f7 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -61,6 +61,44 @@ $(function () { updateTable(); return false; }); + + var startDate = $('#startDate'); + var endDate = $('#endDate'); + + startDate.datetimepicker({ + timepicker: false, + format: 'Y-m-d', + lang: 'ru', + formatDate: 'Y-m-d', + onShow: function (ct) { + this.setOptions({ + maxDate: endDate.val() ? endDate.val() : false + }) + } + }); + endDate.datetimepicker({ + timepicker: false, + format: 'Y-m-d', + lang: 'ru', + formatDate: 'Y-m-d', + onShow: function (ct) { + this.setOptions({ + minDate: startDate.val() ? startDate.val() : false + }) + } + }); + + $('.time-picker').datetimepicker({ + datepicker: false, + format: 'H:i', + lang: 'ru' + }); + + $('#dateTime').datetimepicker({ + format: 'Y-m-d H:i', + lang: 'ru' + }); + makeEditable(); } }); From 5c5a68066607384eb163ff9a1345581d05b2a5aa Mon Sep 17 00:00:00 2001 From: ilnur serbaev Date: Mon, 13 Jun 2016 02:07:32 +0500 Subject: [PATCH 122/166] 10-04-secure-tag-annotation --- pom.xml | 5 +++++ .../ru/javawebinar/topjava/web/RootController.java | 3 +++ src/main/resources/spring/spring-mvc.xml | 7 ++++++- .../webapp/WEB-INF/jsp/fragments/bodyHeader.jsp | 14 +++++++++----- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 5ce38b236d35..3506b12df7cb 100644 --- a/pom.xml +++ b/pom.xml @@ -162,6 +162,11 @@ spring-security-config ${spring-security.version} + + org.springframework.security + spring-security-taglibs + ${spring-security.version} + diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index bfa681ba4d09..8229ee5da585 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,6 +19,8 @@ public String root() { return "redirect:meals"; } +// @Secured("ROLE_ADMIN") + @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "/users", method = RequestMethod.GET) public String userList() { return "userList"; diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 04b6ad321dc3..d341467a6acd 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -2,14 +2,19 @@ xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns="http://www.springframework.org/schema/beans" + xmlns:security="http://www.springframework.org/schema/security" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 3035f2a6a6df..170239d88fd8 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,17 +1,21 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> +<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>