From 4d8d2e2ca5e5945494e12a408c3c4934e44c8c77 Mon Sep 17 00:00:00 2001 From: Grigory Kislin Date: Tue, 9 Aug 2016 21:32:21 +0300 Subject: [PATCH 001/111] Add description --- README.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ cv.md | 76 +++++++++++++++++++++++++++++ description.md | 67 ++++++++++++++++++++++++++ 3 files changed, 269 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..0c9156d3756d --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +Java Enterprise Online Project +=============================== +Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: +Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. + + Когда вы слышите что-то, вы забываете это. + Когда вы видите что-то, вы запоминаете это. + Но только когда вы начинаете делать это, + вы начинаете понимать это + + Старинная китайская поговорка + +## Описание и план проекта +### Демо разрабатываемого приложения +### Требования к участникам, Wiki +### Составление резюме, подготовка к интервью, поиск работы + +Вводное занятие +=============== +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Системы управления версиями. Git. +- **Wiki по ведению проекта в Git** +- Система управления версиями. VCS/DVSC. +- Ресурсы: + - Интерактивная Git обучалка + - Книга Git + - Working with remote repositories + - Видео по обучению Git + - 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 + - Выставить кодировку UTF-8 в консоли + - Поставить кодировку UTF-8 + - Поменять фонт по умолчанию (DejaVu) +- По ходу видео сделать 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.getFilteredWithExceeded: +- должны возвращаться только записи между 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 +- A Guide to Java 8 +- Шпаргалка Java Stream API +- Алексея Владыкин: Элементы функционального программирования в Java +- stream.map vs forEach + +``` +Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) +``` +### Полезные ресурсы +> ВНИМАНИЕ: основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! + +Все остальное - опционально. + +#### HTML, JavaScript, CSS +- Введение в программирование на JavaScript +- Основы работы с HTML/CSS/JavaScript +- JavaScript - Основы +- Bootstrap 3 - Основы +- jQuery для начинающих + +#### Java (базовые вещи) +- Интуит. Программирование на Java +- Основы Java garbage collection +- Размер Java объектов +- Блог о памяти, сборщике мусора, многопоточности и производительности в JAVA +- Введение в Java Reflection API +- Структуры данных в картинках +- Обзор java.util.concurrent.* +- Синхронизация потоков +- String literal pool +- Маленькие хитрости Java +- Как создать Servlet? Полное руководство. +- A Guide to Java 8 + +#### JDBC, SQL +- Уроки по JDBC +- Learn SQL +- Try SQL +- Курс "Введение в базы данных" + +#### Разное +- Эффективная работа с кодом в IntelliJ IDEA +- Quizful- тесты онлайн +- Введение в Linux + +#### Книги +- Джошуа Блох: Java. Эффективное программирование. Второе издание +- Гамма, Хелм, Джонсон: Приемы объектно-ориентированного проектирования. Паттерны проектирования +- Редмонд Э.: Семь баз данных за семь недель. Введение в современные базы данных и идеологию NoSQL +- Brian Goetz: Java Concurrency in Practice +- G.L. McDowell: Cracking the Coding Interview diff --git a/cv.md b/cv.md new file mode 100644 index 000000000000..42543dfdabe6 --- /dev/null +++ b/cv.md @@ -0,0 +1,76 @@ +## Составление резюме, подготовка к интервью, поиск работы + +![cv](https://cloud.githubusercontent.com/assets/13649199/10877471/93ea86b8-8157-11e5-9bfa-95e3fba75c58.jpg) + +- Научиться программировать сложнее, чем кажется + +### Составление резюме: +- Выбрать шаблон для резюме +- Идеальное резюме Junior’а +- Как продать свое резюме в 2 раза дороже +- Как правильно составить резюме +- Резюме программистов. Часть 1 (плохие) +- Резюме программистов. Часть 2 (хорошие) +- Как составить резюме на английском +- ОФОРМЛЕНИЕ IT-РЕЗЮМЕ для USA + +### Наши истории (делимся опытом и успехом) + +### Тесты/задачи онлайн: +- Codility lesson tests +- Quizful- тесты онлайн +- Multiple-Choice Questions in Java +- LeetCode Online Judge +- Sphere online judge +- Codility programmers lessons +- Hackerrank practice coding + +### Интервью: +- Михаил Портнов. Собеседование на работу: как продать себя грамотно +- Михаил Портнов. Какие вопросы мы задаем на собеседовании? +- Канал: Резюме, поиск работы, интервью +- Яков Файн: Как стать профессиональным Java разработчиком +- Список вопросов с ответами для собеседования по Java +- Сборка по вопросам на интервью +- Сборка вопросов-ответов от JavaStudy +- Тест на знание SQL +- Вопросы на собеседовании Java Junior Developer +- Java вопросы с собеседований на Android +- Сборка вопросов от 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. + - отлично будет, если вы его еще как то сами доработаете + + +Делайте упор не на обучение, а на *участие в проекте*. Выполнение домашних заданий - это полноценное участие с написанием функционала по всем этим технологиям. На собеседовании меньше смотрят на то, что вы заканчивали, больше - на ваш опыт и то что вы знаете. + +- Ссылку на организацию можно поставить: http://javaops.ru (или в linkedin: https://www.linkedin.com/company/java-online-projects) + +### В течении проекта заполните форму соискателя "Разработчик Java" +Я ее рассылаю всем знакомым HR, уже есть пинги и трудоустройство. + +Ближе к концу курса я вышлю приглашение в нашу общую группу slack: общение на все темы IT, обсуждение вступительных заданий, вакансии, цены на рынке труда, события, помощь, интересные видео по теме и пр. + +### Основные сайты поиска работы: +- Яндекс агрегатор +- HH +- LinkedIn +- djinni.co (более актуально для Украины) diff --git a/description.md b/description.md new file mode 100644 index 000000000000..cbb102fa72aa --- /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 (добавление в проект защиты от межсайтовой подделки запроса) +- form-login. Spring Security Taglib +- Handler interceptor +- Spring Exception Handling +- Деплой в Heroku From 9be04db8afa78324d9152526f623fd43cb2335de Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 29 Aug 2016 17:48:27 +0500 Subject: [PATCH 002/111] Prepare 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..aec9beb946cf --- /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 { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + public UserMeal(LocalDateTime dateTime, String description, int calories) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java new file mode 100644 index 000000000000..f4f880e4e67a --- /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 { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private 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..8c5f1dc60142 --- /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) + ); + getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +// .toLocalDate(); +// .toLocalTime(); + } + + public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO return filtered list with correctly exceeded field + return null; + } +} From aab6658f8b20ed2c9a23a48d24d9f47220b888b0 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 14 Sep 2016 12:05:06 +0500 Subject: [PATCH 003/111] HW0 --- .../model/{UserMeal.java => Meal.java} | 14 ++++- .../topjava/model/MealWithExceed.java | 34 ++++++++++ .../topjava/model/UserMealWithExceed.java | 24 ------- .../javawebinar/topjava/util/MealsUtil.java | 63 +++++++++++++++++++ .../topjava/util/UserMealsUtil.java | 35 ----------- 5 files changed, 109 insertions(+), 61 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{UserMeal.java => Meal.java} (63%) create mode 100644 src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java delete mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java similarity index 63% rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java rename to src/main/java/ru/javawebinar/topjava/model/Meal.java index aec9beb946cf..006d5cac8caf 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,19 +1,21 @@ package ru.javawebinar.topjava.model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; /** * GKislin * 11.01.2015. */ -public class UserMeal { +public class Meal { private final LocalDateTime dateTime; private final String description; private final int calories; - public UserMeal(LocalDateTime dateTime, String description, int calories) { + public Meal(LocalDateTime dateTime, String description, int calories) { this.dateTime = dateTime; this.description = description; this.calories = calories; @@ -30,4 +32,12 @@ public String getDescription() { public int getCalories() { return calories; } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java new file mode 100644 index 000000000000..d14128b1f61f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -0,0 +1,34 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +/** + * GKislin + * 11.01.2015. + */ +public class MealWithExceed { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private final boolean exceed; + + public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.dateTime = dateTime; + this.description = description; + 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/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java deleted file mode 100644 index f4f880e4e67a..000000000000 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.javawebinar.topjava.model; - -import java.time.LocalDateTime; - -/** - * GKislin - * 11.01.2015. - */ -public class UserMealWithExceed { - private final LocalDateTime dateTime; - - private final String description; - - private final int calories; - - private 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/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java new file mode 100644 index 000000000000..9f4a325847da --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,63 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealWithExceed; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.*; +import java.util.stream.Collectors; + +/** + * GKislin + * 31.05.2015. + */ +public class MealsUtil { + public static void main(String[] args) { + List meals = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) + ); + List filteredMealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + filteredMealsWithExceeded.forEach(System.out::println); + + System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) + ); + + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } + + public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + + final Map caloriesSumByDate = new HashMap<>(); + meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); + + final List mealExceeded = new ArrayList<>(); + meals.forEach(meal -> { + if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { + mealExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + }); + return mealExceeded; + } + + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java deleted file mode 100644 index 8c5f1dc60142..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -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) - ); - getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); -// .toLocalDate(); -// .toLocalTime(); - } - - public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with correctly exceeded field - return null; - } -} From 3c1504cd8025f36f32d0bda7548b33ef8c511b0a Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 14 Sep 2016 20:13:26 +0500 Subject: [PATCH 004/111] HW01 --- config/setenv.bat | 4 ++++ src/main/resources/logback.xml | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 config/setenv.bat create mode 100644 src/main/resources/logback.xml 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/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..8ee5f21b023e --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,33 @@ + + + + + true + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From 11f9a8c85e0c75d3ae49d438d6bad35e90a612fb Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 15 Sep 2016 00:34:24 +0500 Subject: [PATCH 005/111] HW01 --- pom.xml | 37 ++++++++++++++++++- .../javawebinar/topjava/web/UserServlet.java | 26 +++++++++++++ src/main/webapp/WEB-INF/web.xml | 18 +++++++++ src/main/webapp/index.html | 14 +++++++ src/main/webapp/userList.jsp | 10 +++++ 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java 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..204e2d01084c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT @@ -15,11 +15,15 @@ 1.8 UTF-8 UTF-8 + + + 1.1.7 + 1.7.21 topjava - install + package org.apache.maven.plugins @@ -34,6 +38,35 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + + + + + javax.servlet + javax.servlet-api + 3.1.0 + 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..806169a585a8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * 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); + response.sendRedirect("userList.jsp"); + } +} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..f346a5efd3c0 --- /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..c65c87566193 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,14 @@ + + + + + Java Enterprise (Topjava) + + +

Проект "Java Enterprise (Topjava)"

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

Home

+

User list

+ + From f8bb74226acec156d4c4dde54c35b0bf9170f7fb Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 20 Sep 2016 15:38:00 +0500 Subject: [PATCH 006/111] HW02 --- pom.xml | 21 ++++ .../javawebinar/topjava/AuthorizedUser.java | 18 ++++ .../ru/javawebinar/topjava/SpringMain.java | 24 +++++ .../javawebinar/topjava/model/BaseEntity.java | 28 ++++++ .../ru/javawebinar/topjava/model/Meal.java | 29 ++++++ .../topjava/model/MealWithExceed.java | 30 +++++- .../topjava/model/NamedEntity.java | 31 ++++++ .../ru/javawebinar/topjava/model/Role.java | 10 ++ .../ru/javawebinar/topjava/model/User.java | 98 +++++++++++++++++++ .../topjava/repository/MealRepository.java | 19 ++++ .../topjava/repository/UserRepository.java | 24 +++++ .../mock/InMemoryMealRepositoryImpl.java | 48 +++++++++ .../mock/MockUserRepositoryImpl.java | 49 ++++++++++ .../topjava/service/MealService.java | 8 ++ .../topjava/service/MealServiceImpl.java | 13 +++ .../topjava/service/UserService.java | 26 +++++ .../topjava/service/UserServiceImpl.java | 45 +++++++++ .../javawebinar/topjava/util/MealsUtil.java | 31 +++--- .../ru/javawebinar/topjava/util/TimeUtil.java | 8 ++ .../topjava/util/exception/ExceptionUtil.java | 25 +++++ .../util/exception/NotFoundException.java | 11 +++ .../javawebinar/topjava/web/MealServlet.java | 76 ++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 6 +- .../topjava/web/meal/MealRestController.java | 12 +++ .../web/user/AbstractUserController.java | 51 ++++++++++ .../topjava/web/user/AdminRestController.java | 38 +++++++ .../web/user/ProfileRestController.java | 25 +++++ src/main/resources/spring/spring-app.xml | 23 +++++ src/main/webapp/WEB-INF/tld/functions.tld | 16 +++ src/main/webapp/WEB-INF/web.xml | 10 ++ src/main/webapp/index.html | 1 + src/main/webapp/mealEdit.jsp | 51 ++++++++++ src/main/webapp/mealList.jsp | 51 ++++++++++ 33 files changed, 937 insertions(+), 19 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/AuthorizedUser.java create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.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/MealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.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/MealServlet.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java create mode 100644 src/main/resources/spring/spring-app.xml create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/mealEdit.jsp create mode 100644 src/main/webapp/mealList.jsp diff --git a/pom.xml b/pom.xml index 204e2d01084c..d8a9b835cc97 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 4.3.2.RELEASE + 1.1.7 1.7.21 @@ -60,6 +62,19 @@ runtime + + + org.springframework + spring-context + ${spring.version} + + + commons-logging + commons-logging + + + + javax.servlet @@ -67,6 +82,12 @@ 3.1.0 provided + + + javax.servlet + jstl + 1.2 + diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java new file mode 100644 index 000000000000..7d9365626a55 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -0,0 +1,18 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.util.MealsUtil; + +/** + * GKislin + * 06.03.2015. + */ +public class AuthorizedUser { + + public static int id() { + return 1; + } + + public static int getCaloriesPerDay() { + return MealsUtil.DEFAULT_CALORIES_PER_DAY; + } +} 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..92a53add1073 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Arrays; + +/** + * User: gkislin + * Date: 22.08.2014 + */ +public class SpringMain { + public static void main(String[] args) { + // java 7 Automatic resource management + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN)); + } + } +} 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/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 006d5cac8caf..d24341443257 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -9,6 +9,8 @@ * 11.01.2015. */ public class Meal { + private Integer id; + private final LocalDateTime dateTime; private final String description; @@ -16,11 +18,24 @@ public class Meal { private final int calories; public Meal(LocalDateTime dateTime, String description, int calories) { + this(null, dateTime, description, calories); + } + + public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -40,4 +55,18 @@ public LocalDate getDate() { public LocalTime getTime() { return dateTime.toLocalTime(); } + + public boolean isNew() { + return id == null; + } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index d14128b1f61f..204c177e46dc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -7,6 +7,8 @@ * 11.01.2015. */ public class MealWithExceed { + private final Integer id; + private final LocalDateTime dateTime; private final String description; @@ -15,17 +17,39 @@ public class MealWithExceed { private final boolean exceed; - public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + public MealWithExceed(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; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExceed() { + return exceed; + } + @Override public String toString() { - return "UserMealWithExceed{" + - "dateTime=" + dateTime + + return "MealWithExceed{" + + "id=" + id + + ", dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + ", exceed=" + exceed + 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..c5edf2fb997b --- /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.MealsUtil; + +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 = MealsUtil.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, MealsUtil.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/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java new file mode 100644 index 000000000000..f80eec996562 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; + +import java.util.Collection; + +/** + * GKislin + * 06.03.2015. + */ +public interface MealRepository { + Meal save(Meal Meal); + + void delete(int id); + + Meal get(int id); + + Collection getAll(); +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java new file mode 100644 index 000000000000..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/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java new file mode 100644 index 000000000000..f6ead5176644 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -0,0 +1,48 @@ +package ru.javawebinar.topjava.repository.mock; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * GKislin + * 15.09.2015. + */ +public class InMemoryMealRepositoryImpl implements MealRepository { + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + { + MealsUtil.MEALS.forEach(this::save); + } + + @Override + public Meal save(Meal meal) { + if (meal.isNew()) { + meal.setId(counter.incrementAndGet()); + } + repository.put(meal.getId(), meal); + return meal; + } + + @Override + public void delete(int id) { + repository.remove(id); + } + + @Override + public Meal get(int id) { + return repository.get(id); + } + + @Override + public Collection getAll() { + return repository.values(); + } +} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java new file mode 100644 index 000000000000..932f7c44e151 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -0,0 +1,49 @@ +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 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/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 000000000000..0a0ba9b9b78f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.service; + +/** + * GKislin + * 15.06.2015. + */ +public interface MealService { +} diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java new file mode 100644 index 000000000000..6e00e324ef51 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -0,0 +1,13 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.repository.MealRepository; + +/** + * GKislin + * 06.03.2015. + */ +public class MealServiceImpl implements MealService { + + private MealRepository 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..77782de1784e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -0,0 +1,45 @@ +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; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +/** + * GKislin + * 06.03.2015. + */ +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserRepository repository; + + public User save(User user) { + return repository.save(user); + } + + public void delete(int id) { + ExceptionUtil.checkNotFoundWithId(repository.delete(id), id); + } + + public User get(int id) throws NotFoundException { + return ExceptionUtil.checkNotFoundWithId(repository.get(id), id); + } + + public User getByEmail(String email) throws NotFoundException { + return ExceptionUtil.checkNotFound(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/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 9f4a325847da..c10ed7394c09 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -15,22 +15,29 @@ * 31.05.2015. */ public class MealsUtil { + public static final List MEALS = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new Meal(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 meals = Arrays.asList( - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) - ); - List filteredMealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + List filteredMealsWithExceeded = getFilteredWithExceeded(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY); filteredMealsWithExceeded.forEach(System.out::println); - System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededByCycle(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY)); + } + + public static List getWithExceeded(Collection meals, int caloriesPerDay) { + return getFilteredWithExceeded(meals, LocalTime.MIN, LocalTime.MAX, caloriesPerDay); } - public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + public static List getFilteredWithExceeded(Collection meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -58,6 +65,6 @@ public static List getFilteredWithExceededByCycle(List mea } public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { - return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } } \ No newline at end of file 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/exception/ExceptionUtil.java b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java new file mode 100644 index 000000000000..254a7dba2b00 --- /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 checkNotFoundWithId(boolean found, int id) { + checkNotFound(found, "id=" + id); + } + + public static T checkNotFoundWithId(T object, int id) { + return checkNotFound(object, "id=" + id); + } + + public static T checkNotFound(T object, String msg) { + checkNotFound(object != null, msg); + return object; + } + + public static void checkNotFound(boolean found, String msg) { + if (!found) throw new NotFoundException("Not found entity with " + msg); + } +} 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/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java new file mode 100644 index 000000000000..c2c2d172549f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,76 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.MealsUtil; + +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.LocalDateTime; +import java.util.Objects; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class MealServlet extends HttpServlet { + private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); + + private MealRepository repository; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + repository = new InMemoryMealRepositoryImpl(); + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String id = request.getParameter("id"); + + Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); + + LOG.info(meal.isNew() ? "Create {}" : "Update {}", meal); + repository.save(meal); + response.sendRedirect("meals"); + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String action = request.getParameter("action"); + + if (action == null) { + LOG.info("getAll"); + request.setAttribute("mealList", + MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/mealList.jsp").forward(request, response); + + } else if ("delete".equals(action)) { + int id = getId(request); + LOG.info("Delete {}", id); + repository.delete(id); + response.sendRedirect("meals"); + + } else if ("create".equals(action) || "update".equals(action)) { + final Meal meal = action.equals("create") ? + new Meal(LocalDateTime.now().withNano(0).withSecond(0), "", 1000) : + repository.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("mealEdit.jsp").forward(request, response); + } + } + + 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/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 806169a585a8..7cdd767ffd0f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -18,9 +18,7 @@ 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); - response.sendRedirect("userList.jsp"); + LOG.debug("forward to userList"); + request.getRequestDispatcher("/userList.jsp").forward(request, response); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java new file mode 100644 index 000000000000..a9ab9e306f8a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava.web.meal; + +import ru.javawebinar.topjava.service.MealService; + +/** + * GKislin + * 06.03.2015. + */ +public class MealRestController { + private MealService 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..e2b847678af2 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,51 @@ +package ru.javawebinar.topjava.web.user; + +import org.springframework.beans.factory.annotation.Autowired; +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()); + + @Autowired + private UserService service; + + public List getAll() { + LOG.info("getAll"); + return service.getAll(); + } + + public User get(int id) { + LOG.info("get " + id); + return service.get(id); + } + + public User create(User user) { + 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..d6b8bb420803 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.web.user; + +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +/** + * GKislin + * 06.03.2015. + */ +@Controller +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..443db9c53263 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.web.user; + +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.AuthorizedUser; +import ru.javawebinar.topjava.model.User; + +/** + * GKislin + * 06.03.2015. + */ +@Controller +public class ProfileRestController extends AbstractUserController { + + public User get() { + return super.get(AuthorizedUser.id()); + } + + public void delete() { + super.delete(AuthorizedUser.id()); + } + + public void update(User user) { + super.update(user, AuthorizedUser.id()); + } +} \ No newline at end of file diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..96e40e902b66 --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld new file mode 100644 index 000000000000..7921d2914838 --- /dev/null +++ b/src/main/webapp/WEB-INF/tld/functions.tld @@ -0,0 +1,16 @@ + + + + 1.0 + functions + http://topjava.javawebinar.ru/functions + + + formatDateTime + ru.javawebinar.topjava.util.TimeUtil + java.lang.String toString(java.time.LocalDateTime) + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index f346a5efd3c0..686b79f85e6f 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 c65c87566193..892cf5638cb1 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -9,6 +9,7 @@

Проект "User List +
  • Meal List
  • diff --git a/src/main/webapp/mealEdit.jsp b/src/main/webapp/mealEdit.jsp new file mode 100644 index 000000000000..bef9569b1716 --- /dev/null +++ b/src/main/webapp/mealEdit.jsp @@ -0,0 +1,51 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
    +

    Home

    +

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

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

    Home

    +

    Meal list

    + Add Meal +
    + + + + + + + + + + + + + + + + + + + + +
    DateDescriptionCalories
    + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}UpdateDelete
    +
    + + \ No newline at end of file From 8a2fe85f701132eadf08686b9d97839a3f2ce0e9 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 13:29:03 +0500 Subject: [PATCH 007/111] 3 0 fix --- config/setenv.sh | 3 +++ .../ru/javawebinar/topjava/service/UserServiceImpl.java | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 config/setenv.sh diff --git a/config/setenv.sh b/config/setenv.sh new file mode 100644 index 000000000000..7f5c0ebd9b90 --- /dev/null +++ b/config/setenv.sh @@ -0,0 +1,3 @@ +# run tomcat with JMX ability as admin +# for remote connection add -Djava.rmi.server.hostname=TomcatServer_IP +export CATALINA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false" \ 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 77782de1784e..d1f29b2531c8 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -19,26 +19,32 @@ public class UserServiceImpl implements UserService { @Autowired private UserRepository repository; + @Override public User save(User user) { return repository.save(user); } + @Override public void delete(int id) { ExceptionUtil.checkNotFoundWithId(repository.delete(id), id); } + @Override public User get(int id) throws NotFoundException { return ExceptionUtil.checkNotFoundWithId(repository.get(id), id); } + @Override public User getByEmail(String email) throws NotFoundException { return ExceptionUtil.checkNotFound(repository.getByEmail(email), "email=" + email); } + @Override public List getAll() { return repository.getAll(); } + @Override public void update(User user) { repository.save(user); } From c2fa9d183599e1043c3d770ad98e24c660fa8d97 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 13:30:03 +0500 Subject: [PATCH 008/111] 3 01 HW2 repository --- .../topjava/repository/MealRepository.java | 16 +++-- .../mock/InMemoryUserRepositoryImpl.java | 64 +++++++++++++++++++ .../mock/MockUserRepositoryImpl.java | 49 -------------- .../javawebinar/topjava/web/MealServlet.java | 9 +-- 4 files changed, 81 insertions(+), 57 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/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index f80eec996562..adb42da23a5d 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -2,6 +2,7 @@ import ru.javawebinar.topjava.model.Meal; +import java.time.LocalDateTime; import java.util.Collection; /** @@ -9,11 +10,18 @@ * 06.03.2015. */ public interface MealRepository { - Meal save(Meal Meal); + // null if updated meal do not belong to userId + Meal save(Meal Meal, int userId); - void delete(int id); + // false if meal do not belong to userId + boolean delete(int id, int userId); - Meal get(int id); + // null if meal do not belong to userId + Meal 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/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java new file mode 100644 index 000000000000..74b4cb84326f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -0,0 +1,64 @@ +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); + + private 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 932f7c44e151..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.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Repository; -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/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index c2c2d172549f..1d04786e6577 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.repository.MealRepository; @@ -41,7 +42,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Integer.valueOf(request.getParameter("calories"))); LOG.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal); + repository.save(meal, AuthorizedUser.id()); response.sendRedirect("meals"); } @@ -51,19 +52,19 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t if (action == null) { LOG.info("getAll"); request.setAttribute("mealList", - MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + MealsUtil.getWithExceeded(repository.getAll(AuthorizedUser.id()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); request.getRequestDispatcher("/mealList.jsp").forward(request, response); } else if ("delete".equals(action)) { int id = getId(request); LOG.info("Delete {}", id); - repository.delete(id); + repository.delete(id, AuthorizedUser.id()); response.sendRedirect("meals"); } else if ("create".equals(action) || "update".equals(action)) { final Meal meal = action.equals("create") ? new Meal(LocalDateTime.now().withNano(0).withSecond(0), "", 1000) : - repository.get(getId(request)); + repository.get(getId(request), AuthorizedUser.id()); request.setAttribute("meal", meal); request.getRequestDispatcher("mealEdit.jsp").forward(request, response); } From ca8e780ebb652a007a19a5e4a85d9c993b328921 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 13:30:25 +0500 Subject: [PATCH 009/111] 3 02 HW2 meal layers --- .../ru/javawebinar/topjava/SpringMain.java | 14 +++++ .../ru/javawebinar/topjava/model/Meal.java | 16 +---- .../mock/InMemoryMealRepositoryImpl.java | 62 +++++++++++++++---- .../topjava/service/MealService.java | 23 +++++++ .../topjava/service/MealServiceImpl.java | 38 ++++++++++++ .../topjava/{model => to}/MealWithExceed.java | 2 +- .../javawebinar/topjava/util/MealsUtil.java | 2 +- .../ru/javawebinar/topjava/util/TimeUtil.java | 13 ++-- .../topjava/web/meal/MealRestController.java | 60 ++++++++++++++++++ src/main/webapp/mealList.jsp | 2 +- 10 files changed, 197 insertions(+), 35 deletions(-) rename src/main/java/ru/javawebinar/topjava/{model => to}/MealWithExceed.java (96%) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 92a53add1073..68e894921a64 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.MealWithExceed; +import ru.javawebinar.topjava.web.meal.MealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; import java.util.Arrays; +import java.util.List; /** * User: gkislin @@ -19,6 +25,14 @@ public static void main(String[] args) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN)); + System.out.println(); + + MealRestController mealController = appCtx.getBean(MealRestController.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/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index d24341443257..c8589c09d45f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -8,7 +8,7 @@ * GKislin * 11.01.2015. */ -public class Meal { +public class Meal extends BaseEntity { private Integer id; private final LocalDateTime dateTime; @@ -22,20 +22,12 @@ public Meal(LocalDateTime dateTime, String description, int calories) { } public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { - this.id = id; + super(id); this.dateTime = dateTime; this.description = description; this.calories = calories; } - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - public LocalDateTime getDateTime() { return dateTime; } @@ -56,10 +48,6 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } - public boolean isNew() { - return id == null; - } - @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index f6ead5176644..f43b78dd372e 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -1,48 +1,84 @@ package ru.javawebinar.topjava.repository.mock; +import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.TimeUtil; -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 InMemoryMealRepositoryImpl implements MealRepository { - private Map repository = new ConcurrentHashMap<>(); + + private static final Comparator MEAL_COMPARATOR = Comparator.comparing(Meal::getDateTime).reversed(); + + // Map userId -> (mealId-> meal) + private Map> repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); { - MealsUtil.MEALS.forEach(this::save); + MealsUtil.MEALS.forEach(um -> save(um, USER_ID)); + + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); } @Override - public Meal save(Meal meal) { + public Meal save(Meal meal, int userId) { + Objects.requireNonNull(meal); + + Integer mealId = meal.getId(); if (meal.isNew()) { - meal.setId(counter.incrementAndGet()); + mealId = counter.incrementAndGet(); + meal.setId(mealId); + } else if (get(mealId, userId) == null) { + return null; } - repository.put(meal.getId(), meal); + Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); + meals.put(mealId, meal); return meal; } @Override - public void delete(int id) { - repository.remove(id); + public boolean delete(int id, int userId) { + Map meals = repository.get(userId); + return meals != null && meals.remove(id) != null; + } + + @Override + public Meal get(int id, int userId) { + Map meals = repository.get(userId); + return meals == null ? null : meals.get(id); } @Override - public Meal get(int id) { - return repository.get(id); + public Collection getAll(int userId) { + Map meals = repository.get(userId); + return meals == null ? + Collections.emptyList() : + meals.values().stream().sorted(MEAL_COMPARATOR).collect(Collectors.toList()); } @Override - public Collection getAll() { - return repository.values(); + 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/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index 0a0ba9b9b78f..36ff1dea0619 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -1,8 +1,31 @@ package ru.javawebinar.topjava.service; +import ru.javawebinar.topjava.model.Meal; +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 MealService { + Meal 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); + + Meal update(Meal meal, int userId) throws NotFoundException; + + Meal save(Meal meal, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 6e00e324ef51..b27938f7612b 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.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.Meal; import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.exception.ExceptionUtil; + +import java.time.LocalDateTime; +import java.util.Collection; /** * GKislin * 06.03.2015. */ +@Service public class MealServiceImpl implements MealService { + @Autowired private MealRepository repository; + @Override + public Meal get(int id, int userId) { + return ExceptionUtil.checkNotFoundWithId(repository.get(id, userId), id); + } + + @Override + public void delete(int id, int userId) { + ExceptionUtil.checkNotFoundWithId(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 Meal update(Meal meal, int userId) { + return ExceptionUtil.checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + + @Override + public Meal save(Meal meal, int userId) { + return repository.save(meal, userId); + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java similarity index 96% rename from src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java rename to src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java index 204c177e46dc..53795a28d214 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.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/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c10ed7394c09..0451e73ab547 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.util; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.model.MealWithExceed; +import ru.javawebinar.topjava.to.MealWithExceed; import java.time.LocalDate; 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 115311fad6b7..595f01f0d1dd 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.format.DateTimeFormatter; /** @@ -9,13 +9,16 @@ * 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 boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; + public static final LocalDate MIN_DATE = LocalDate.of(1, 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; } public static String toString(LocalDateTime ldt) { - return ldt == null ? "" : ldt.format(DATE_TME_FORMATTER); + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index a9ab9e306f8a..d928b5a6c0ff 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,12 +1,72 @@ 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.AuthorizedUser; +import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.to.MealWithExceed; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.TimeUtil; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; /** * GKislin * 06.03.2015. */ +@Controller public class MealRestController { + private static final Logger LOG = LoggerFactory.getLogger(MealRestController.class); + + @Autowired private MealService service; + public Meal get(int id) { + int userId = AuthorizedUser.id(); + LOG.info("get meal {} for User {}", id, userId); + return service.get(id, userId); + } + + public void delete(int id) { + int userId = AuthorizedUser.id(); + LOG.info("delete meal {} for User {}", id, userId); + service.delete(id, userId); + } + + public List getAll() { + int userId = AuthorizedUser.id(); + LOG.info("getAll for User {}", userId); + return MealsUtil.getWithExceeded(service.getAll(userId), AuthorizedUser.getCaloriesPerDay()); + } + + public void update(Meal meal, int id) { + meal.setId(id); + int userId = AuthorizedUser.id(); + LOG.info("update {} for User {}", meal, userId); + service.update(meal, userId); + } + + public Meal create(Meal meal) { + meal.setId(null); + int userId = AuthorizedUser.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 = AuthorizedUser.id(); + LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId); + + return MealsUtil.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, + AuthorizedUser.getCaloriesPerDay() + ); + } } diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/mealList.jsp index 684fc5fb015d..655888c764e3 100644 --- a/src/main/webapp/mealList.jsp +++ b/src/main/webapp/mealList.jsp @@ -32,7 +32,7 @@ - + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> From 6181a91e6927cbb6187b1d4de076259872f25611 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 13:34:36 +0500 Subject: [PATCH 010/111] 3 04 HW2 optional filter --- .../ru/javawebinar/topjava/util/TimeUtil.java | 11 ++++ .../javawebinar/topjava/web/MealServlet.java | 65 ++++++++++++++----- src/main/webapp/css/style.css | 24 +++++++ src/main/webapp/mealEdit.jsp | 19 +----- src/main/webapp/mealList.jsp | 30 ++++++--- 5 files changed, 104 insertions(+), 45 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 595f01f0d1dd..51f36a6ba31b 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; /** @@ -21,4 +24,12 @@ public static > boolean isBetween(T value, T sta public static String toString(LocalDateTime ldt) { return ldt == null ? "" : ldt.format(DATE_TIME_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 1d04786e6577..2071fe839cb9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,11 +2,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ru.javawebinar.topjava.AuthorizedUser; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; -import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.TimeUtil; +import ru.javawebinar.topjava.web.meal.MealRestController; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -14,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; /** @@ -24,26 +26,54 @@ public class MealServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - private MealRepository repository; + private ConfigurableApplicationContext springContext; + private MealRestController mealController; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - repository = new InMemoryMealRepositoryImpl(); + springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + mealController = springContext.getBean(MealRestController.class); + } + + @Override + public void destroy() { + springContext.close(); + super.destroy(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); - String id = request.getParameter("id"); + String action = request.getParameter("action"); + if (action == null) { + final Meal meal = new Meal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); - Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.valueOf(request.getParameter("calories"))); + if (request.getParameter("id").isEmpty()) { + LOG.info("Create {}", meal); + mealController.create(meal); + } else { + LOG.info("Update {}", meal); + mealController.update(meal, getId(request)); + } + response.sendRedirect("meals"); + + } else if ("filter".equals(action)) { + 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); + } + } - LOG.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal, AuthorizedUser.id()); - response.sendRedirect("meals"); + private String resetParam(String param, HttpServletRequest request) { + String value = request.getParameter(param); + request.setAttribute(param, value); + return value; } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -51,20 +81,19 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t if (action == null) { LOG.info("getAll"); - request.setAttribute("mealList", - MealsUtil.getWithExceeded(repository.getAll(AuthorizedUser.id()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + 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); - repository.delete(id, AuthorizedUser.id()); + mealController.delete(id); response.sendRedirect("meals"); } else if ("create".equals(action) || "update".equals(action)) { final Meal meal = action.equals("create") ? new Meal(LocalDateTime.now().withNano(0).withSecond(0), "", 1000) : - repository.get(getId(request), AuthorizedUser.id()); + mealController.get(getId(request)); request.setAttribute("meal", meal); request.getRequestDispatcher("mealEdit.jsp").forward(request, response); } 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 bef9569b1716..7c32430b1857 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 655888c764e3..2fb6810bb748 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 fcae146e054848a63e67bf64b4cb6e13d405fba3 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 13:35:05 +0500 Subject: [PATCH 011/111] 3 05 HW2 optional select user --- .../java/ru/javawebinar/topjava/AuthorizedUser.java | 7 ++++++- .../java/ru/javawebinar/topjava/web/UserServlet.java | 7 +++++++ src/main/webapp/index.html | 12 ++++++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index 7d9365626a55..d1b16bc5f27a 100644 --- a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -7,9 +7,14 @@ * 06.03.2015. */ public class AuthorizedUser { + public static int id = 1; public static int id() { - return 1; + return id; + } + + public static void setId(int id) { + AuthorizedUser.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 7cdd767ffd0f..40b20861da9a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.web; import org.slf4j.Logger; +import ru.javawebinar.topjava.AuthorizedUser; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -17,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")); + AuthorizedUser.setId(userId); + response.sendRedirect("meals"); + } + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOG.debug("forward to userList"); request.getRequestDispatcher("/userList.jsp").forward(request, response); diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 892cf5638cb1..5cabf1e8db3f 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -7,9 +7,13 @@

    Проект "Java Enterprise (Topjava)"


    - +
    + Meal list of  + + + From 6677a7673be9fecdfb3129f4f8e62faa941ea4c7 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:14:52 +0500 Subject: [PATCH 012/111] 3_06_bean_life_cycle --- .../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 74b4cb84326f..57716837f553 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 c8447051d15b24124e7b8a001923e93b710f3b60 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:54:37 +0500 Subject: [PATCH 013/111] 3_08 --- pom.xml | 18 ++++++ .../ru/javawebinar/topjava/SpringMain.java | 4 +- .../ru/javawebinar/topjava/UserTestData.java | 16 ++++++ .../mock/InMemoryMealRepositoryImpl.java | 4 +- .../mock/InMemoryUserRepositoryImpl.java | 3 - ...InMemoryAdminRestControllerSpringTest.java | 54 ++++++++++++++++++ .../web/InMemoryAdminRestControllerTest.java | 55 +++++++++++++++++++ 7 files changed, 146 insertions(+), 8 deletions(-) rename src/{main => test}/java/ru/javawebinar/topjava/SpringMain.java (88%) create mode 100644 src/test/java/ru/javawebinar/topjava/UserTestData.java rename src/{main => test}/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (93%) rename src/{main => test}/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java (96%) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index d8a9b835cc97..190c4f22ffc7 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ 1.1.7 1.7.21 + + + 4.12 @@ -88,6 +91,21 @@ jstl 1.2 + + + + junit + junit + ${junit.version} + test + + + org.springframework + spring-test + ${spring.version} + test + + diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java similarity index 88% rename from src/main/java/ru/javawebinar/topjava/SpringMain.java rename to src/test/java/ru/javawebinar/topjava/SpringMain.java index 68e894921a64..d659837ae51e 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.MealWithExceed; import ru.javawebinar.topjava.web.meal.MealRestController; 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("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); - adminUserController.create(new User(1, "userName", "email", "password", Role.ROLE_ADMIN)); + adminUserController.create(UserTestData.USER); System.out.println(); MealRestController mealController = appCtx.getBean(MealRestController.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/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java similarity index 93% rename from src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index f43b78dd372e..25c28e8e5e17 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.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 96% 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 57716837f553..6b1e658d5739 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 { private 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/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); + } +} 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..becff75a3425 --- /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); + } + + @AfterClass + public static void afterClass() { + appCtx.close(); + } + + @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); + } + + @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 17273f9135828f4e93c9c151725441fa16246ed4 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:57:59 +0500 Subject: [PATCH 014/111] 3 09 add postgresql --- src/main/resources/db/postgres.properties | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/resources/db/postgres.properties diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties new file mode 100644 index 000000000000..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 7a44808707c4c544a971e76481a0667e285bd375 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:58:22 +0500 Subject: [PATCH 015/111] 3 10 populate and init db --- 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 aea3b5a5ca0200b2d47602a08c4335555073ad2b Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:58:40 +0500 Subject: [PATCH 016/111] 3 11 impl JdbcUserRepository --- pom.xml | 14 ++++ .../jdbc/JdbcUserRepositoryImpl.java | 84 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java diff --git a/pom.xml b/pom.xml index 190c4f22ffc7..90d19917a0c3 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,8 @@ 1.1.7 1.7.21 + + 9.4.1211 4.12 @@ -77,6 +79,18 @@ + + org.springframework + spring-jdbc + ${spring.version} + + + + + org.postgresql + postgresql + ${postgresql.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); + } +} From f0abd87d12c09949353143b307e8a5a57c200e8a Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:59:01 +0500 Subject: [PATCH 017/111] 3 12 test UserService --- .../javawebinar/topjava/model/BaseEntity.java | 23 +++++ .../ru/javawebinar/topjava/model/User.java | 4 + .../jdbc/JdbcMealRepositoryImpl.java | 42 +++++++++ .../javawebinar/topjava/util/DbPopulator.java | 28 ++++++ src/main/resources/spring/spring-app.xml | 2 +- src/main/resources/spring/spring-db.xml | 29 ++++++ .../ru/javawebinar/topjava/MealTestData.java | 14 +++ .../ru/javawebinar/topjava/UserTestData.java | 23 ++++- .../topjava/matcher/ModelMatcher.java | 72 ++++++++++++++ .../topjava/service/UserServiceTest.java | 94 +++++++++++++++++++ 10 files changed, 327 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DbPopulator.java create mode 100644 src/main/resources/spring/spring-db.xml 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 c5edf2fb997b..fd8e49bef0ab 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, MealsUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java new file mode 100644 index 000000000000..cd8d54990d6d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * User: gkislin + * Date: 26.08.2014 + */ + +@Repository +public class JdbcMealRepositoryImpl implements MealRepository { + + @Override + public Meal save(Meal Meal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public Meal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List 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/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 new file mode 100644 index 000000000000..051f225300cd --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java new file mode 100644 index 000000000000..c05cd451506f --- /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.Meal; + +/** + * GKislin + * 13.03.2015. + */ +public class MealTestData { + + public static final ModelMatcher MATCHER = new ModelMatcher<>(); + +} diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index bb8a9fb19788..f7ce7774b6b4 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -1,16 +1,33 @@ package ru.javawebinar.topjava; +import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import java.util.Objects; + +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<>( + (expected, actual) -> expected == actual || + (Objects.equals(expected.getPassword(), actual.getPassword()) + && Objects.equals(expected.getId(), actual.getId()) + && Objects.equals(expected.getName(), actual.getName()) + && Objects.equals(expected.getEmail(), actual.getEmail()) + && Objects.equals(expected.getCaloriesPerDay(), actual.getCaloriesPerDay()) + && Objects.equals(expected.isEnabled(), actual.isEnabled()) +// && Objects.equals(expected.getRoles(), actual.getRoles()) + ) + ); +} \ No newline at end of file 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..0a88d5eb03ed --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -0,0 +1,72 @@ +package ru.javawebinar.topjava.matcher; + +import org.junit.Assert; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * GKislin + * 06.01.2015. + * + * This class wrap every entity by Wrapper before assertEquals in order to compare them by comparator + * Default comparator compare by String.valueOf(entity) + * + * @param : Entity + */ +public class ModelMatcher { + public interface Comparator { + boolean compare(T expected, T actual); + } + + private static final Comparator DEFAULT_COMPARATOR = + (Object expected, Object actual) -> String.valueOf(expected).equals(String.valueOf(actual)); + + private Comparator comparator; + + public ModelMatcher() { + this((Comparator) DEFAULT_COMPARATOR); + } + + public ModelMatcher(Comparator comparator) { + this.comparator = comparator; + } + + private class Wrapper { + private T entity; + + private Wrapper(T entity) { + this.entity = entity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Wrapper that = (Wrapper) o; + return entity != null ? comparator.compare(entity, that.entity) : that.entity == null; + } + + @Override + public String toString() { + return String.valueOf(entity); + } + } + + public void assertEquals(T expected, T actual) { + Assert.assertEquals(wrap(expected), wrap(actual)); + } + + public void assertCollectionEquals(Collection expected, Collection actual) { + Assert.assertEquals(wrap(expected), wrap(actual)); + } + + public Wrapper wrap(T entity) { + return new Wrapper(entity); + } + + public List wrap(Collection collection) { + return collection.stream().map(this::wrap).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..48d1f0a36a9d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -0,0 +1,94 @@ +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.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 { + User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); + User created = service.save(newUser); + newUser.setId(created.getId()); + MATCHER.assertCollectionEquals(Arrays.asList(ADMIN, newUser, USER), service.getAll()); + } + + @Test(expected = DataAccessException.class) + public void testDuplicateMailSave() throws Exception { + service.save(new User(null, "Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER)); + } + + @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 { + User updated = new User(USER); + updated.setName("UpdatedName"); + updated.setCaloriesPerDay(330); + service.update(updated); + MATCHER.assertEquals(updated, service.get(USER_ID)); + } +} \ No newline at end of file From 855712a61fa5e77b3dc32b2a305d50b6501af287 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:59:13 +0500 Subject: [PATCH 018/111] 3 13 test logging --- 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 136c098b6d8b6e4a4bfdb0815f36041a861bb1e0 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 23 Sep 2016 16:59:27 +0500 Subject: [PATCH 019/111] 3 14 fix servlet --- src/main/java/ru/javawebinar/topjava/AuthorizedUser.java | 3 ++- src/main/java/ru/javawebinar/topjava/web/MealServlet.java | 2 +- src/main/webapp/index.html | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index d1b16bc5f27a..0347b999afaa 100644 --- a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava; +import ru.javawebinar.topjava.model.BaseEntity; import ru.javawebinar.topjava.util.MealsUtil; /** @@ -7,7 +8,7 @@ * 06.03.2015. */ public class AuthorizedUser { - 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 2071fe839cb9..576e916afa2b 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(MealRestController.class); } diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 5cabf1e8db3f..7478d296d331 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -10,8 +10,8 @@

    Проект " Meal list of  From 8420a9ed77715d286bea23c640a16ec0058a5e33 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 12:31:06 +0500 Subject: [PATCH 020/111] 4_0_1_fix --- pom.xml | 8 ++++++++ src/main/java/ru/javawebinar/topjava/model/Meal.java | 2 -- .../ru/javawebinar/topjava/repository/MealRepository.java | 2 +- .../topjava/repository/jdbc/JdbcMealRepositoryImpl.java | 2 +- .../topjava/repository/jdbc/JdbcUserRepositoryImpl.java | 3 ++- src/main/resources/logback.xml | 4 ---- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 90d19917a0c3..745d672ac8bf 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,14 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + -Dfile.encoding=UTF-8 + + diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index c8589c09d45f..74e0de04e1e8 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -9,8 +9,6 @@ * 11.01.2015. */ public class Meal extends BaseEntity { - private Integer id; - private final LocalDateTime dateTime; private final String description; diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index adb42da23a5d..a274d756f88f 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -11,7 +11,7 @@ */ public interface MealRepository { // null if updated meal do not belong to userId - Meal save(Meal Meal, int userId); + Meal save(Meal meal, int userId); // false if meal do not belong to userId boolean delete(int id, int userId); diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index cd8d54990d6d..129c4263d8f6 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -16,7 +16,7 @@ public class JdbcMealRepositoryImpl implements MealRepository { @Override - public Meal save(Meal Meal, int userId) { + public Meal save(Meal meal, int userId) { return null; } 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..d5471553b575 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -74,7 +74,8 @@ public User get(int id) { @Override public User getByEmail(String email) { - return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + return DataAccessUtils.singleResult(users); } @Override diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 8ee5f21b023e..e9b900b26669 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,10 +1,6 @@ - - true - - From adf79a2c0aedad0f4f77eeac42e5b89c5a8ecef9 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 12:32:25 +0500 Subject: [PATCH 021/111] 4 0 2 refactoring --- .../javawebinar/topjava/web/MealServlet.java | 13 +++++++------ .../javawebinar/topjava/web/UserServlet.java | 4 ++-- src/main/webapp/{mealEdit.jsp => meal.jsp} | 0 src/main/webapp/{mealList.jsp => meals.jsp} | 2 +- src/main/webapp/{userList.jsp => users.jsp} | 0 .../topjava/matcher/ModelMatcher.java | 4 ++-- .../mock/InMemoryMealRepositoryImpl.java | 19 +++++++++++++------ 7 files changed, 25 insertions(+), 17 deletions(-) rename src/main/webapp/{mealEdit.jsp => meal.jsp} (100%) rename src/main/webapp/{mealList.jsp => meals.jsp} (99%) rename src/main/webapp/{userList.jsp => users.jsp} (100%) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 576e916afa2b..d3df866a79dc 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -17,6 +17,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.temporal.ChronoUnit; import java.util.Objects; /** @@ -65,8 +66,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) 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); + request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); } } @@ -81,8 +82,8 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t if (action == null) { LOG.info("getAll"); - request.setAttribute("mealList", mealController.getAll()); - request.getRequestDispatcher("/mealList.jsp").forward(request, response); + request.setAttribute("meals", mealController.getAll()); + request.getRequestDispatcher("/meals.jsp").forward(request, response); } else if ("delete".equals(action)) { int id = getId(request); @@ -92,10 +93,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t } else if ("create".equals(action) || "update".equals(action)) { final Meal meal = action.equals("create") ? - new Meal(LocalDateTime.now().withNano(0).withSecond(0), "", 1000) : + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000) : mealController.get(getId(request)); request.setAttribute("meal", meal); - request.getRequestDispatcher("mealEdit.jsp").forward(request, response); + request.getRequestDispatcher("meal.jsp").forward(request, response); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 40b20861da9a..9e98b822ef47 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -25,7 +25,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - LOG.debug("forward to userList"); - request.getRequestDispatcher("/userList.jsp").forward(request, response); + LOG.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); } } diff --git a/src/main/webapp/mealEdit.jsp b/src/main/webapp/meal.jsp similarity index 100% rename from src/main/webapp/mealEdit.jsp rename to src/main/webapp/meal.jsp diff --git a/src/main/webapp/mealList.jsp b/src/main/webapp/meals.jsp similarity index 99% rename from src/main/webapp/mealList.jsp rename to src/main/webapp/meals.jsp index 2fb6810bb748..63bd33c4c4a0 100644 --- a/src/main/webapp/mealList.jsp +++ b/src/main/webapp/meals.jsp @@ -60,4 +60,4 @@

    - \ No newline at end of file + diff --git a/src/main/webapp/userList.jsp b/src/main/webapp/users.jsp similarity index 100% rename from src/main/webapp/userList.jsp rename to src/main/webapp/users.jsp diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index 0a88d5eb03ed..052c69ce5b2e 100644 --- a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -9,7 +9,7 @@ /** * GKislin * 06.01.2015. - * + *

    * This class wrap every entity by Wrapper before assertEquals in order to compare them by comparator * Default comparator compare by String.valueOf(entity) * @@ -21,7 +21,7 @@ public interface Comparator { } private static final Comparator DEFAULT_COMPARATOR = - (Object expected, Object actual) -> String.valueOf(expected).equals(String.valueOf(actual)); + (Object expected, Object actual) -> expected == actual || String.valueOf(expected).equals(String.valueOf(actual)); private Comparator comparator; diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 25c28e8e5e17..50ba68cad7b8 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -8,10 +8,14 @@ import java.time.LocalDateTime; import java.time.Month; -import java.util.*; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import java.util.stream.Stream; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; @@ -66,19 +70,22 @@ public Meal get(int id, int userId) { @Override public Collection getAll(int userId) { - Map meals = repository.get(userId); - return meals == null ? - Collections.emptyList() : - meals.values().stream().sorted(MEAL_COMPARATOR).collect(Collectors.toList()); + return getAllStream(userId).collect(Collectors.toList()); } @Override public Collection getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { Objects.requireNonNull(startDateTime); Objects.requireNonNull(endDateTime); - return getAll(userId).stream() + return getAllStream(userId) .filter(um -> TimeUtil.isBetween(um.getDateTime(), startDateTime, endDateTime)) .collect(Collectors.toList()); } + + private Stream getAllStream(int userId) { + Map meals = repository.get(userId); + return meals == null ? + Stream.empty() : meals.values().stream().sorted(MEAL_COMPARATOR); + } } From 5086a368938b38d67718ac2ec2ddc7ff4493e809 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 14:09:48 +0500 Subject: [PATCH 022/111] 4 1 HW3 --- .../ru/javawebinar/topjava/model/Meal.java | 21 ++++++- .../jdbc/JdbcMealRepositoryImpl.java | 62 +++++++++++++++++-- src/main/resources/db/initDB.sql | 11 ++++ src/main/resources/db/populateDB.sql | 11 ++++ 4 files changed, 96 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 74e0de04e1e8..41796d382ad9 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -9,11 +9,14 @@ * 11.01.2015. */ public class Meal extends BaseEntity { - private final LocalDateTime dateTime; + private LocalDateTime dateTime; - private final String description; + private String description; - private final int calories; + private int calories; + + public Meal() { + } public Meal(LocalDateTime dateTime, String description, int calories) { this(null, dateTime, description, calories); @@ -46,6 +49,18 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } + public void setDateTime(LocalDateTime dateTime) { + this.dateTime = dateTime; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setCalories(int calories) { + this.calories = calories; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index 129c4263d8f6..6f7d3c7d3927 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.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.Meal; import ru.javawebinar.topjava.repository.MealRepository; +import javax.sql.DataSource; import java.time.LocalDateTime; import java.util.List; @@ -15,28 +24,69 @@ @Repository public class JdbcMealRepositoryImpl implements MealRepository { + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private SimpleJdbcInsert insertUserMeal; + + @Autowired + public JdbcMealRepositoryImpl(DataSource dataSource) { + this.insertUserMeal = new SimpleJdbcInsert(dataSource) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + } + @Override - public Meal save(Meal meal, int userId) { - return null; + public Meal save(Meal 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 Meal get(int id, int userId) { - return null; + List meals = jdbcTemplate.query( + "SELECT * FROM meals WHERE id = ? AND user_id = ?", ROW_MAPPER, id, userId); + return DataAccessUtils.singleResult(meals); } @Override public List getAll(int userId) { - return 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); } } diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index a0631c79567b..aede3736c9d0 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 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/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); From f867a7c0deda59ba3288da872bac04f522922d52 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 14:10:11 +0500 Subject: [PATCH 023/111] 4 2 HW3 optional --- .../javawebinar/topjava/util/MealsUtil.java | 18 ---- .../ru/javawebinar/topjava/MealTestData.java | 28 ++++++ .../mock/InMemoryMealRepositoryImpl.java | 12 --- .../topjava/service/MealServiceTest.java | 92 +++++++++++++++++++ 4 files changed, 120 insertions(+), 30 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 0451e73ab547..46a14b9edc93 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -4,9 +4,7 @@ import ru.javawebinar.topjava.to.MealWithExceed; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.Month; import java.util.*; import java.util.stream.Collectors; @@ -15,24 +13,8 @@ * 31.05.2015. */ public class MealsUtil { - public static final List MEALS = Arrays.asList( - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), - new Meal(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(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY); - filteredMealsWithExceeded.forEach(System.out::println); - - System.out.println(getFilteredWithExceededByCycle(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY)); - } - public static List getWithExceeded(Collection meals, int caloriesPerDay) { return getFilteredWithExceeded(meals, LocalTime.MIN, LocalTime.MAX, caloriesPerDay); } diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index c05cd451506f..c5c224f3db60 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -3,6 +3,13 @@ import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.Meal; +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 +18,25 @@ public class MealTestData { public static final ModelMatcher MATCHER = new ModelMatcher<>(); + public static final int MEAL1_ID = START_SEQ + 2; + public static final int ADMIN_MEAL_ID = START_SEQ + 8; + + public static final Meal MEAL1 = new Meal(MEAL1_ID, of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500); + public static final Meal MEAL2 = new Meal(MEAL1_ID + 1, of(2015, Month.MAY, 30, 13, 0), "Обед", 1000); + public static final Meal MEAL3 = new Meal(MEAL1_ID + 2, of(2015, Month.MAY, 30, 20, 0), "Ужин", 500); + public static final Meal MEAL4 = new Meal(MEAL1_ID + 3, of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500); + public static final Meal MEAL5 = new Meal(MEAL1_ID + 4, of(2015, Month.MAY, 31, 13, 0), "Обед", 1000); + public static final Meal MEAL6 = new Meal(MEAL1_ID + 5, of(2015, Month.MAY, 31, 20, 0), "Ужин", 510); + public static final Meal ADMIN_MEAL1 = new Meal(ADMIN_MEAL_ID, of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510); + public static final Meal ADMIN_MEAL2 = new Meal(ADMIN_MEAL_ID + 1, of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500); + + public static final List MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + + public static Meal getCreated() { + return new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Созданный ужин", 300); + } + + public static Meal getUpdated() { + return new Meal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); + } } diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 50ba68cad7b8..6a644f9fe054 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -3,11 +3,9 @@ import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.util.MealsUtil; import ru.javawebinar.topjava.util.TimeUtil; import java.time.LocalDateTime; -import java.time.Month; import java.util.Collection; import java.util.Comparator; import java.util.Map; @@ -17,9 +15,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; -import static ru.javawebinar.topjava.UserTestData.USER_ID; - /** * GKislin * 15.09.2015. @@ -33,13 +28,6 @@ public class InMemoryMealRepositoryImpl implements MealRepository { private Map> repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); - { - MealsUtil.MEALS.forEach(um -> save(um, USER_ID)); - - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); - } - @Override public Meal save(Meal meal, int userId) { Objects.requireNonNull(meal); diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java new file mode 100644 index 000000000000..f944d9c9787a --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.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.Meal; +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 MealServiceTest { + + @Autowired + protected MealService 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 { + Meal 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 { + Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); + MATCHER.assertEquals(ADMIN_MEAL1, actual); + } + + @Test(expected = NotFoundException.class) + public void testGetNotFound() throws Exception { + service.get(MEAL1_ID, ADMIN_ID); + } + + @Test + public void testUpdate() throws Exception { + Meal 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 { + Meal item = service.get(MEAL1_ID, USER_ID); + service.update(item, ADMIN_ID); + } + + @Test + public void testGetAll() throws Exception { + MATCHER.assertCollectionEquals(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 7a6c3a48e47d18ad7c5692c06d6922d9e463aaee Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 14:49:37 +0500 Subject: [PATCH 024/111] 4 3 improve code --- .travis.yml | 2 ++ config/setenv.sh | 1 + .../topjava/service/MealServiceImpl.java | 5 +++++ .../topjava/service/UserServiceImpl.java | 4 ++++ .../java/ru/javawebinar/topjava/util/TimeUtil.java | 3 +++ .../topjava/util/exception/ExceptionUtil.java | 7 ++++++- .../ru/javawebinar/topjava/web/MealServlet.java | 2 +- .../topjava/web/user/AbstractUserController.java | 14 +++++++------- .../javawebinar/topjava/matcher/ModelMatcher.java | 8 ++++---- .../mock/InMemoryMealRepositoryImpl.java | 5 ----- .../mock/InMemoryUserRepositoryImpl.java | 3 --- 11 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..6f1b2a97afbf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +language: java +jdk: oraclejdk8 \ No newline at end of file diff --git a/config/setenv.sh b/config/setenv.sh index 7f5c0ebd9b90..e7986cf38aae 100644 --- a/config/setenv.sh +++ b/config/setenv.sh @@ -1,3 +1,4 @@ +#!/usr/bin/env bash # run tomcat with JMX ability as admin # for remote connection add -Djava.rmi.server.hostname=TomcatServer_IP export CATALINA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false" \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index b27938f7612b..72207b808fae 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.exception.ExceptionUtil; @@ -31,6 +32,8 @@ public void delete(int id, int userId) { @Override public Collection getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Assert.notNull(startDateTime, "startDateTime must not be null"); + Assert.notNull(endDateTime, "endDateTime must not be null"); return repository.getBetween(startDateTime, endDateTime, userId); } @@ -41,11 +44,13 @@ public Collection getAll(int userId) { @Override public Meal update(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); return ExceptionUtil.checkNotFoundWithId(repository.save(meal, userId), meal.getId()); } @Override public Meal save(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index d1f29b2531c8..e31832d2af06 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.ExceptionUtil; @@ -21,6 +22,7 @@ public class UserServiceImpl implements UserService { @Override public User save(User user) { + Assert.notNull(user, "user must not be null"); return repository.save(user); } @@ -36,6 +38,7 @@ public User get(int id) throws NotFoundException { @Override public User getByEmail(String email) throws NotFoundException { + Assert.notNull(email, "email must not be null"); return ExceptionUtil.checkNotFound(repository.getByEmail(email), "email=" + email); } @@ -46,6 +49,7 @@ public List getAll() { @Override public void update(User user) { + Assert.notNull(user, "user must not be null"); repository.save(user); } } diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 51f36a6ba31b..d3d2b8bd1210 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -17,6 +17,9 @@ public class TimeUtil { public static final LocalDate MIN_DATE = LocalDate.of(1, 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; } 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 254a7dba2b00..db860486466f 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 checkNotFoundWithId(boolean found, int id) { checkNotFound(found, "id=" + id); } @@ -20,6 +23,8 @@ public static T checkNotFound(T object, String msg) { } public static void checkNotFound(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 d3df866a79dc..77f6f81f2b27 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -92,7 +92,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t response.sendRedirect("meals"); } else if ("create".equals(action) || "update".equals(action)) { - final Meal meal = action.equals("create") ? + final Meal meal = "create".equals(action) ? new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000) : mealController.get(getId(request)); request.setAttribute("meal", meal); 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/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index 052c69ce5b2e..393e28076542 100644 --- a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -16,15 +16,15 @@ * @param : Entity */ public class ModelMatcher { - public interface Comparator { - boolean compare(T expected, T actual); - } - private static final Comparator DEFAULT_COMPARATOR = (Object expected, Object actual) -> expected == actual || String.valueOf(expected).equals(String.valueOf(actual)); private Comparator comparator; + public interface Comparator { + boolean compare(T expected, T actual); + } + public ModelMatcher() { this((Comparator) DEFAULT_COMPARATOR); } diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 6a644f9fe054..1335bf2b8cb8 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -9,7 +9,6 @@ import java.util.Collection; import java.util.Comparator; import java.util.Map; -import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -30,8 +29,6 @@ public class InMemoryMealRepositoryImpl implements MealRepository { @Override public Meal save(Meal meal, int userId) { - Objects.requireNonNull(meal); - Integer mealId = meal.getId(); if (meal.isNew()) { mealId = counter.incrementAndGet(); @@ -63,8 +60,6 @@ public Collection getAll(int userId) { @Override public Collection getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { - Objects.requireNonNull(startDateTime); - Objects.requireNonNull(endDateTime); return getAllStream(userId) .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 6b1e658d5739..99d13a6b52a0 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -11,7 +11,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; @@ -31,7 +30,6 @@ public class InMemoryUserRepositoryImpl implements UserRepository { @Override public User save(User user) { - Objects.requireNonNull(user); if (user.isNew()) { user.setId(counter.incrementAndGet()); } @@ -68,7 +66,6 @@ public List getAll() { @Override public User getByEmail(String email) { - Objects.requireNonNull(email); return repository.values().stream() .filter(u -> email.equals(u.getEmail())) .findFirst() From f0b3fb1467f6cd5163878b207013898275a5f6d7 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 16:16:56 +0500 Subject: [PATCH 025/111] 4 4 init and populate db --- .../javawebinar/topjava/util/DbPopulator.java | 28 ------------------- .../topjava/service/MealServiceTest.java | 13 ++------- .../topjava/service/UserServiceTest.java | 13 ++------- 3 files changed, 6 insertions(+), 48 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/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index f944d9c9787a..1c1f524dda8e 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,14 +1,14 @@ 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.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.model.Meal; -import ru.javawebinar.topjava.util.DbPopulator; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.time.LocalDate; @@ -24,19 +24,12 @@ "classpath:spring/spring-db.xml" }) @RunWith(SpringJUnit4ClassRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { @Autowired protected MealService 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); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 48d1f0a36a9d..c175aae677e3 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -1,15 +1,15 @@ 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.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 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; @@ -23,19 +23,12 @@ "classpath:spring/spring-db.xml" }) @RunWith(SpringJUnit4ClassRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) 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 { User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); From 9a7a4642c0d0e7471a3ae5cd0f725ad352ebdb51 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 16:17:12 +0500 Subject: [PATCH 026/111] 4 5 create mock test ctx --- src/main/resources/spring/spring-app.xml | 2 -- src/test/java/ru/javawebinar/topjava/SpringMain.java | 2 +- .../topjava/web/InMemoryAdminRestControllerSpringTest.java | 2 +- .../topjava/web/InMemoryAdminRestControllerTest.java | 2 +- src/test/resources/spring/mock.xml | 7 +++++++ 5 files changed, 10 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/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index d659837ae51e..d9ce7e6274ac 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/mock.xml")) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(UserTestData.USER); diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java index 9396810afe48..a9125c6e4d96 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -22,7 +22,7 @@ * GKislin * 13.03.2015. */ -@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 becff75a3425..f7006200f781 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/mock.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 921dcc28df0873382b3127d9d443f3f350d2d546 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 16:17:30 +0500 Subject: [PATCH 027/111] 4 6 add jpa --- .../javawebinar/topjava/model/BaseEntity.java | 7 ++++ .../ru/javawebinar/topjava/model/Meal.java | 13 ++++++ .../topjava/model/NamedEntity.java | 8 ++++ .../repository/jpa/JpaMealRepositoryImpl.java | 42 +++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 892beb4c6281..007e260dcb68 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,12 +1,19 @@ package ru.javawebinar.topjava.model; +import javax.persistence.*; + /** * User: gkislin * Date: 22.08.2014 */ +@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/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 41796d382ad9..6e3c3b79ccdc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -15,6 +17,9 @@ public class Meal extends BaseEntity { private int calories; + @ManyToOne(fetch = FetchType.LAZY) + private User user; + public Meal() { } @@ -61,6 +66,14 @@ public void setCalories(int calories) { this.calories = calories; } + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java index ca92e394dc98..e593d37981e7 100644 --- a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -1,11 +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/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java new file mode 100644 index 000000000000..552cc073f555 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * User: gkisline + * Date: 26.08.2014 + */ + +@Repository +public class JpaMealRepositoryImpl implements MealRepository { + + @Override + public Meal save(Meal meal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public Meal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} \ No newline at end of file From 49e37437520cc8714c8ccb4d8c6fd7b29b0481d6 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 16:17:42 +0500 Subject: [PATCH 028/111] 4 7 add named query and transaction --- .../ru/javawebinar/topjava/model/User.java | 31 ++++++++ .../repository/jpa/JpaUserRepositoryImpl.java | 72 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index fd8e49bef0ab..b699ef73565d 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -2,6 +2,12 @@ import ru.javawebinar.topjava.util.MealsUtil; +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; @@ -10,18 +16,43 @@ * 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 = MealsUtil.DEFAULT_CALORIES_PER_DAY; public User() { 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..30a5fc452a58 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -0,0 +1,72 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; + +/** + * 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) { + List users = em.createNamedQuery(User.BY_EMAIL, User.class).setParameter(1, email).getResultList(); + return DataAccessUtils.singleResult(users); + } + + @Override + public List getAll() { + return em.createNamedQuery(User.ALL_SORTED, User.class).getResultList(); + } +} From d41bd9a67e2094649d79b638993909afac106292 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 30 Sep 2016 16:17:55 +0500 Subject: [PATCH 029/111] 4 8 add hsqldb --- pom.xml | 35 ++++++++++++- src/main/resources/db/hsqldb.properties | 13 +++++ src/main/resources/db/initDB_hsql.sql | 37 ++++++++++++++ src/main/resources/db/postgres.properties | 8 +++ src/main/resources/spring/spring-db.xml | 62 +++++++++++++++++++---- 5 files changed, 142 insertions(+), 13 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 745d672ac8bf..9e9c473efdcc 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,10 @@ 9.4.1211 4.12 + + + 5.2.2.Final + 5.2.4.Final @@ -89,7 +93,7 @@ org.springframework - spring-jdbc + spring-orm ${spring.version} @@ -99,6 +103,34 @@ postgresql ${postgresql.version} + + org.hsqldb + hsqldb + 2.3.4 + + + + + org.hibernate + hibernate-core + ${hibernate.version} + + + javax.transaction + jta + 1.1 + runtime + + + org.hibernate + hibernate-validator + ${hibernate-validator.version} + + + javax.el + javax.el-api + 2.2.5 + @@ -127,7 +159,6 @@ ${spring.version} test - diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties new file mode 100644 index 000000000000..e879739f755a --- /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 \ No newline at end of file diff --git a/src/main/resources/db/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql new file mode 100644 index 000000000000..3ac041a8d1e8 --- /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 NOT NULL, + description VARCHAR(255) NOT NULL, + calories INT NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY ( user_id ) REFERENCES USERS (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals (user_id, date_time) \ No newline at end of file diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 44dd64eb26b8..51be31f5f7dd 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,3 +5,11 @@ 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 \ 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 051f225300cd..ea1fdf6dd3b4 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,29 +1,69 @@ + 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"> + + + - - - + + + + + + - + - - - + + + + + + + + + + - - + + + + + + + + + + + \ No newline at end of file From 91f49fb9541e753b719c5eb6bb56345f4ef53b17 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 11 Oct 2016 13:24:01 +0500 Subject: [PATCH 030/111] 5 0 fix --- .travis.yml | 8 ++++++- .../ru/javawebinar/topjava/model/User.java | 12 +++++----- .../jdbc/JdbcMealRepositoryImpl.java | 22 +++++++++---------- .../topjava/service/MealServiceTest.java | 17 +++++++++----- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f1b2a97afbf..e8e09032e8f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,8 @@ +# https://docs.travis-ci.com/user/languages/java/ language: java -jdk: oraclejdk8 \ No newline at end of file +jdk: oraclejdk8 + +# https://docs.travis-ci.com/user/database-setup/#PostgreSQL +before_script: + - psql -c 'create database topjava' -U postgres + - psql -c 'create user "user"; grant all privileges on database topjava to "user"' -U postgres \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index b699ef73565d..c1df84abc3f1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -32,28 +32,28 @@ public class User extends NamedEntity { @Column(name = "email", nullable = false, unique = true) @Email @NotEmpty - protected String email; + private String email; @Column(name = "password", nullable = false) @NotEmpty @Length(min = 5) - protected String password; + private String password; @Column(name = "enabled", nullable = false) - protected boolean enabled = true; + private boolean enabled = true; @Column(name = "registered", columnDefinition = "timestamp default now()") - protected Date registered = new Date(); + private 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; + private Set roles; @Column(name = "calories_per_day", columnDefinition = "default 2000") @Digits(fraction = 0, integer = 4) - protected int caloriesPerDay = MealsUtil.DEFAULT_CALORIES_PER_DAY; + private int caloriesPerDay = MealsUtil.DEFAULT_CALORIES_PER_DAY; public User() { } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index 6f7d3c7d3927..f531dc875ef4 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -32,27 +32,27 @@ public class JdbcMealRepositoryImpl implements MealRepository { @Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate; - private SimpleJdbcInsert insertUserMeal; + private SimpleJdbcInsert insertMeal; @Autowired public JdbcMealRepositoryImpl(DataSource dataSource) { - this.insertUserMeal = new SimpleJdbcInsert(dataSource) + this.insertMeal = new SimpleJdbcInsert(dataSource) .withTableName("meals") .usingGeneratedKeyColumns("id"); } @Override - public Meal save(Meal userMeal, int userId) { + public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() - .addValue("id", userMeal.getId()) - .addValue("description", userMeal.getDescription()) - .addValue("calories", userMeal.getCalories()) - .addValue("date_time", userMeal.getDateTime()) + .addValue("id", meal.getId()) + .addValue("description", meal.getDescription()) + .addValue("calories", meal.getCalories()) + .addValue("date_time", meal.getDateTime()) .addValue("user_id", userId); - if (userMeal.isNew()) { - Number newId = insertUserMeal.executeAndReturnKey(map); - userMeal.setId(newId.intValue()); + if (meal.isNew()) { + Number newId = insertMeal.executeAndReturnKey(map); + meal.setId(newId.intValue()); } else { if (namedParameterJdbcTemplate.update("" + "UPDATE meals " + @@ -62,7 +62,7 @@ public Meal save(Meal userMeal, int userId) { return null; } } - return userMeal; + return meal; } @Override diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 1c1f524dda8e..0588afe57381 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,13 +1,14 @@ package ru.javawebinar.topjava.service; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; 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.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -27,17 +28,21 @@ @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + @Autowired protected MealService service; @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)); } - @Test(expected = NotFoundException.class) + @Test public void testDeleteNotFound() throws Exception { + exception.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @@ -54,8 +59,9 @@ public void testGet() throws Exception { MATCHER.assertEquals(ADMIN_MEAL1, actual); } - @Test(expected = NotFoundException.class) + @Test public void testGetNotFound() throws Exception { + exception.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @@ -66,8 +72,9 @@ public void testUpdate() throws Exception { MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); } - @Test(expected = NotFoundException.class) + @Test public void testNotFoundUpdate() throws Exception { + exception.expect(NotFoundException.class); Meal item = service.get(MEAL1_ID, USER_ID); service.update(item, ADMIN_ID); } From 03c876e8aa5622240f93b93cd54b08d8c32e6706 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 11 Oct 2016 13:35:48 +0500 Subject: [PATCH 031/111] 5 1 HW4 --- .../ru/javawebinar/topjava/model/Meal.java | 33 +++++++++++++-- .../repository/jpa/JpaMealRepositoryImpl.java | 42 ++++++++++++++++--- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 6e3c3b79ccdc..45ddfc27a6b0 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,7 +1,10 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import org.hibernate.validator.constraints.NotEmpty; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -10,14 +13,38 @@ * GKislin * 11.01.2015. */ +@NamedQueries({ + @NamedQuery(name = Meal.GET, query = "SELECT m FROM Meal m WHERE m.id=:id AND m.user.id=:userId"), + @NamedQuery(name = Meal.ALL_SORTED, query = "SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC"), + @NamedQuery(name = Meal.DELETE, query = "DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId"), + @NamedQuery(name = Meal.GET_BETWEEN, query = "SELECT m FROM Meal m " + + "WHERE m.user.id=:userId AND m.dateTime BETWEEN :startDate AND :endDate ORDER BY m.dateTime DESC"), + +// @NamedQuery(name = Meal.UPDATE, query = "UPDATE Meal m SET m.dateTime = :datetime, m.calories= :calories," + +// "m.description=:desc where m.id=:id and m.user.id=:userId") +}) +@Entity +@Table(name = "meals", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "date_time"}, name = "meals_unique_user_datetime_idx")}) public class Meal extends BaseEntity { + public static final String GET = "Meal.get"; + public static final String ALL_SORTED = "Meal.getAll"; + public static final String DELETE = "Meal.delete"; + public static final String GET_BETWEEN = "Meal.getBetween"; + + @Column(name = "date_time", nullable = false) + @NotNull private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotEmpty private String description; - private int calories; + @Column(name = "calories", nullable = false) + @Range(min = 10, max = 5000) + protected int calories; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) private User user; public Meal() { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java index 552cc073f555..2a4d4858a582 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -1,9 +1,14 @@ 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.Meal; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.MealRepository; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @@ -13,30 +18,57 @@ */ @Repository +@Transactional(readOnly = true) public class JpaMealRepositoryImpl implements MealRepository { + @PersistenceContext + private EntityManager em; + @Override + @Transactional public Meal save(Meal meal, int userId) { - return null; + if (!meal.isNew() && get(meal.getId(), userId) == null) { + return null; + } + meal.setUser(em.getReference(User.class, userId)); + if (meal.isNew()) { + em.persist(meal); + return meal; + } else { + return em.merge(meal); + } } @Override + @Transactional public boolean delete(int id, int userId) { - return false; + return em.createNamedQuery(Meal.DELETE) + .setParameter("id", id) + .setParameter("userId", userId) + .executeUpdate() != 0; } @Override public Meal get(int id, int userId) { - return null; + List meals = em.createNamedQuery(Meal.GET, Meal.class) + .setParameter("id", id) + .setParameter("userId", userId) + .getResultList(); + return DataAccessUtils.singleResult(meals); } @Override public List getAll(int userId) { - return null; + return em.createNamedQuery(Meal.ALL_SORTED, Meal.class) + .setParameter("userId", userId) + .getResultList(); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return em.createNamedQuery(Meal.GET_BETWEEN, Meal.class) + .setParameter("userId", userId) + .setParameter("startDate", startDate) + .setParameter("endDate", endDate).getResultList(); } } \ No newline at end of file From 93ac4609c64d7c51580b4527154b3de90568b285 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 12 Oct 2016 12:25:10 +0500 Subject: [PATCH 032/111] 5 3 profiles connection pool --- .../java/ru/javawebinar/topjava/Profiles.java | 15 +++++++ src/main/resources/db/hsqldb.properties | 1 - src/main/resources/db/postgres.properties | 1 - .../topjava/service/MealServiceTest.java | 40 ++++++++++++++++--- 4 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/Profiles.java 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..9826ef551b6a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -0,0 +1,15 @@ +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"; + + public static final String ACTIVE_DB = POSTGRES; +} diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties index e879739f755a..aeeb57462caf 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 51be31f5f7dd..8d4b680629ff 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 diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 0588afe57381..a51d112b5175 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,20 +1,27 @@ package ru.javawebinar.topjava.service; +import org.junit.AfterClass; 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.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.model.Meal; 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; @@ -27,22 +34,44 @@ @RunWith(SpringJUnit4ClassRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { + private static final Logger LOG = LoggerFactory.getLogger(MealServiceTest.class); @Rule - public ExpectedException exception = ExpectedException.none(); + public ExpectedException thrown = ExpectedException.none(); + + private static StringBuffer results = new StringBuffer(); + + @AfterClass + public static void printResult() { + System.out.printf("%nTest Duration, ms%n"); + System.out.println("-----------------------------"); + System.out.println(results); + System.out.printf("-----------------------------%n%n"); + } + + @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() { + @Override + protected void finished(long nanos, Description description) { + String result = String.format("%-20s %8d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result).append('\n'); + LOG.info(result + " ms\n"); + } + }; @Autowired protected MealService service; @Test public void testDelete() throws Exception { - service.delete(MEAL1_ID, USER_ID); + service.delete(MealTestData.MEAL1_ID, USER_ID); MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID)); } @Test public void testDeleteNotFound() throws Exception { - exception.expect(NotFoundException.class); + thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @@ -61,7 +90,7 @@ public void testGet() throws Exception { @Test public void testGetNotFound() throws Exception { - exception.expect(NotFoundException.class); + thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @@ -74,8 +103,9 @@ public void testUpdate() throws Exception { @Test public void testNotFoundUpdate() throws Exception { - exception.expect(NotFoundException.class); Meal 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); } From 6aa5a4708508bacf5c282e86cb1daef14a6d6cd1 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 12 Oct 2016 12:26:09 +0500 Subject: [PATCH 033/111] 5 4 spring data jpa --- .../datajpa/CrudMealRepository.java | 11 ++++ .../datajpa/DataJpaMealRepositoryImpl.java | 45 ++++++++++++++++ src/main/resources/spring/spring-db.xml | 51 +++++++++++++------ 3 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java new file mode 100644 index 000000000000..e32c99aef1e0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -0,0 +1,11 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.javawebinar.topjava.model.Meal; + +/** + * gkislin + * 02.10.2016 + */ +public interface CrudMealRepository extends JpaRepository { +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java new file mode 100644 index 000000000000..4fb669ec517d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * GKislin + * 27.03.2015. + */ +@Repository +public class DataJpaMealRepositoryImpl implements MealRepository { + + @Autowired + private CrudMealRepository crudRepository; + + @Override + public Meal save(Meal Meal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public Meal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index ea1fdf6dd3b4..c3b5bab77a15 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -4,32 +4,23 @@ 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"> - + - - - - + + - - - - - - - - @@ -66,4 +57,34 @@ --> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 709dd059a7750207047b6ec32c2bbc138b7f8205 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 12 Oct 2016 12:26:28 +0500 Subject: [PATCH 034/111] 5 5 fix hibernate issue --- .../javawebinar/topjava/model/BaseEntity.java | 26 +++++++---- .../ru/javawebinar/topjava/model/User.java | 2 +- .../datajpa/CrudUserRepository.java | 36 +++++++++++++++ .../datajpa/DataJpaUserRepositoryImpl.java | 45 +++++++++++++++++++ 4 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 007e260dcb68..abf94f644fca 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,19 +1,30 @@ package ru.javawebinar.topjava.model; +import org.hibernate.Hibernate; +import org.springframework.data.domain.Persistable; + import javax.persistence.*; /** * User: gkislin * Date: 22.08.2014 + *

    + * Do not manipulate new (transient) entries in HashSet/HashMap without overriding hashCode + * http://stackoverflow.com/a/39827962/548473 + * + * @see org.springframework.data.jpa.domain.AbstractPersistable */ @MappedSuperclass +// http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) -public class BaseEntity { +public class BaseEntity implements Persistable { public static final int START_SEQ = 100000; @Id @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") + // PROPERTY access for id due to bug: https://hibernate.atlassian.net/browse/HHH-3718 + @Access(value = AccessType.PROPERTY) protected Integer id; public BaseEntity() { @@ -27,10 +38,12 @@ public void setId(Integer id) { this.id = id; } + @Override public Integer getId() { return id; } + @Override public boolean isNew() { return (this.id == null); } @@ -40,19 +53,16 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !getClass().equals(Hibernate.getClass(o))) { return false; } BaseEntity that = (BaseEntity) o; - if (id == null || that.id == null) { - return false; - } - return id.equals(that.id); + + return null != getId() && getId().equals(that.getId()); } @Override public int hashCode() { - return (id == null) ? 0 : id; + return (getId() == null) ? 0 : getId(); } - } diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index c1df84abc3f1..ea5dafe271a1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -48,7 +48,7 @@ public class User extends NamedEntity { @Enumerated(EnumType.STRING) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") - @ElementCollection(fetch = FetchType.EAGER) + @ElementCollection(fetch = FetchType.LAZY) private Set roles; @Column(name = "calories_per_day", columnDefinition = "default 2000") diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java new file mode 100644 index 000000000000..845a777909ad --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -0,0 +1,36 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +/** + * gkislin + * 02.10.2016 + */ +@Transactional(readOnly = true) +public interface CrudUserRepository extends JpaRepository { + @Transactional + @Modifying + @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 + @Query("SELECT u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email") + List findAll(); + + User getByEmail(String email); + +} 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..a714d97a1b22 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -0,0 +1,45 @@ +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.repository.UserRepository; + +import java.util.List; + +/** + * GKislin + * 27.03.2015. + */ + +@Repository +public class DataJpaUserRepositoryImpl implements UserRepository { + + @Autowired + private CrudUserRepository crudRepository; + + @Override + public User save(User user) { + return crudRepository.save(user); + } + + @Override + public boolean delete(int id) { + return crudRepository.delete(id) != 0; + } + + @Override + public User get(int id) { + return crudRepository.findOne(id); + } + + @Override + public User getByEmail(String email) { + return crudRepository.getByEmail(email); + } + + @Override + public List getAll() { + return crudRepository.findAll(); + } +} From 31f36f92d8353297bb65096b9d7dcfdbf07153b4 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 12 Oct 2016 12:26:49 +0500 Subject: [PATCH 035/111] 5 6 spring cache --- pom.xml | 70 ++- .../topjava/service/UserService.java | 2 + .../topjava/service/UserServiceImpl.java | 11 + 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-tools.xml | 24 + .../topjava/service/UserServiceTest.java | 9 + 8 files changed, 547 insertions(+), 17 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 9e9c473efdcc..572877b2ca6c 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,9 @@ UTF-8 UTF-8 + 8.0.33 4.3.2.RELEASE + 1.10.4.RELEASE 1.1.7 @@ -30,6 +32,9 @@ 5.2.2.Final 5.2.4.Final + + + 2.10.2.2.21 @@ -82,7 +87,7 @@ org.springframework - spring-context + spring-context-support ${spring.version} @@ -92,24 +97,11 @@ - org.springframework - spring-orm - ${spring.version} + org.springframework.data + spring-data-jpa + ${spring-data-jpa.version} - - - org.postgresql - postgresql - ${postgresql.version} - - - org.hsqldb - hsqldb - 2.3.4 - - - org.hibernate hibernate-core @@ -126,6 +118,13 @@ hibernate-validator ${hibernate-validator.version} + + + net.sf.ehcache + ehcache + ${ehcache.version} + + javax.el javax.el-api @@ -162,8 +161,45 @@ + + hsqldb + + + org.hsqldb + hsqldb + 2.3.4 + + + + + postgres + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + + + + true + + + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 85c15e48f951..080b94b93c93 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -23,4 +23,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 e31832d2af06..03838efa0a9f 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,6 +1,8 @@ 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 org.springframework.util.Assert; import ru.javawebinar.topjava.model.User; @@ -20,12 +22,14 @@ public class UserServiceImpl implements UserService { @Autowired private UserRepository repository; + @CacheEvict(value = "users", allEntries = true) @Override public User save(User user) { Assert.notNull(user, "user must not be null"); return repository.save(user); } + @CacheEvict(value = "users", allEntries = true) @Override public void delete(int id) { ExceptionUtil.checkNotFoundWithId(repository.delete(id), id); @@ -42,14 +46,21 @@ public User getByEmail(String email) throws NotFoundException { return ExceptionUtil.checkNotFound(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) { Assert.notNull(user, "user must not be null"); 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-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 c175aae677e3..7e4a3de81323 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -1,13 +1,16 @@ 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.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.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -24,11 +27,17 @@ }) @RunWith(SpringJUnit4ClassRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(Profiles.ACTIVE_DB) public class UserServiceTest { @Autowired protected UserService service; + @Before + public void setUp() throws Exception { + service.evictCache(); + } + @Test public void testSave() throws Exception { User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); From c9529fa6761a712bb7f9f33c6ffca0f618c29ae4 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 18 Oct 2016 14:16:38 +0500 Subject: [PATCH 036/111] 6 0 fix --- src/main/java/ru/javawebinar/topjava/model/Meal.java | 2 +- .../topjava/repository/jpa/JpaMealRepositoryImpl.java | 8 ++------ .../repository/mock/InMemoryUserRepositoryImpl.java | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 45ddfc27a6b0..0ac5adbabf08 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -13,8 +13,8 @@ * GKislin * 11.01.2015. */ +@SuppressWarnings("JpaQlInspection") @NamedQueries({ - @NamedQuery(name = Meal.GET, query = "SELECT m FROM Meal m WHERE m.id=:id AND m.user.id=:userId"), @NamedQuery(name = Meal.ALL_SORTED, query = "SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC"), @NamedQuery(name = Meal.DELETE, query = "DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId"), @NamedQuery(name = Meal.GET_BETWEEN, query = "SELECT m FROM Meal m " + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java index 2a4d4858a582..200a2c018745 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -1,6 +1,5 @@ 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.Meal; @@ -50,11 +49,8 @@ public boolean delete(int id, int userId) { @Override public Meal get(int id, int userId) { - List meals = em.createNamedQuery(Meal.GET, Meal.class) - .setParameter("id", id) - .setParameter("userId", userId) - .getResultList(); - return DataAccessUtils.singleResult(meals); + Meal meal = em.find(Meal.class, id); + return meal != null && meal.getUser().getId() == userId ? meal : null; } @Override 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 99d13a6b52a0..77125080c281 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -26,7 +26,7 @@ public class InMemoryUserRepositoryImpl implements UserRepository { private Map repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); - private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName); + private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName).thenComparing(User::getEmail); @Override public User save(User user) { From 527f8bf5398c1a436d91b47b40cab286032b9c0c Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 18 Oct 2016 14:17:38 +0500 Subject: [PATCH 037/111] 6 01 HW5 data jpa --- src/main/resources/logback.xml | 3 +++ src/test/resources/logback-test.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index e9b900b26669..0c368f53e409 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -22,6 +22,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 d53c56bffd7fa2cb1ed0b8979ecc6bbed5426131 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 18 Oct 2016 14:18:03 +0500 Subject: [PATCH 038/111] 6 02 HW5 profiles --- .../java/ru/javawebinar/topjava/Profiles.java | 4 +- src/main/resources/spring/spring-db.xml | 84 ++++++++++--------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index 9826ef551b6a..4ce31d6f690a 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -9,7 +9,9 @@ public class Profiles { POSTGRES = "postgres", HSQLDB = "hsqldb", JDBC = "jdbc", - JPA = "jpa"; + JPA = "jpa", + DATAJPA = "datajpa"; public static final String ACTIVE_DB = POSTGRES; + public static final String DB_IMPLEMENTATION = DATAJPA; } diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index c3b5bab77a15..1e78287feb20 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -11,52 +11,12 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -82,9 +42,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 85ce2f6c069837d6e798ecc8772cc17c25acda61 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 18 Oct 2016 14:18:22 +0500 Subject: [PATCH 039/111] 6 03 HW5 tests --- ...Test.java => AbstractMealServiceTest.java} | 0 .../topjava/service/AbstractServiceTest.java | 61 +++++++++++++++++++ ...Test.java => AbstractUserServiceTest.java} | 16 +---- .../service/jdbc/JdbcMealServiceTest.java | 10 +++ .../service/jdbc/JdbcUserServiceTest.java | 10 +++ .../service/jpa/JpaMealServiceTest.java | 10 +++ .../service/jpa/JpaUserServiceTest.java | 10 +++ 7 files changed, 102 insertions(+), 15 deletions(-) rename src/test/java/ru/javawebinar/topjava/service/{MealServiceTest.java => AbstractMealServiceTest.java} (100%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java rename src/test/java/ru/javawebinar/topjava/service/{UserServiceTest.java => AbstractUserServiceTest.java} (78%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java similarity index 100% rename from src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java 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..f7a25ed77a59 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -0,0 +1,61 @@ +package ru.javawebinar.topjava.service; + +import org.junit.AfterClass; +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.ACTIVE_DB; + +/** + * User: gkislin + */ +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +@ActiveProfiles(ACTIVE_DB) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +abstract public class AbstractServiceTest { + private static final Logger LOG = LoggerFactory.getLogger(AbstractServiceTest.class); + + private static StringBuilder results = new StringBuilder(); + + @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() { + + @Override + protected void finished(long nanos, Description description) { + String result = String.format("%-25s %7d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result).append('\n'); + LOG.info(result + " ms\n"); + } + }; + + @AfterClass + public static void printResults() { + results = new StringBuilder("\n---------------------------------") + .append("\nTest Duration, ms") + .append("\n---------------------------------\n") + .append(results) + .append("---------------------------------\n"); + LOG.info(results.toString()); + results.setLength(0); + } +} \ 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/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 7e4a3de81323..faed42c3fe73 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.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -21,14 +14,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.ACTIVE_DB) -public class UserServiceTest { +public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired protected UserService service; diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java new file mode 100644 index 000000000000..9ff4ae615b2b --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jdbc; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JDBC; + +@ActiveProfiles(JDBC) +public class JdbcMealServiceTest extends AbstractMealServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java new file mode 100644 index 000000000000..419f68ed1098 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jdbc; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.JDBC; + +@ActiveProfiles(JDBC) +public class JdbcUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java new file mode 100644 index 000000000000..70e7bf865421 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JPA; + +@ActiveProfiles(JPA) +public class JpaMealServiceTest extends AbstractMealServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java new file mode 100644 index 000000000000..d1b3e4699785 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.JPA; + +@ActiveProfiles(JPA) +public class JpaUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file From 7593db50fcbe71fd0dd740847b074c772b63a11b Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 18 Oct 2016 14:18:39 +0500 Subject: [PATCH 040/111] 6 04 HW5 optional fix jdbc profiles --- .../jdbc/JdbcMealRepositoryImpl.java | 33 ++++++++++++++++--- .../javawebinar/topjava/web/MealServlet.java | 10 ++++-- .../ru/javawebinar/topjava/SpringMain.java | 9 +++-- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index f531dc875ef4..d2a5b3605bc7 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.repository.jdbc; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; @@ -9,10 +10,12 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import javax.sql.DataSource; +import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.List; @@ -21,8 +24,7 @@ * Date: 26.08.2014 */ -@Repository -public class JdbcMealRepositoryImpl implements MealRepository { +public abstract class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); @@ -34,20 +36,41 @@ public class JdbcMealRepositoryImpl implements MealRepository { private SimpleJdbcInsert insertMeal; + protected abstract T toDbDateTime(LocalDateTime ldt); + @Autowired - public JdbcMealRepositoryImpl(DataSource dataSource) { + private void setDataSource(DataSource dataSource) { this.insertMeal = new SimpleJdbcInsert(dataSource) .withTableName("meals") .usingGeneratedKeyColumns("id"); } + @Repository + @Profile(Profiles.POSTGRES) + public static class Java8JdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { + @Override + protected LocalDateTime toDbDateTime(LocalDateTime ldt) { + return ldt; + } + } + + @Repository + @Profile(Profiles.HSQLDB) + public static class TimestampJdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { + + @Override + protected Timestamp toDbDateTime(LocalDateTime ldt) { + return Timestamp.valueOf(ldt); + } + } + @Override public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) .addValue("description", meal.getDescription()) .addValue("calories", meal.getCalories()) - .addValue("date_time", meal.getDateTime()) + .addValue("date_time", toDbDateTime(meal.getDateTime())) .addValue("user_id", userId); if (meal.isNew()) { @@ -87,6 +110,6 @@ public List getAll(int userId) { 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); + ROW_MAPPER, userId, toDbDateTime(startDate), toDbDateTime(endDate)); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 77f6f81f2b27..349ed933268a 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.Meal; import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.web.meal.MealRestController; @@ -27,13 +27,17 @@ public class MealServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - private ConfigurableApplicationContext springContext; + private ClassPathXmlApplicationContext springContext; private MealRestController 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.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.DB_IMPLEMENTATION); +// ClassPathXmlApplicationContext.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); +// GenericXmlApplicationContext.load("spring/spring-app.xml", "spring/mock.xml"); + springContext.refresh(); mealController = springContext.getBean(MealRestController.class); } diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index d9ce7e6274ac..a2fa2de2d9ce 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.to.MealWithExceed; import ru.javawebinar.topjava.web.meal.MealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; @@ -19,7 +18,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("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(UserTestData.USER); From 7bb0ab7328d4749bb98646ce576f4b532757e534 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 18 Oct 2016 14:18:55 +0500 Subject: [PATCH 041/111] 6 05 HW5 optional fetch join --- .../ru/javawebinar/topjava/model/User.java | 12 +++++++-- .../topjava/repository/MealRepository.java | 4 +++ .../topjava/repository/UserRepository.java | 4 +++ .../datajpa/CrudMealRepository.java | 26 ++++++++++++++++++ .../datajpa/CrudUserRepository.java | 2 ++ .../datajpa/DataJpaMealRepositoryImpl.java | 27 ++++++++++++++----- .../datajpa/DataJpaUserRepositoryImpl.java | 5 ++++ .../topjava/service/MealService.java | 2 ++ .../topjava/service/MealServiceImpl.java | 12 +++++++++ .../topjava/service/UserService.java | 4 ++- .../topjava/service/UserServiceImpl.java | 5 ++++ .../datajpa/DataJpaMealServiceTest.java | 27 +++++++++++++++++++ .../datajpa/DataJpaUserServiceTest.java | 26 ++++++++++++++++++ 13 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index ea5dafe271a1..c5f628263880 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,15 +1,15 @@ package ru.javawebinar.topjava.model; -import ru.javawebinar.topjava.util.MealsUtil; - import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; +import ru.javawebinar.topjava.util.MealsUtil; import javax.persistence.*; import javax.validation.constraints.Digits; import java.util.Date; import java.util.EnumSet; +import java.util.List; import java.util.Set; /** @@ -55,6 +55,10 @@ public class User extends NamedEntity { @Digits(fraction = 0, integer = 4) private int caloriesPerDay = MealsUtil.DEFAULT_CALORIES_PER_DAY; + @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "user") + @OrderBy("dateTime DESC") + protected List meals; + public User() { } @@ -119,6 +123,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/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index a274d756f88f..1645a2f9ae49 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -24,4 +24,8 @@ public interface MealRepository { // ORDERED dateTime Collection getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); + + default Meal getWithUser(int id, int userId) { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java index 24c1c45beca6..47ded4cec6b8 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -21,4 +21,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/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java index e32c99aef1e0..530a81086f0a 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -1,11 +1,37 @@ package ru.javawebinar.topjava.repository.datajpa; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; +import java.time.LocalDateTime; +import java.util.List; + /** * gkislin * 02.10.2016 */ +@Transactional(readOnly = true) public interface CrudMealRepository extends JpaRepository { + + @Modifying + @Transactional + @Query("DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId") + int delete(@Param("id") int id, @Param("userId") int userId); + + @Override + Meal save(Meal item); + + @Query("SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC") + List getAll(@Param("userId") int userId); + + @SuppressWarnings("JpaQlInspection") + @Query("SELECT m from Meal 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); + + @Query("SELECT m FROM Meal m JOIN FETCH m.user WHERE m.id = ?1 and m.user.id = ?2") + Meal getWithUser(int id, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java index 845a777909ad..a308b92c9e46 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -33,4 +33,6 @@ public interface CrudUserRepository extends JpaRepository { User getByEmail(String email); + @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") + User getWithMeals(int id); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java index 4fb669ec517d..d3a6c2c1bd56 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -18,28 +19,42 @@ public class DataJpaMealRepositoryImpl implements MealRepository { @Autowired private CrudMealRepository crudRepository; + @Autowired + private CrudUserRepository crudUserRepository; + @Override - public Meal save(Meal Meal, int userId) { - return null; + @Transactional + public Meal save(Meal meal, int userId) { + if (!meal.isNew() && get(meal.getId(), userId) == null) { + return null; + } + meal.setUser(crudUserRepository.getOne(userId)); + return crudRepository.save(meal); } @Override public boolean delete(int id, int userId) { - return false; + return crudRepository.delete(id, userId) != 0; } @Override public Meal get(int id, int userId) { - return null; + Meal meal = crudRepository.findOne(id); + return meal != null && meal.getUser().getId() == userId ? meal : null; } @Override public List getAll(int userId) { - return null; + return crudRepository.getAll(userId); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return crudRepository.getBetween(startDate, endDate, userId); + } + + @Override + public Meal getWithUser(int id, int userId) { + return crudRepository.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 a714d97a1b22..18e127fed47a 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -42,4 +42,9 @@ public User getByEmail(String email) { public List getAll() { return crudRepository.findAll(); } + + @Override + public User getWithMeals(int id) { + return crudRepository.getWithMeals(id); + } } diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index 36ff1dea0619..1dbdd716033d 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -28,4 +28,6 @@ default Collection getBetweenDates(LocalDate startDate, LocalDate endDate, Meal update(Meal meal, int userId) throws NotFoundException; Meal save(Meal meal, int userId); + + Meal getWithUser(int id, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 72207b808fae..97a465234702 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -53,4 +53,16 @@ public Meal save(Meal meal, int userId) { Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } + +// @Autowired +// private UserRepository userRepository; + +// @Transactional + public Meal getWithUser(int id, int userId) { +// Meal meal = get(id, userId); +// meal.setUser(userRepository.get(userId)); +// return meal; + return ExceptionUtil.checkNotFoundWithId(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 080b94b93c93..aa06ef3607f6 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -23,6 +23,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 03838efa0a9f..e0ee4b3deed4 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -63,4 +63,9 @@ public void update(User user) { @Override public void evictCache() { } + + @Override + public User getWithMeals(int id) { + return ExceptionUtil.checkNotFoundWithId(repository.getWithMeals(id), id); + } } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java new file mode 100644 index 000000000000..cfb91aeff0b0 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -0,0 +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.Meal; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; + +@ActiveProfiles(DATAJPA) +public class DataJpaMealServiceTest extends AbstractMealServiceTest { + @Test + public void testGetWithUser() throws Exception { + Meal adminMeal = service.getWithUser(ADMIN_MEAL_ID, ADMIN_ID); + MATCHER.assertEquals(ADMIN_MEAL1, adminMeal); + UserTestData.MATCHER.assertEquals(UserTestData.ADMIN, adminMeal.getUser()); + } + + @Test(expected = NotFoundException.class) + 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 new file mode 100644 index 000000000000..999479ec822e --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.junit.Test; +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.MEALS, user.getMeals()); + } + + @Test(expected = NotFoundException.class) + public void testGetWithMealsNotFound() throws Exception { + service.getWithMeals(1); + } +} \ No newline at end of file From be1aedce8f95b6e5a395ab0206b15b73c9a8a340 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 18 Oct 2016 14:41:54 +0500 Subject: [PATCH 042/111] 6 05 HW5 optional fetch join --- .../service/AbstractMealServiceTest.java | 49 +------------------ 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index a51d112b5175..67edd3e0f157 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,71 +1,26 @@ package ru.javawebinar.topjava.service; -import org.junit.AfterClass; -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.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.model.Meal; 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; 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")) -public class MealServiceTest { - private static final Logger LOG = LoggerFactory.getLogger(MealServiceTest.class); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private static StringBuffer results = new StringBuffer(); - - @AfterClass - public static void printResult() { - System.out.printf("%nTest Duration, ms%n"); - System.out.println("-----------------------------"); - System.out.println(results); - System.out.printf("-----------------------------%n%n"); - } - - @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() { - @Override - protected void finished(long nanos, Description description) { - String result = String.format("%-20s %8d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); - results.append(result).append('\n'); - LOG.info(result + " ms\n"); - } - }; +public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired protected MealService service; @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)); } From cae2c9abd10046a077173de12c7da3ea8d684de9 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 19 Oct 2016 16:15:58 +0500 Subject: [PATCH 043/111] 6 06 hibernate cache --- .../ru/javawebinar/topjava/model/User.java | 4 +++ .../topjava/repository/JpaUtil.java | 25 +++++++++++++++++++ src/main/resources/cache/ehcache.xml | 3 +++ .../service/AbstractUserServiceTest.java | 5 ++++ 4 files changed, 37 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index c5f628263880..21a81dd48e69 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +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; @@ -21,6 +23,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 { @@ -49,6 +52,7 @@ public class User extends NamedEntity { @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") @ElementCollection(fetch = FetchType.LAZY) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private 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/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index faed42c3fe73..ddbec46bbee6 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -6,6 +6,7 @@ import org.springframework.dao.DataAccessException; 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 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired protected UserService service; + @Autowired + protected JpaUtil jpaUtil; + @Before public void setUp() throws Exception { service.evictCache(); + jpaUtil.clear2ndLevelHibernateCache(); } @Test From 905eaa35133d8ab1cfe59b3eb42a40d573d1dde1 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 19 Oct 2016 16:17:14 +0500 Subject: [PATCH 044/111] 6 07 spring web --- .../ru/javawebinar/topjava/web/MealServlet.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 349ed933268a..482da30a0815 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.Meal; import ru.javawebinar.topjava.util.TimeUtil; import ru.javawebinar.topjava.web.meal.MealRestController; @@ -27,26 +27,15 @@ public class MealServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - private ClassPathXmlApplicationContext springContext; private MealRestController 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.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.DB_IMPLEMENTATION); -// ClassPathXmlApplicationContext.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); -// GenericXmlApplicationContext.load("spring/spring-app.xml", "spring/mock.xml"); - springContext.refresh(); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); mealController = springContext.getBean(MealRestController.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"); From d6dbd2d236f7ed48565f995f3040ead6097cfe4e Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 19 Oct 2016 16:18:04 +0500 Subject: [PATCH 045/111] 6 08 jsp jstl i18n --- .../javawebinar/topjava/web/UserServlet.java | 16 +++++++++++++++- src/main/webapp/index.html | 19 ------------------- 2 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 src/main/webapp/index.html diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 9e98b822ef47..5c803dd0f3f9 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.AuthorizedUser; +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; @@ -18,6 +22,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")); AuthorizedUser.setId(userId); @@ -25,7 +38,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - LOG.debug("forward to users"); + LOG.debug("getAll"); + request.setAttribute("users", adminController.getAll()); request.getRequestDispatcher("/users.jsp").forward(request, response); } } diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html deleted file mode 100644 index 7478d296d331..000000000000 --- a/src/main/webapp/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Java Enterprise (Topjava) - - -

    Проект "Java Enterprise (Topjava)"

    -
    -
    - Meal list of  - - -
    - - From 34c348da2e0d7043e6a153fba7dde2ba311c5b12 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 19 Oct 2016 16:18:16 +0500 Subject: [PATCH 046/111] 6 08 jsp jstl i18n --- src/main/webapp/users.jsp | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/main/webapp/users.jsp diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp deleted file mode 100644 index fbd40d3d7219..000000000000 --- a/src/main/webapp/users.jsp +++ /dev/null @@ -1,10 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - - - User list - - -

    Home

    -

    User list

    - - From a518c94e150cbdb12ba8b04a164553b6b76b5485 Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 19 Oct 2016 16:18:53 +0500 Subject: [PATCH 047/111] 6 09 tomcat pool jndi --- src/main/resources/db/tomcat.properties | 7 ++++ src/main/resources/spring/spring-db.xml | 17 +++++++- src/main/resources/tomcat/context.xml | 53 +++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/tomcat.properties create mode 100644 src/main/resources/tomcat/context.xml 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 1e78287feb20..c792ac7262ca 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"> @@ -41,6 +43,11 @@ p:password="${database.password}"/> + + + + + @@ -64,6 +71,12 @@ + + + + + + @@ -79,6 +92,8 @@ + + diff --git a/src/main/resources/tomcat/context.xml b/src/main/resources/tomcat/context.xml new file mode 100644 index 000000000000..4ec4a8d5893e --- /dev/null +++ b/src/main/resources/tomcat/context.xml @@ -0,0 +1,53 @@ + + + + + + + + WEB-INF/web.xml + ${catalina.base}/conf/web.xml + + + + + + + + + From 4e6cd4543701eb0bb6783ee8df8dbd1ccab81acb Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 19 Oct 2016 16:19:53 +0500 Subject: [PATCH 048/111] 6 10 spring webmvc --- pom.xml | 88 ++++++++++++++++++- .../topjava/web/RootController.java | 39 ++++++++ src/main/resources/spring/spring-app.xml | 3 - .../{ => WEB-INF/jsp/WEB-INF/jsp}/meal.jsp | 0 .../{ => WEB-INF/jsp/WEB-INF/jsp}/meals.jsp | 2 +- src/main/webapp/WEB-INF/web.xml | 33 +++++++ .../{ => resources/resources}/css/style.css | 8 ++ 7 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/RootController.java rename src/main/webapp/{ => WEB-INF/jsp/WEB-INF/jsp}/meal.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp/WEB-INF/jsp}/meals.jsp (96%) rename src/main/webapp/{ => resources/resources}/css/style.css (65%) diff --git a/pom.xml b/pom.xml index 572877b2ca6c..ab3050151409 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,70 @@ -Dfile.encoding=UTF-8 + + + + org.codehaus.cargo + cargo-maven2-plugin + 1.5.0 + + + tomcat8x + + UTF-8 + tomcat,datajpa + + + + org.postgresql + postgresql + + + + + + + src/main/resources/tomcat/context.xml + conf/Catalina/localhost/ + context.xml.default + + + + + + ru.javawebinar + topjava + war + + ${project.build.finalName} + + + + + + + @@ -102,6 +166,18 @@ ${spring-data-jpa.version} + + org.springframework + spring-webmvc + ${spring.version} + + + commons-logging + commons-logging + + + + org.hibernate hibernate-core @@ -118,6 +194,11 @@ hibernate-validator ${hibernate-validator.version} + + org.hibernate + hibernate-ehcache + ${hibernate.version} + net.sf.ehcache @@ -129,13 +210,14 @@ javax.el javax.el-api 2.2.5 + provided - javax.servlet - javax.servlet-api - 3.1.0 + org.apache.tomcat + tomcat-servlet-api + ${tomcat.version} provided 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..f987016aba04 --- /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.AuthorizedUser; +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 users(Model model) { + model.addAttribute("users", service.getAll()); + return "users"; + } + + @RequestMapping(value = "/users", method = RequestMethod.POST) + public String setUser(HttpServletRequest request) { + int userId = Integer.valueOf(request.getParameter("userId")); + AuthorizedUser.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/webapp/meal.jsp b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meal.jsp similarity index 100% rename from src/main/webapp/meal.jsp rename to src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meal.jsp diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meals.jsp similarity index 96% rename from src/main/webapp/meals.jsp rename to src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meals.jsp index 63bd33c4c4a0..16fad23617d6 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meals.jsp @@ -9,7 +9,7 @@
    -

    Home

    +

    Home

    Meal list

    diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 686b79f85e6f..46061022703a 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -5,6 +5,38 @@ version="3.1"> Topjava + + spring.profiles.default + postgres,datajpa + + + + contextConfigLocation + + classpath:spring/spring-app.xml + classpath:spring/spring-db.xml + + + + + + 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/resources/css/style.css similarity index 65% rename from src/main/webapp/css/style.css rename to src/main/webapp/resources/resources/css/style.css index 0c9fc667371c..d4807fb38efb 100644 --- a/src/main/webapp/css/style.css +++ b/src/main/webapp/resources/resources/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; +} From b52c1d1fcccf11a50241a7f4591cf49ba522532f Mon Sep 17 00:00:00 2001 From: afarida Date: Wed, 19 Oct 2016 16:21:28 +0500 Subject: [PATCH 049/111] 6 11 spring i18n --- config/app.properties | 10 +++++ config/messages/app_ru.properties | 10 +++++ src/main/resources/spring/spring-mvc.xml | 36 +++++++++++++++++ .../jsp/WEB-INF/jsp/fragments/bodyHeader.jsp | 4 ++ .../jsp/WEB-INF/jsp/fragments/footer.jsp | 4 ++ .../jsp/WEB-INF/jsp/fragments/headTag.jsp | 9 +++++ .../webapp/WEB-INF/jsp/WEB-INF/jsp/index.jsp | 24 ++++++++++++ .../webapp/WEB-INF/jsp/WEB-INF/jsp/users.jsp | 39 +++++++++++++++++++ 8 files changed, 136 insertions(+) create mode 100644 config/app.properties create mode 100644 config/messages/app_ru.properties create mode 100644 src/main/resources/spring/spring-mvc.xml create mode 100644 src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/bodyHeader.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/footer.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/headTag.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/index.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/users.jsp diff --git a/config/app.properties b/config/app.properties new file mode 100644 index 000000000000..512a5a11585a --- /dev/null +++ b/config/app.properties @@ -0,0 +1,10 @@ +app.title=Calories management +app.home=Home +app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application +app.login=Login as + +users.title=User list + +meals.title=Meals list + +common.select=Select diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties new file mode 100644 index 000000000000..17df8ebcf8fa --- /dev/null +++ b/config/messages/app_ru.properties @@ -0,0 +1,10 @@ +app.title=Подсчет калорий +app.home=Главная +app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery +app.login=Зайти как + +users.title=Список пользователей + +meals.title=Список еды + +common.select=Выбрать \ 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..12ac233daa15 --- /dev/null +++ b/src/main/resources/spring/spring-mvc.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/bodyHeader.jsp new file mode 100644 index 000000000000..833a84d0b2a4 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -0,0 +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/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/footer.jsp new file mode 100644 index 000000000000..997a1677296c --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/footer.jsp @@ -0,0 +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/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/headTag.jsp new file mode 100644 index 000000000000..4f44c6dee4d5 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/headTag.jsp @@ -0,0 +1,9 @@ +<%@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"/> + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/index.jsp new file mode 100644 index 000000000000..8eefc3c1623a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/index.jsp @@ -0,0 +1,24 @@ +<%@ 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/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/users.jsp new file mode 100644 index 000000000000..824c78a6919c --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/users.jsp @@ -0,0 +1,39 @@ +<%@ 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" %> + + + + + + +
    +

    +

    + + + + + + + + + + + + + + + + + + + + + +
    NameEmailRolesActiveRegistered
    ${user.email}${user.roles}<%=user.isEnabled()%> +
    +
    + + + From 2005b06c87e220b0a8be4ed9efb55c31e5f7f3db Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 20 Oct 2016 14:22:30 +0500 Subject: [PATCH 050/111] 6 11 spring i18n --- config/{ => messages}/app.properties | 0 config/messages/app_ru.properties | 2 +- .../WEB-INF/jsp/{WEB-INF/jsp => }/fragments/bodyHeader.jsp | 0 .../webapp/WEB-INF/jsp/{WEB-INF/jsp => }/fragments/footer.jsp | 0 .../webapp/WEB-INF/jsp/{WEB-INF/jsp => }/fragments/headTag.jsp | 0 src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/index.jsp | 0 src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/meal.jsp | 0 src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/meals.jsp | 0 src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/users.jsp | 0 src/main/webapp/resources/{resources => }/css/style.css | 0 10 files changed, 1 insertion(+), 1 deletion(-) rename config/{ => messages}/app.properties (100%) rename src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/fragments/bodyHeader.jsp (100%) rename src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/fragments/footer.jsp (100%) rename src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/fragments/headTag.jsp (100%) rename src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/index.jsp (100%) rename src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/meal.jsp (100%) rename src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/meals.jsp (100%) rename src/main/webapp/WEB-INF/jsp/{WEB-INF/jsp => }/users.jsp (100%) rename src/main/webapp/resources/{resources => }/css/style.css (100%) diff --git a/config/app.properties b/config/messages/app.properties similarity index 100% rename from config/app.properties rename to config/messages/app.properties diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 17df8ebcf8fa..5407904f408c 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -1,6 +1,6 @@ app.title=Подсчет калорий app.home=Главная -app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery +app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery app.login=Зайти как users.title=Список пользователей diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/bodyHeader.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/footer.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/footer.jsp diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/fragments/headTag.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/index.jsp rename to src/main/webapp/WEB-INF/jsp/index.jsp diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meal.jsp b/src/main/webapp/WEB-INF/jsp/meal.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meal.jsp rename to src/main/webapp/WEB-INF/jsp/meal.jsp diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/meals.jsp rename to src/main/webapp/WEB-INF/jsp/meals.jsp diff --git a/src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp similarity index 100% rename from src/main/webapp/WEB-INF/jsp/WEB-INF/jsp/users.jsp rename to src/main/webapp/WEB-INF/jsp/users.jsp diff --git a/src/main/webapp/resources/resources/css/style.css b/src/main/webapp/resources/css/style.css similarity index 100% rename from src/main/webapp/resources/resources/css/style.css rename to src/main/webapp/resources/css/style.css From 7837c594617e38b34a114cc7b647a646664be5a2 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 24 Oct 2016 13:45:14 +0500 Subject: [PATCH 051/111] 7 01 HW6 fix tests --- .../ru/javawebinar/topjava/SpringMain.java | 2 +- .../service/AbstractJpaUserServiceTest.java | 21 +++++++++++++++++++ .../datajpa/DataJpaUserServiceTest.java | 4 ++-- .../service/jpa/JpaUserServiceTest.java | 4 ++-- ...InMemoryAdminRestControllerSpringTest.java | 2 +- .../web/InMemoryAdminRestControllerTest.java | 6 ++++-- 6 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index a2fa2de2d9ce..c55eea7570b3 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -20,7 +20,7 @@ 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("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); 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/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index 999479ec822e..160ea9572d92 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -4,14 +4,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 a9125c6e4d96..ca416455051c 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -22,7 +22,7 @@ * GKislin * 13.03.2015. */ -@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 f7006200f781..69466f713308 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -21,14 +21,16 @@ 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"); System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); controller = appCtx.getBean(AdminRestController.class); } @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(); } @Before From f03110cdfc5087aa1d55d4285250f85f82984597 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 24 Oct 2016 13:47:19 +0500 Subject: [PATCH 052/111] 7 02 HW6 jsp i18n --- config/messages/app.properties | 19 ++++++++++++++++ config/messages/app_ru.properties | 21 +++++++++++++++++- .../topjava/web/RootController.java | 16 ++++++++++++-- .../WEB-INF/jsp/fragments/bodyHeader.jsp | 2 +- src/main/webapp/WEB-INF/jsp/meal.jsp | 22 +++++++++---------- src/main/webapp/WEB-INF/jsp/users.jsp | 11 +++++----- 6 files changed, 69 insertions(+), 22 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index 512a5a11585a..0bae88156cb8 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -4,7 +4,26 @@ app.footer=Project <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -
    +
      
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/meal.jsp b/src/main/webapp/WEB-INF/jsp/meal.jsp index 7c32430b1857..35ad3198efdf 100644 --- a/src/main/webapp/WEB-INF/jsp/meal.jsp +++ b/src/main/webapp/WEB-INF/jsp/meal.jsp @@ -1,34 +1,32 @@ <%@ 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

    -

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

    +


    -
    DateTime:
    +
    :
    -
    Description:
    +
    :
    -
    Calories:
    +
    :
    - - + +
    + diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 824c78a6919c..d2c721583b06 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -8,17 +8,16 @@
    -

    - - - - - + + + + + From 6db8484f0161aa98c8f207c5759727defa6476eb Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 24 Oct 2016 13:48:51 +0500 Subject: [PATCH 053/111] 7 03 HW6 meals --- .../javawebinar/topjava/web/MealServlet.java | 100 ------------------ .../javawebinar/topjava/web/UserServlet.java | 45 -------- .../web/meal/AbstractMealController.java | 68 ++++++++++++ .../topjava/web/meal/MealRestController.java | 63 +---------- src/main/webapp/WEB-INF/jsp/meals.jsp | 35 +++--- 5 files changed, 86 insertions(+), 225 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/AbstractMealController.java 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 482da30a0815..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ /dev/null @@ -1,100 +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.Meal; -import ru.javawebinar.topjava.util.TimeUtil; -import ru.javawebinar.topjava.web.meal.MealRestController; - -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.time.temporal.ChronoUnit; -import java.util.Objects; - -/** - * User: gkislin - * Date: 19.08.2014 - */ -public class MealServlet extends HttpServlet { - private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); - - private MealRestController mealController; - - @Override - public void init(ServletConfig config) throws ServletException { - super.init(config); - WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - mealController = springContext.getBean(MealRestController.class); - } - - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - request.setCharacterEncoding("UTF-8"); - String action = request.getParameter("action"); - if (action == null) { - final Meal meal = new Meal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.valueOf(request.getParameter("calories"))); - - if (request.getParameter("id").isEmpty()) { - LOG.info("Create {}", meal); - mealController.create(meal); - } else { - LOG.info("Update {}", meal); - mealController.update(meal, getId(request)); - } - response.sendRedirect("meals"); - - } else if ("filter".equals(action)) { - 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("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); - request.getRequestDispatcher("/meals.jsp").forward(request, response); - } - } - - private String resetParam(String param, HttpServletRequest request) { - String value = request.getParameter(param); - request.setAttribute(param, value); - return value; - } - - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String action = request.getParameter("action"); - - if (action == null) { - LOG.info("getAll"); - request.setAttribute("meals", mealController.getAll()); - request.getRequestDispatcher("/meals.jsp").forward(request, response); - - } else if ("delete".equals(action)) { - int id = getId(request); - LOG.info("Delete {}", id); - mealController.delete(id); - response.sendRedirect("meals"); - - } else if ("create".equals(action) || "update".equals(action)) { - final Meal meal = "create".equals(action) ? - new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000) : - mealController.get(getId(request)); - request.setAttribute("meal", meal); - request.getRequestDispatcher("meal.jsp").forward(request, response); - } - } - - 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/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java deleted file mode 100644 index 5c803dd0f3f9..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ /dev/null @@ -1,45 +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.AuthorizedUser; -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; - -/** - * User: gkislin - * Date: 19.08.2014 - */ -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")); - AuthorizedUser.setId(userId); - response.sendRedirect("meals"); - } - - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - LOG.debug("getAll"); - request.setAttribute("users", adminController.getAll()); - request.getRequestDispatcher("/users.jsp").forward(request, response); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java new file mode 100644 index 000000000000..8d8d4d0f2edf --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java @@ -0,0 +1,68 @@ +package ru.javawebinar.topjava.web.meal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import ru.javawebinar.topjava.AuthorizedUser; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.to.MealWithExceed; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.TimeUtil; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +/** + * GKislin + * 06.03.2015. + */ +public abstract class AbstractMealController { + private static final Logger LOG = LoggerFactory.getLogger(AbstractMealController.class); + + @Autowired + private MealService service; + + public Meal get(int id) { + int userId = AuthorizedUser.id(); + LOG.info("get meal {} for User {}", id, userId); + return service.get(id, userId); + } + + public void delete(int id) { + int userId = AuthorizedUser.id(); + LOG.info("delete meal {} for User {}", id, userId); + service.delete(id, userId); + } + + public List getAll() { + int userId = AuthorizedUser.id(); + LOG.info("getAll for User {}", userId); + return MealsUtil.getWithExceeded(service.getAll(userId), AuthorizedUser.getCaloriesPerDay()); + } + + public void update(Meal meal, int id) { + meal.setId(id); + int userId = AuthorizedUser.id(); + LOG.info("update {} for User {}", meal, userId); + service.update(meal, userId); + } + + public Meal create(Meal meal) { + meal.setId(null); + int userId = AuthorizedUser.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 = AuthorizedUser.id(); + LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId); + return MealsUtil.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, AuthorizedUser.getCaloriesPerDay() + ); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index d928b5a6c0ff..188431479bf7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,72 +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.AuthorizedUser; -import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.service.MealService; -import ru.javawebinar.topjava.to.MealWithExceed; -import ru.javawebinar.topjava.util.MealsUtil; -import ru.javawebinar.topjava.util.TimeUtil; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; /** * GKislin * 06.03.2015. */ @Controller -public class MealRestController { - private static final Logger LOG = LoggerFactory.getLogger(MealRestController.class); - - @Autowired - private MealService service; - - public Meal get(int id) { - int userId = AuthorizedUser.id(); - LOG.info("get meal {} for User {}", id, userId); - return service.get(id, userId); - } - - public void delete(int id) { - int userId = AuthorizedUser.id(); - LOG.info("delete meal {} for User {}", id, userId); - service.delete(id, userId); - } - - public List getAll() { - int userId = AuthorizedUser.id(); - LOG.info("getAll for User {}", userId); - return MealsUtil.getWithExceeded(service.getAll(userId), AuthorizedUser.getCaloriesPerDay()); - } - - public void update(Meal meal, int id) { - meal.setId(id); - int userId = AuthorizedUser.id(); - LOG.info("update {} for User {}", meal, userId); - service.update(meal, userId); - } - - public Meal create(Meal meal) { - meal.setId(null); - int userId = AuthorizedUser.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 = AuthorizedUser.id(); - LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId); - - return MealsUtil.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, - AuthorizedUser.getCaloriesPerDay() - ); - } +public class MealRestController extends AbstractMealController { } diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 16fad23617d6..1efcfcf48c64 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -3,42 +3,40 @@ <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> - - Meal list - - + +
    -

    Home

    -

    Meal list

    -
    +

    + +
    -
    From Date:
    +
    :
    -
    To Date:
    +
    :
    -
    From Time:
    +
    :
    -
    To Time:
    +
    :
    - +
    - Add Meal +
    NameEmailRolesActiveRegistered
    - - - + + + @@ -53,11 +51,12 @@ - - + +
    DateDescriptionCalories
    ${meal.description} ${meal.calories}UpdateDelete
    + From 524edb734c23d7030090fa09b4f1759a82113ab7 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 24 Oct 2016 13:49:08 +0500 Subject: [PATCH 054/111] 7 04 HW6 fix relative url utf8 --- .../topjava/web/meal/JspMealController.java | 79 +++++++++++++++++++ .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 5 +- src/main/webapp/WEB-INF/web.xml | 37 ++++----- 3 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java new file mode 100644 index 000000000000..b1ed9d7a8423 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -0,0 +1,79 @@ +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.Meal; +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.time.temporal.ChronoUnit; +import java.util.Objects; + +/** + * gkislin + * 16.10.2016 + */ +@Controller +@RequestMapping(value = "/meals") +public class JspMealController extends AbstractMealController { + + @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 update(HttpServletRequest request, Model model) { + model.addAttribute("meal", super.get(getId(request))); + return "meal"; + } + + @RequestMapping(value = "/create", method = RequestMethod.GET) + public String create(Model model) { + model.addAttribute("meal", new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000)); + return "meal"; + } + + @RequestMapping(method = RequestMethod.POST) + public String updateOrCreate(HttpServletRequest request) { + String id = request.getParameter("id"); + Meal userMeal = new Meal(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("meals", super.getBetween(startDate, startTime, endDate, endTime)); + return "meals"; + } + + 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/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 4f44c6dee4d5..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} + - \ 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 46061022703a..6e1280c56d07 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 + /* + From 83f713ae787c5861489340f308b5741ae6de3053 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 24 Oct 2016 13:49:25 +0500 Subject: [PATCH 055/111] 7 05 HW6 optional add role --- .../ru/javawebinar/topjava/model/User.java | 20 ++++++++++++------- .../datajpa/CrudUserRepository.java | 7 +++++-- src/main/resources/db/populateDB.sql | 3 ++- .../ru/javawebinar/topjava/UserTestData.java | 4 ++-- .../service/AbstractUserServiceTest.java | 14 +++++-------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 21a81dd48e69..ed951605eb89 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Email; @@ -9,10 +10,7 @@ import javax.persistence.*; import javax.validation.constraints.Digits; -import java.util.Date; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * User: gkislin @@ -20,14 +18,17 @@ */ @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.BY_EMAIL, query = "SELECT u FROM User u WHERE u.email=?1"), + @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u ORDER BY u.name, u.email"), }) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Entity +@NamedEntityGraph(name = User.GRAPH_WITH_MEALS, attributeNodes = {@NamedAttributeNode("meals")}) @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) public class User extends NamedEntity { + public static final String GRAPH_WITH_MEALS = "User.withMeals"; + public static final String DELETE = "User.delete"; public static final String ALL_SORTED = "User.getAllSorted"; public static final String BY_EMAIL = "User.getByEmail"; @@ -51,7 +52,8 @@ public class User extends NamedEntity { @Enumerated(EnumType.STRING) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") - @ElementCollection(fetch = FetchType.LAZY) + @ElementCollection(fetch = FetchType.EAGER) + @BatchSize(size = 200) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private Set roles; @@ -123,6 +125,10 @@ public Set getRoles() { return roles; } + public void setRoles(Collection roles) { + this.roles = EnumSet.copyOf(roles); + } + public String getPassword() { return password; } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java index a308b92c9e46..d310c388cbc7 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.repository.datajpa; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -28,11 +29,13 @@ public interface CrudUserRepository extends JpaRepository { User findOne(Integer id); @Override - @Query("SELECT u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email") + @Query("SELECT u FROM User u ORDER BY u.name, u.email") List findAll(); + @Query("SELECT u FROM User u WHERE u.email=?1") User getByEmail(String email); - @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") + @EntityGraph(value = User.GRAPH_WITH_MEALS) + @Query("SELECT u FROM User u WHERE u.id=?1") User getWithMeals(int id); } 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/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index f7ce7774b6b4..193701cea95c 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -17,7 +17,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<>( (expected, actual) -> expected == actual || @@ -27,7 +27,7 @@ public class UserTestData { && Objects.equals(expected.getEmail(), actual.getEmail()) && Objects.equals(expected.getCaloriesPerDay(), actual.getCaloriesPerDay()) && Objects.equals(expected.isEnabled(), actual.isEnabled()) -// && Objects.equals(expected.getRoles(), actual.getRoles()) + && Objects.equals(expected.getRoles(), actual.getRoles()) ) ); } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index ddbec46bbee6..ac78104f6645 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -6,7 +6,6 @@ import org.springframework.dao.DataAccessException; 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 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired protected UserService service; - @Autowired - protected JpaUtil jpaUtil; - @Before public void setUp() throws Exception { service.evictCache(); - jpaUtil.clear2ndLevelHibernateCache(); } @Test @@ -55,8 +50,8 @@ public void testNotFoundDelete() throws Exception { @Test public void testGet() throws Exception { - User user = service.get(USER_ID); - MATCHER.assertEquals(USER, user); + User user = service.get(ADMIN_ID); + MATCHER.assertEquals(ADMIN, user); } @Test(expected = NotFoundException.class) @@ -66,8 +61,8 @@ public void testGetNotFound() throws Exception { @Test public void testGetByEmail() throws Exception { - User user = service.getByEmail("user@yandex.ru"); - MATCHER.assertEquals(USER, user); + User user = service.getByEmail("admin@gmail.com"); + MATCHER.assertEquals(ADMIN, user); } @Test @@ -81,6 +76,7 @@ public void testUpdate() throws Exception { User updated = new User(USER); updated.setName("UpdatedName"); updated.setCaloriesPerDay(330); + updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); service.update(updated); MATCHER.assertEquals(updated, service.get(USER_ID)); } From 5b560f0a3a0a078144ef83e5ee5598b2717e91d5 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 24 Oct 2016 13:49:43 +0500 Subject: [PATCH 056/111] 7 06 HW6 optional jdbc --- .../jdbc/JdbcMealRepositoryImpl.java | 4 + .../jdbc/JdbcUserRepositoryImpl.java | 81 ++++++++++++++++++- src/main/resources/spring/spring-db.xml | 9 ++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index d2a5b3605bc7..ddb69c0df063 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -10,6 +10,7 @@ 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.Profiles; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -24,6 +25,7 @@ * Date: 26.08.2014 */ +@Transactional(readOnly = true) public abstract class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); @@ -65,6 +67,7 @@ protected Timestamp toDbDateTime(LocalDateTime ldt) { } @Override + @Transactional public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) @@ -89,6 +92,7 @@ public Meal save(Meal meal, 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 d5471553b575..300574997076 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -2,17 +2,27 @@ 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; /** * User: gkislin @@ -20,9 +30,11 @@ */ @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; @@ -40,6 +52,7 @@ public JdbcUserRepositoryImpl(DataSource dataSource) { } @Override + @Transactional public User save(User user) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", user.getId()) @@ -53,7 +66,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); @@ -62,6 +78,7 @@ public User save(User user) { } @Override + @Transactional public boolean delete(int id) { return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0; } @@ -69,17 +86,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) { List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); - return DataAccessUtils.singleResult(users); + return setRoles(DataAccessUtils.singleResult(users)); } @Override public List getAll() { - return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + class UserRole { + final private int userId; + final private Role role; + + private UserRole(int userId, Role role) { + this.userId = userId; + this.role = role; + } + + public int getUserId() { + return userId; + } + + public Role getRole() { + return role; + } + } + + Map> userRolesMap = jdbcTemplate.query("SELECT role, user_id FROM user_roles", + (rs, rowNum) -> new UserRole(rs.getInt("user_id"), Role.valueOf(rs.getString("role")))).stream() + .collect( + Collectors.groupingBy(UserRole::getUserId, Collectors.mapping(UserRole::getRole, Collectors.toList())) + ); + + List users = jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + 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/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index c792ac7262ca..3ebede5d1310 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -20,6 +20,8 @@ + + @@ -57,6 +59,11 @@ + + + + @@ -87,8 +94,6 @@ - - From f860230782d71035c25a2750ca05a563ed156a8d Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:18:27 +0500 Subject: [PATCH 057/111] 7 07 generate ddl --- src/main/resources/spring/spring-db.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 3ebede5d1310..62d2a5c62cae 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -84,6 +84,9 @@ + + + From 331217f1450f6197f69385eeb450cbf11f4804d2 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:20:20 +0500 Subject: [PATCH 058/111] 7 08 controller test --- pom.xml | 14 ++++ src/main/resources/spring/spring-db.xml | 2 + .../topjava/web/AbstractControllerTest.java | 64 +++++++++++++++++++ .../topjava/web/user/RootControllerTest.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/RootControllerTest.java diff --git a/pom.xml b/pom.xml index ab3050151409..0e77a5eb9f88 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 9.4.1211 4.12 + 1.3 5.2.2.Final @@ -233,7 +234,20 @@ junit ${junit.version} test + + + hamcrest-core + org.hamcrest + + + + org.hamcrest + hamcrest-all + ${hamcrest.version} + test + + org.springframework spring-test diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 62d2a5c62cae..62e5d7a65f0c 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..fd5691cfa9b4 --- /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.ACTIVE_DB; +import static ru.javawebinar.topjava.Profiles.DB_IMPLEMENTATION; + +/** + * 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({ACTIVE_DB, DB_IMPLEMENTATION}) +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/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java new file mode 100644 index 000000000000..744d37ebc35f --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.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 RootControllerTest extends AbstractControllerTest { + + @Test + public void testUsers() throws Exception { + mockMvc.perform(get("/users")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(view().name("users")) + .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) + .andExpect(model().attribute("users", hasSize(2))) + .andExpect(model().attribute("users", hasItem( + allOf( + hasProperty("id", is(START_SEQ)), + hasProperty("name", is(USER.getName())) + ) + ))); + } +} \ No newline at end of file From e3092fd7aabba80a9e37f1a3519ec47146d0a452 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:20:56 +0500 Subject: [PATCH 059/111] 7 09 rest controller --- .../topjava/web/user/AdminRestController.java | 37 +++++++++++++++---- .../web/user/ProfileRestController.java | 12 ++++-- 2 files changed, 38 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 d6b8bb420803..710d4de047f3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,38 +1,59 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +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; /** * GKislin * 06.03.2015. */ -@Controller +@RestController +@RequestMapping(AdminRestController.REST_URL) public class AdminRestController extends AbstractUserController { + static final String REST_URL = "/rest/admin/users"; + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { return super.getAll(); } - public User get(int id) { + @GetMapping(value = "/{id}", 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); + @PostMapping(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 ResponseEntity.created(uriOfNewResource).body(created); } - public void delete(int id) { + @DeleteMapping(value = "/{id}") + public void delete(@PathVariable("id") int id) { super.delete(id); } - public void update(User user, int id) { + @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody User user, @PathVariable("id") int id) { super.update(user, id); } - public User getByMail(String email) { + @GetMapping(value = "/by", 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 443db9c53263..1078e256780b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; @@ -8,18 +9,23 @@ * GKislin * 06.03.2015. */ -@Controller +@RestController +@RequestMapping(ProfileRestController.REST_URL) public class ProfileRestController extends AbstractUserController { + static final String REST_URL = "/rest/profile"; + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public User get() { return super.get(AuthorizedUser.id()); } + @DeleteMapping public void delete() { super.delete(AuthorizedUser.id()); } - public void update(User user) { + @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody User user) { super.update(user, AuthorizedUser.id()); } } \ No newline at end of file From 6af8c4cf090aea18533642ddcadc02b2b59a9528 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:22:45 +0500 Subject: [PATCH 060/111] 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 | 25 +++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index 0e77a5eb9f88..3cc6042cce24 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 8.0.33 4.3.2.RELEASE 1.10.4.RELEASE + 2.8.4 1.1.7 @@ -228,6 +229,13 @@ 1.2 + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-json.version} + + junit diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index abf94f644fca..83f7a4a228d0 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,10 +1,14 @@ package ru.javawebinar.topjava.model; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import org.hibernate.Hibernate; import org.springframework.data.domain.Persistable; import javax.persistence.*; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + /** * User: gkislin * Date: 22.08.2014 @@ -17,6 +21,7 @@ @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public class BaseEntity implements Persistable { 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 ed951605eb89..7e37491510dd 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -7,6 +7,7 @@ import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; import ru.javawebinar.topjava.util.MealsUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; import javax.persistence.*; import javax.validation.constraints.Digits; @@ -63,6 +64,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..cf1baf2a399d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -0,0 +1,25 @@ +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()) +// https://jira.spring.io/browse/SPR-14472 + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)); + } +} \ No newline at end of file From f715bdc3fc32f58acc9c80acf96ee0f1962798a9 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:24:43 +0500 Subject: [PATCH 061/111] 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 | 42 +++++++++++++++++++ .../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, 180 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 3cc6042cce24..fb55e359ddfd 100644 --- a/pom.xml +++ b/pom.xml @@ -235,6 +235,11 @@ jackson-databind ${jackson-json.version} + + com.fasterxml.jackson.datatype + jackson-datatype-hibernate5 + ${jackson-json.version} + diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 83f7a4a228d0..ae243a6a6eb8 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,14 +1,10 @@ package ru.javawebinar.topjava.model; -import com.fasterxml.jackson.annotation.JsonAutoDetect; import org.hibernate.Hibernate; import org.springframework.data.domain.Persistable; import javax.persistence.*; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; - /** * User: gkislin * Date: 22.08.2014 @@ -21,7 +17,7 @@ @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) -@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) +//@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public class BaseEntity implements Persistable { 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 7e37491510dd..87e8de7c915e 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -7,7 +7,6 @@ import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; import ru.javawebinar.topjava.util.MealsUtil; -import com.fasterxml.jackson.annotation.JsonIgnore; import javax.persistence.*; import javax.validation.constraints.Digits; @@ -64,7 +63,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 d3d2b8bd1210..1d861de93c2e 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -35,4 +35,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..69b45078bbea --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -0,0 +1,42 @@ +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.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; + +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 Hibernate5Module()); + + 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..9015e61c08f2 --- /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.Meal; + +import java.util.List; + +/** + * GKislin + * 22.07.2015. + */ +public class JsonUtilTest { + + @Test + public void testReadWriteValue() throws Exception { + String json = JsonUtil.writeValue(MealTestData.ADMIN_MEAL1); + System.out.println(json); + Meal userMeal = JsonUtil.readValue(json, Meal.class); + MealTestData.MATCHER.assertEquals(MealTestData.ADMIN_MEAL1, userMeal); + } + + @Test + public void testReadWriteValues() throws Exception { + String json = JsonUtil.writeValue(MealTestData.MEALS); + System.out.println(json); + List userMeals = JsonUtil.readValues(json, Meal.class); + MealTestData.MATCHER.assertCollectionEquals(MealTestData.MEALS, userMeals); + } +} \ No newline at end of file From accc8a2e5e04afbf131850541c2c73def2bf978a Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:25:14 +0500 Subject: [PATCH 062/111] 7 12 jackson jsr310 --- pom.xml | 5 +++ .../topjava/web/json/JacksonObjectMapper.java | 11 ++--- .../web/json/JsonLocalDateTimeConverter.java | 42 ------------------- 3 files changed, 9 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java diff --git a/pom.xml b/pom.xml index fb55e359ddfd..ea3e7df1f39e 100644 --- a/pom.xml +++ b/pom.xml @@ -240,6 +240,11 @@ jackson-datatype-hibernate5 ${jackson-json.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-json.version} + 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 69b45078bbea..cdd7fcdd4d3b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -4,10 +4,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; - -import java.time.LocalDateTime; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; /** * User: gkislin @@ -30,10 +29,8 @@ public static ObjectMapper getMapper() { private JacksonObjectMapper() { registerModule(new Hibernate5Module()); - 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); 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 073bdb9f4528c6257fc0318cb2762c3f44eab107 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:26:06 +0500 Subject: [PATCH 063/111] 7 13 test with matcher --- .../ru/javawebinar/topjava/model/User.java | 5 +- .../ru/javawebinar/topjava/MealTestData.java | 2 +- .../java/ru/javawebinar/topjava/TestUtil.java | 21 ++++++ .../ru/javawebinar/topjava/UserTestData.java | 2 +- .../topjava/matcher/ModelMatcher.java | 56 ++++++++++++++- .../topjava/matcher/TestMatcher.java | 29 ++++++++ .../topjava/web/AbstractControllerTest.java | 5 ++ .../web/user/AdminRestControllerTest.java | 68 +++++++++++++++++-- .../web/user/ProfileRestControllerTest.java | 47 +++++++++++++ 9 files changed, 224 insertions(+), 11 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/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 87e8de7c915e..cbd1e7518cbd 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -6,6 +6,7 @@ import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; +import org.springframework.util.CollectionUtils; import ru.javawebinar.topjava.util.MealsUtil; import javax.persistence.*; @@ -83,7 +84,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; + setRoles(roles); } public String getEmail() { @@ -127,7 +128,7 @@ public Set getRoles() { } public void setRoles(Collection roles) { - this.roles = EnumSet.copyOf(roles); + this.roles = CollectionUtils.isEmpty(roles) ? Collections.emptySet() : EnumSet.copyOf(roles); } public String getPassword() { diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index c5c224f3db60..b0991b4b62e2 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -16,7 +16,7 @@ */ public class MealTestData { - public static final ModelMatcher MATCHER = new ModelMatcher<>(); + public static final ModelMatcher MATCHER = new ModelMatcher<>(Meal.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..059a771ccbdb --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -0,0 +1,21 @@ +package ru.javawebinar.topjava; + +import org.springframework.test.web.servlet.ResultActions; + +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(); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 193701cea95c..42c90944f334 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -19,7 +19,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<>( + public static final ModelMatcher MATCHER = new ModelMatcher<>(User.class, (expected, actual) -> expected == actual || (Objects.equals(expected.getPassword(), actual.getPassword()) && Objects.equals(expected.getId(), actual.getId()) diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index 393e28076542..85db621a2934 100644 --- a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -1,11 +1,19 @@ 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.stream.Collectors; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; + /** * GKislin * 06.01.2015. @@ -20,16 +28,18 @@ public class ModelMatcher { (Object expected, Object actual) -> expected == actual || String.valueOf(expected).equals(String.valueOf(actual)); private Comparator comparator; + private Class entityClass; public interface Comparator { boolean compare(T expected, T actual); } - public ModelMatcher() { - this((Comparator) DEFAULT_COMPARATOR); + public ModelMatcher(Class entityClass) { + this(entityClass, (Comparator) DEFAULT_COMPARATOR); } - public ModelMatcher(Comparator comparator) { + public ModelMatcher(Class entityClass, Comparator comparator) { + this.entityClass = entityClass; this.comparator = comparator; } @@ -54,6 +64,18 @@ public String toString() { } } + private T fromJsonValue(String json) { + return JsonUtil.readValue(json, entityClass); + } + + private Collection fromJsonValues(String json) { + return JsonUtil.readValues(json, entityClass); + } + + public T fromJsonAction(ResultActions action) throws UnsupportedEncodingException { + return fromJsonValue(TestUtil.getContent(action)); + } + public void assertEquals(T expected, T actual) { Assert.assertEquals(wrap(expected), wrap(actual)); } @@ -69,4 +91,32 @@ public Wrapper wrap(T entity) { public List wrap(Collection collection) { return collection.stream().map(this::wrap).collect(Collectors.toList()); } + + public ResultMatcher contentMatcher(T expect) { + return content().string( + new TestMatcher(expect) { + @Override + protected boolean compare(T expected, String body) { + Wrapper expectedForCompare = wrap(expected); + Wrapper actualForCompare = wrap(fromJsonValue(body)); + 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 expectedList = wrap(expected); + List actualList = wrap(fromJsonValues(actual)); + return expectedList.equals(actualList); + } + }); + } } 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/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index fd5691cfa9b4..a9615f4daaae 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; +import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.service.UserService; import javax.annotation.PostConstruct; @@ -41,6 +42,9 @@ abstract public class AbstractControllerTest { CHARACTER_ENCODING_FILTER.setForceEncoding(true); } + @Autowired + private JpaUtil jpaUtil; + protected MockMvc mockMvc; @Autowired @@ -60,5 +64,6 @@ void postConstruct() { @Before public void setUp() { userService.evictCache(); + jpaUtil.clear2ndLevelHibernateCache(); } } 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 cf1baf2a399d..8b4bc1b49b4d 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -2,17 +2,25 @@ 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 { - public static final String REST_URL = AdminRestController.REST_URL + '/'; + private static final String REST_URL = AdminRestController.REST_URL + '/'; @Test public void testGet() throws Exception { @@ -20,6 +28,58 @@ public void testGet() throws Exception { .andExpect(status().isOk()) .andDo(print()) // https://jira.spring.io/browse/SPR-14472 - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)); + .andExpect(content().contentTypeCompatibleWith(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().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER.contentMatcher(USER)); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + USER_ID)) + .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 { + User expected = new User(null, "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))).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)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(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..830de49dafaa --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.Test; +import org.springframework.http.MediaType; +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().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER.contentMatcher(USER))); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL)) + .andExpect(status().isOk()); + MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); + } + + @Test + public void testUpdate() throws Exception { + User updated = new User(USER_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@ya.ru"))); + } +} \ No newline at end of file From 57ebb0dd7f27b2a5735f450bf2727aa2fa76fc95 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 25 Oct 2016 13:26:36 +0500 Subject: [PATCH 064/111] 7 14 soapui utf8 converter --- config/Topjava-soapui-project.xml | 17 +++++++++++ .../web/user/ProfileRestController.java | 5 ++++ src/main/resources/spring/spring-mvc.xml | 30 ++++++++++++------- 3 files changed, 41 insertions(+), 11 deletions(-) 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 1078e256780b..2a8ac9d9037a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -28,4 +28,9 @@ public void delete() { public void update(@RequestBody User user) { super.update(user, AuthorizedUser.id()); } + + @GetMapping(value = "/text") + 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..890848966fb0 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 + + + @@ -24,18 +32,18 @@ p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> - + + + messages/app + + + + --> From 4cba587149762f18f5e3d51b54ffc56fce2f748e Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 10:53:23 +0500 Subject: [PATCH 065/111] 8 0 refactoring fix --- .../topjava/web/meal/JspMealController.java | 27 ++++++++----------- .../WEB-INF/jsp/fragments/bodyHeader.jsp | 2 +- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 3 +-- src/main/webapp/WEB-INF/jsp/meals.jsp | 8 +++--- .../ru/javawebinar/topjava/UserTestData.java | 2 +- .../topjava/matcher/ModelMatcher.java | 8 ++++++ .../topjava/web/AbstractControllerTest.java | 2 +- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java index b1ed9d7a8423..38737fe41e2c 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -2,8 +2,9 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.TimeUtil; @@ -22,25 +23,25 @@ @RequestMapping(value = "/meals") public class JspMealController extends AbstractMealController { - @RequestMapping(value = "/delete", method = RequestMethod.GET) + @GetMapping("/delete") public String delete(HttpServletRequest request) { super.delete(getId(request)); return "redirect:/meals"; } - @RequestMapping(value = "/update", method = RequestMethod.GET) + @GetMapping("/update") public String update(HttpServletRequest request, Model model) { model.addAttribute("meal", super.get(getId(request))); return "meal"; } - @RequestMapping(value = "/create", method = RequestMethod.GET) + @GetMapping("/create") public String create(Model model) { model.addAttribute("meal", new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000)); return "meal"; } - @RequestMapping(method = RequestMethod.POST) + @PostMapping public String updateOrCreate(HttpServletRequest request) { String id = request.getParameter("id"); Meal userMeal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), @@ -56,22 +57,16 @@ public String updateOrCreate(HttpServletRequest request) { return "redirect:/meals"; } - @RequestMapping(value = "/filter", method = RequestMethod.POST) + @PostMapping("/filter") 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)); + LocalDate startDate = TimeUtil.parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = TimeUtil.parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = TimeUtil.parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = TimeUtil.parseLocalTime(request.getParameter("endTime")); model.addAttribute("meals", super.getBetween(startDate, startTime, endDate, endTime)); return "meals"; } - 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/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index f872830dd3ef..27b777137b0c 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,4 +1,4 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -

      
    \ No newline at end of file +
      
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 90f86d23e672..74236e0b683b 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -6,7 +6,6 @@ <fmt:message key="app.title"/> - ${pageContext.request.requestURL} - + diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 1efcfcf48c64..806c831660aa 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -12,19 +12,19 @@
    :
    -
    +
    :
    -
    +
    :
    -
    +
    :
    -
    +
    diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 42c90944f334..71349e3deeb4 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -19,7 +19,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<>(User.class, + public static final ModelMatcher MATCHER = ModelMatcher.of(User.class, (expected, actual) -> expected == actual || (Objects.equals(expected.getPassword(), actual.getPassword()) && Objects.equals(expected.getId(), actual.getId()) diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index 85db621a2934..bdbba1285040 100644 --- a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -34,6 +34,14 @@ public interface Comparator { boolean compare(T expected, T actual); } + public static ModelMatcher of(Class entityClass) { + return new ModelMatcher<>(entityClass); + } + + public static ModelMatcher of(Class entityClass, Comparator comparator) { + return new ModelMatcher<>(entityClass, comparator); + } + public ModelMatcher(Class entityClass) { this(entityClass, (Comparator) DEFAULT_COMPARATOR); } diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index a9615f4daaae..a20e2668456f 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -54,7 +54,7 @@ abstract public class AbstractControllerTest { private WebApplicationContext webApplicationContext; @PostConstruct - void postConstruct() { + private void postConstruct() { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .addFilter(CHARACTER_ENCODING_FILTER) From cf98782a0c5f2d4620a7b54b7b199a71e0012126 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 10:54:27 +0500 Subject: [PATCH 066/111] 8 01 HW07 controller test --- src/main/resources/logback.xml | 2 ++ .../topjava/web/ResourceControllerTest.java | 24 +++++++++++++++++++ .../web/{user => }/RootControllerTest.java | 22 +++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java rename src/test/java/ru/javawebinar/topjava/web/{user => }/RootControllerTest.java (59%) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 0c368f53e409..911b469f7aad 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -20,6 +20,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/user/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java similarity index 59% rename from src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java rename to src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index 744d37ebc35f..184a71bd7e50 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,12 +1,13 @@ -package ru.javawebinar.topjava.web.user; +package ru.javawebinar.topjava.web; 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; import static ru.javawebinar.topjava.UserTestData.USER; import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; @@ -31,4 +32,21 @@ public void testUsers() throws Exception { ) ))); } + + @Test + public void testMeals() throws Exception { + mockMvc.perform(get("/meals")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(view().name("meals")) + .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")) + .andExpect(model().attribute("meals", hasSize(6))) + .andExpect(model().attribute("meals", hasItem( + allOf( + hasProperty("id", is(MEAL1_ID)), + hasProperty("description", is(MEAL1.getDescription())) + ) + ))); + } + } \ No newline at end of file From f56378525d4b638efb8eb37256a89ff4e8eb7729 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 10:54:41 +0500 Subject: [PATCH 067/111] 8 02 HW07 rest controller --- .../java/ru/javawebinar/topjava/to/MealWithExceed.java | 8 +++++++- src/test/java/ru/javawebinar/topjava/MealTestData.java | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java index 53795a28d214..f94ba51fe7cd 100644 --- a/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.to; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.time.LocalDateTime; /** @@ -17,7 +19,11 @@ public class MealWithExceed { private final boolean exceed; - public MealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { + public MealWithExceed(@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/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index b0991b4b62e2..335c9d79343d 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -2,6 +2,7 @@ import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.to.MealWithExceed; import java.time.Month; import java.util.Arrays; @@ -16,7 +17,8 @@ */ public class MealTestData { - public static final ModelMatcher MATCHER = new ModelMatcher<>(Meal.class); + public static final ModelMatcher MATCHER = ModelMatcher.of(Meal.class); + public static final ModelMatcher MATCHER_WITH_EXCEED = ModelMatcher.of(MealWithExceed.class); public static final int MEAL1_ID = START_SEQ + 2; public static final int ADMIN_MEAL_ID = START_SEQ + 8; From 6bf600b365c70006441332aa4f112a04af7c6827 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 10:54:56 +0500 Subject: [PATCH 068/111] 8 03 HW07 formatters --- .../util/converter/DateTimeFormatters.java | 42 +++++++ .../topjava/web/meal/MealRestController.java | 69 +++++++++-- src/main/resources/spring/spring-mvc.xml | 11 +- .../web/meal/MealRestControllerTest.java | 112 ++++++++++++++++++ 4 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/converter/DateTimeFormatters.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/util/converter/DateTimeFormatters.java b/src/main/java/ru/javawebinar/topjava/util/converter/DateTimeFormatters.java new file mode 100644 index 000000000000..e152d505c414 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/converter/DateTimeFormatters.java @@ -0,0 +1,42 @@ +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.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +/** + * gkislin + * 25.10.2016 + */ +public class DateTimeFormatters { + public static 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); + } + } + + public static 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/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index 188431479bf7..19f8146fb825 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,11 +1,66 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.stereotype.Controller; +import org.springframework.format.annotation.DateTimeFormat; +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.Meal; +import ru.javawebinar.topjava.to.MealWithExceed; -/** - * GKislin - * 06.03.2015. - */ -@Controller +import java.net.URI; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@RestController +@RequestMapping(value = MealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) public class MealRestController extends AbstractMealController { -} + static final String REST_URL = "/rest/profile/meals"; + + @GetMapping("/{id}") + public Meal get(@PathVariable("id") int id) { + return super.get(id); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @GetMapping + public List getAll() { + return super.getAll(); + } + + @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody Meal meal, @PathVariable("id") int id) { + super.update(meal, id); + } + + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity createWithLocation(@RequestBody Meal meal) { + Meal created = super.create(meal); + + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(REST_URL + "/{id}") + .buildAndExpand(created.getId()).toUri(); + + return ResponseEntity.created(uriOfNewResource).body(created); + } + + @GetMapping(value = "/between") + 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()); + } + + @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, startTime, endDate, endTime); + } +} \ 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 890848966fb0..30b437dad767 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/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java new file mode 100644 index 000000000000..5c81110d2ca5 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -0,0 +1,112 @@ +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.model.Meal; +import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.util.MealsUtil; +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.UserTestData.USER; +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; + +public class MealRestControllerTest extends AbstractControllerTest { + + private static final String REST_URL = MealRestController.REST_URL + '/'; + + @Autowired + private MealService service; + + @Test + public void testGet() throws Exception { + mockMvc.perform(get(REST_URL + MEAL1_ID)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentTypeCompatibleWith(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 { + Meal 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 { + Meal created = getCreated(); + ResultActions action = mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(created))); + + Meal 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().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher(MealsUtil.getWithExceeded(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")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( + MealsUtil.createWithExceed(MEAL4, true), + MealsUtil.createWithExceed(MEAL1, false))); + } + + @Test + public void testFilter() throws Exception { + mockMvc.perform(get(REST_URL + "filter") + .param("startDate", "2015-05-30").param("startTime", "07:00") + .param("endDate", "2015-05-31").param("endTime", "11:00")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( + MealsUtil.createWithExceed(MEAL4, true), + MealsUtil.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( + MealsUtil.getWithExceeded(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), USER.getCaloriesPerDay()))); + } +} \ No newline at end of file From f29ec048c093259f3f7a6054c919907bbddc0ba3 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 10:55:14 +0500 Subject: [PATCH 069/111] 8 04 HW07 soapui curl --- config/Topjava-soapui-project.xml | 624 +++++++++++++++++++++++++++++- config/curl.md | 23 ++ 2 files changed, 643 insertions(+), 4 deletions(-) create mode 100644 config/curl.md diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml index 44ad8436a5cc..6ead35a225a6 100644 --- a/config/Topjava-soapui-project.xml +++ b/config/Topjava-soapui-project.xml @@ -1,17 +1,633 @@ -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{ + + + + + + + http://localhost:8080 + + + + + + + + + text/html;charset=utf-8 + 500 401 + + html + + + application/json + 200 + + ns:Response + + + + 0 + + data + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/admin/users + + user@yandex.ru + password + No Authorization + Basic + No Authorization + + + + + + + + + + + application/json + + + + text/html;charset=utf-8 + 500 + + html + + + application/json + 201 + + user:Response + + + application/json;charset=UTF-8 + 201 + + user: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{ + } + + http://localhost/topjava/rest/admin/users + + No Authorization + Basic + No Authorization + + + + + + + + + + + + + + + text/html;charset=utf-8 + 500 + + html + + + application/json + 200 + + ns:Response + + + application/json;charset=UTF-8 + 200 + + ns:Response + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/admin/users/100000 + + No Authorization + Basic + No Authorization + + + + + + + + + + + text/html;charset=utf-8 + 405 500 + + html + + + application/json + + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + <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{ + } + + http://localhost/topjava/rest/admin/users/100000 + + Basic + Basic + Global HTTP Settings + + + + + + + + + + + + + + + text/html;charset=utf-8 + 500 + + html + + + application/json + 200 + + ns:Response + + + application/json;charset=UTF-8 + 200 + + prof:Response + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/profile + + Basic + Basic + Global HTTP Settings + + + + + + + + + + + application/json + + + + text/html;charset=utf-8 + 500 405 + + html + + + application/json + 201 + + user:Response + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + <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/topjava/rest/profile + + No Authorization + Basic + No Authorization + + + + + + + + + + + + 200 + + data + + + text/html;charset=utf-8 + 500 + + html + + + application/json + + + + + 200 + + data + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/profile + + No Authorization + Basic + No Authorization + + + + + + + + + + + + + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals + + No Authorization + + + + + + + + + + + text/html;charset=utf-8 + 400 + + html + + + application/json + + + + + 0 + + data + + + application/json;charset=UTF-8 + 201 + + meal:Response + + + + <xml-fragment/> + + + http://localhost:8080 + { + "dateTime": "2015-06-01 10:00", + "description": "Новый завтрак", + "calories": 777 + } + + http://localhost/topjava/rest/profile/meals + + No Authorization + + + + + + + + + + + + + + + application/json;charset=UTF-8 + 200 + + ns:Response + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals/100002 + + No Authorization + + + + + + + + + + + text/html;charset=utf-8 + 400 + + html + + + application/json + + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + <xml-fragment/> + + + http://localhost:8080 + { + "id": 100002, + "dateTime": "2015-05-30 10:00", + "description": "Обновленный завтрак", + "calories": 500 + } + + http://localhost/topjava/rest/profile/meals/100002 + + No Authorization + + + + + + + + + + + + startDateTime + + QUERY + + + + + endDateTime + + QUERY + + + + + + + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals/between + + No Authorization + + + + + + + + + startDateTime + endDateTime + + + + + + + + + startDate + + QUERY + + + + endTimeQUERY + + + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals/filter + + No Authorization + + + + + + + + + startDateendTime + + + + + + + + + \ No newline at end of file diff --git a/config/curl.md b/config/curl.md new file mode 100644 index 000000000000..6fc4b3f7f43c --- /dev/null +++ b/config/curl.md @@ -0,0 +1,23 @@ +### Test MealRestController (application deployed in application context `topjava`). +> For windows use `Git Bash` + +#### get All +`curl -s http://localhost:8080/topjava/rest/profile/meals` + +#### get 100003 +`curl -s http://localhost:8080/topjava/rest/profile/meals/100003` + +#### get between +`curl -s "http://localhost:8080/topjava/rest/profile/meals/between?startDateTime=2015-05-30T08:00:00&endDateTime=2015-05-30T16:00:00"` + +#### get not found +`curl -s -v http://localhost:8080/rest/meals/100008` + +#### delete +`curl -s -X DELETE http://localhost:8080/rest/meals/100002` + +#### create +`curl -s -v -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals` + +#### update +`curl -s -v -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003` \ No newline at end of file From 3d550676c383b0e31f8b46210a855a1ff9544413 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 11:16:07 +0500 Subject: [PATCH 070/111] 8 05 webjars --- pom.xml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pom.xml b/pom.xml index ea3e7df1f39e..19a14f1215ef 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,14 @@ 2.10.2.2.21 + + + 3.3.7-1 + 2.2.4 + 2.4.7 + 2.3.8 + 1.10.12-1 + @@ -229,6 +237,33 @@ 1.2
    + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + org.webjars + jquery + ${webjars-jquery.version} + + + org.webjars.bower + datetimepicker + ${webjars-datetimepicker.version} + + + org.webjars.bower + noty + ${webjars-noty.version} + + + org.webjars + datatables + ${webjars-datatables.version} + + com.fasterxml.jackson.core From 6bee4b919736e032f6084617e7f0f2cc42272632 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 12:18:06 +0500 Subject: [PATCH 071/111] 8 06 bootstrap --- config/messages/app.properties | 2 ++ config/messages/app_ru.properties | 2 ++ src/main/resources/spring/spring-mvc.xml | 3 ++ .../WEB-INF/jsp/fragments/bodyHeader.jsp | 14 +++++++- .../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 ++++++++++-------- .../webapp/resources/images/icon-meal.png | Bin 0 -> 1898 bytes 8 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 src/main/webapp/resources/images/icon-meal.png diff --git a/config/messages/app.properties b/config/messages/app.properties index 0bae88156cb8..bad528196f01 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -4,6 +4,8 @@ app.footer=Project + + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 27b777137b0c..e1b072afbc3a 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,4 +1,16 @@ <%@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" %> -
      
    \ No newline at end of file + \ No newline at end of file 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 74236e0b683b..2d230b2c7551 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -7,5 +7,8 @@ <fmt:message key="app.title"/> + + + diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp index 8eefc3c1623a..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/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: Tue, 1 Nov 2016 13:59:32 +0500 Subject: [PATCH 072/111] 8 07 ajax datatables --- .../topjava/web/user/AdminAjaxController.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java 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..2fc61cea2e53 --- /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 { + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + return super.getAll(); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @PostMapping + public void createOrUpdate(@RequestParam("id") Integer 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 (user.isNew()) { + super.create(user); + } else { + super.update(user, id); + } + } +} From 13f069ba7ef2f6da8227ac1d731b66e7788e4cca Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 13:59:50 +0500 Subject: [PATCH 073/111] 8 08 notification --- src/main/webapp/WEB-INF/jsp/users.jsp | 166 +++++++++++++++--- .../webapp/resources/js/datatablesUtil.js | 82 +++++++++ 2 files changed, 221 insertions(+), 27 deletions(-) create mode 100644 src/main/webapp/resources/js/datatablesUtil.js diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index d2c721583b06..772712ce8633 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -4,35 +4,147 @@ + + -
    -

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

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ${user.email}${user.roles} + checked id="${user.id}"/> +
    +
    +
    +
    +
    + + - + + + + + + + \ 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..d43982d3fb71 --- /dev/null +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -0,0 +1,82 @@ +function makeEditable() { + $('.delete').click(function () { + deleteRow($(this).attr("id")); + }); + + $('#detailsForm').submit(function () { + save(); + return false; + }); + + $(document).ajaxError(function (event, jqXHR, options, jsExc) { + failNoty(event, jqXHR, options, jsExc); + }); +} + +function add() { + $('#id').val(null); + $('#editRow').modal(); +} + +function deleteRow(id) { + $.ajax({ + url: ajaxUrl + id, + type: 'DELETE', + success: function () { + updateTable(); + successNoty('Deleted'); + } + }); +} + +function updateTable() { + $.get(ajaxUrl, function (data) { + datatableApi.fnClearTable(); + $.each(data, function (key, item) { + datatableApi.fnAddData(item); + }); + datatableApi.fnDraw(); + }); +} + +function save() { + var form = $('#detailsForm'); + $.ajax({ + type: "POST", + url: ajaxUrl, + data: form.serialize(), + success: function () { + $('#editRow').modal('hide'); + updateTable(); + successNoty('Saved'); + } + }); +} + +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 16f6cf28b2dfc790f03395556f1c701b78ddf699 Mon Sep 17 00:00:00 2001 From: afarida Date: Tue, 1 Nov 2016 14:00:04 +0500 Subject: [PATCH 074/111] 8 09 add security --- pom.xml | 14 ++++++++++ src/main/resources/logback.xml | 1 + src/main/resources/spring/spring-app.xml | 8 +----- src/main/resources/spring/spring-security.xml | 27 +++++++++++++++++++ src/main/webapp/WEB-INF/web.xml | 10 +++++++ 5 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/spring/spring-security.xml diff --git a/pom.xml b/pom.xml index 19a14f1215ef..4cb0a8b72d04 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 8.0.33 4.3.2.RELEASE + 4.1.3.RELEASE 1.10.4.RELEASE 2.8.4 @@ -188,6 +189,19 @@
    + + + org.springframework.security + spring-security-web + ${spring-security.version} + + + org.springframework.security + spring-security-config + ${spring-security.version} + + + org.hibernate hibernate-core diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 911b469f7aad..7544c8c94ae7 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -22,6 +22,7 @@ + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index c3e7587cfdea..f73d99354efc 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -4,14 +4,8 @@ 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"> - + 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 6e1280c56d07..ab23986713bf 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 46a1267cdc783ffb3d869b0a8558f58bc427c9d4 Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 12:30:32 +0500 Subject: [PATCH 075/111] 9 0 1 fix --- .../java/ru/javawebinar/topjava/model/BaseEntity.java | 2 +- .../javawebinar/topjava/web/meal/JspMealController.java | 8 ++++---- src/main/resources/db/populateDB.sql | 5 ++--- .../repository/mock/InMemoryMealRepositoryImpl.java | 2 +- .../ru/javawebinar/topjava/web/json/JsonUtilTest.java | 8 ++++---- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index ae243a6a6eb8..86e5e5de2751 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -46,7 +46,7 @@ public Integer getId() { @Override public boolean isNew() { - return (this.id == null); + return (getId() == null); } @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java index 38737fe41e2c..fcc69572add9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -44,15 +44,15 @@ public String create(Model model) { @PostMapping public String updateOrCreate(HttpServletRequest request) { String id = request.getParameter("id"); - Meal userMeal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), + Meal meal = new Meal(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); + if (meal.isNew()) { + super.create(meal); } else { - super.update(userMeal, userMeal.getId()); + super.update(meal, meal.getId()); } return "redirect:/meals"; } diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index cc816500ce36..5e0847601f61 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -1,6 +1,5 @@ -DELETE FROM user_roles; -DELETE FROM meals; -DELETE FROM users; +-- http://stackoverflow.com/questions/13223820/postgresql-delete-all-content +TRUNCATE users CASCADE; ALTER SEQUENCE global_seq RESTART WITH 100000; INSERT INTO users (name, email, password) diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 1335bf2b8cb8..eec9fb8aad27 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -61,7 +61,7 @@ public Collection getAll(int userId) { @Override public Collection getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { return getAllStream(userId) - .filter(um -> TimeUtil.isBetween(um.getDateTime(), startDateTime, endDateTime)) + .filter(m -> TimeUtil.isBetween(m.getDateTime(), startDateTime, endDateTime)) .collect(Collectors.toList()); } diff --git a/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java index 9015e61c08f2..723d38bc1409 100644 --- a/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java @@ -16,15 +16,15 @@ public class JsonUtilTest { public void testReadWriteValue() throws Exception { String json = JsonUtil.writeValue(MealTestData.ADMIN_MEAL1); System.out.println(json); - Meal userMeal = JsonUtil.readValue(json, Meal.class); - MealTestData.MATCHER.assertEquals(MealTestData.ADMIN_MEAL1, userMeal); + Meal meal = JsonUtil.readValue(json, Meal.class); + MealTestData.MATCHER.assertEquals(MealTestData.ADMIN_MEAL1, meal); } @Test public void testReadWriteValues() throws Exception { String json = JsonUtil.writeValue(MealTestData.MEALS); System.out.println(json); - List userMeals = JsonUtil.readValues(json, Meal.class); - MealTestData.MATCHER.assertCollectionEquals(MealTestData.MEALS, userMeals); + List meal = JsonUtil.readValues(json, Meal.class); + MealTestData.MATCHER.assertCollectionEquals(MealTestData.MEALS, meal); } } \ No newline at end of file From 15ea1461ac3912f66893bd7bc08e821693191220 Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 12:30:49 +0500 Subject: [PATCH 076/111] 9 0 2 fix --- config/messages/app.properties | 1 + config/messages/app_ru.properties | 1 + src/main/webapp/WEB-INF/jsp/users.jsp | 23 ++++++++++++----------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index bad528196f01..8979eefd5659 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -11,6 +11,7 @@ users.email=Email users.roles=Roles users.active=Active users.registered=Registered +users.password=Password meals.title=Meals list meals.edit=Edit meal diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index d272daac5981..377c1b5d4c42 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -11,6 +11,7 @@ users.email=Почта users.roles=Роли users.active=Активный users.registered=Зарегистрирован +users.password=Пароль meals.title=Список еды meals.edit=Редактировать еду diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 772712ce8633..e3874879194c 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -4,7 +4,7 @@ - + @@ -56,39 +56,39 @@ @@ -103,58 +88,5 @@ - + \ 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 e1c88407efb2..66cd5c02bf9b 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -89,3 +89,15 @@ function failNoty(event, jqXHR, options, jsExc) { layout: 'bottomRight' }); } + +function renderEditBtn(data, type, row) { + if (type == 'display') { + return 'Edit'; + } +} + +function renderDeleteBtn(data, type, row) { + if (type == 'display') { + return 'Delete'; + } +} \ 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..3eb4e68bf74d --- /dev/null +++ b/src/main/webapp/resources/js/userDatatables.js @@ -0,0 +1,74 @@ +var ajaxUrl = 'ajax/admin/users/'; +var datatableApi; + +function updateTable() { + $.get(ajaxUrl, updateTableByData); +} + +$(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') { + return '' + date.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("opacity", 0.3); + } + }, + "initComplete": makeEditable + }); +}); \ No newline at end of file From 7a4fec7451db98470e556b7d1fd73bc52a07f5a7 Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 13:12:23 +0500 Subject: [PATCH 084/111] 9 07 datatable via ajax --- src/main/webapp/resources/js/datatablesUtil.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 66cd5c02bf9b..9fa62fc2278f 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -96,6 +96,18 @@ function renderEditBtn(data, type, row) { } } +function renderDeleteBtn(data, type, row) { + if (type == 'display') { + return 'Delete'; + } +} + +function renderEditBtn(data, type, row) { + if (type == 'display') { + return 'Edit'; + } +} + function renderDeleteBtn(data, type, row) { if (type == 'display') { return 'Delete'; From a56cd1d95bd4ae581f59e680c3b4be5e95189502 Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 13:21:10 +0500 Subject: [PATCH 085/111] 9 08 js i18n --- config/messages/app.properties | 6 ++++++ config/messages/app_ru.properties | 8 +++++++- src/main/webapp/WEB-INF/jsp/users.jsp | 11 ++++++++-- .../webapp/resources/js/datatablesUtil.js | 20 ++++++++++--------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index 8979eefd5659..ec76effe8f1c 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -30,3 +30,9 @@ common.delete=Delete common.update=Update common.save=Save common.cancel=Cancel + +common.deleted=Deleted +common.saved=Saved +common.enabled=Enabled +common.disabled=Disabled +common.failed=Failed \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 377c1b5d4c42..72390b4db34e 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -29,4 +29,10 @@ common.select=Выбрать common.delete=Удалить common.update=Обновить common.save=Сохранить -common.cancel=Отмена \ No newline at end of file +common.cancel=Отмена + +common.deleted=Удален +common.saved=Записан +common.enabled=Активирован +common.disabled=Деактивирован +common.failed=Ошибка \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 4bb65cbbe98a..98e28b4ded91 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -15,7 +15,7 @@

    - + ')"> @@ -41,7 +41,7 @@ + diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 9fa62fc2278f..8ea8c42c2ca6 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -7,12 +7,14 @@ function makeEditable() { }); } -function add() { +function add(add_title) { + $('#modalTitle').html(add_title); form.find(":input").val(""); $('#editRow').modal(); } function updateRow(id) { + $('#modalTitle').html(edit_title); $.get(ajaxUrl + id, function (data) { $.each(data, function (key, value) { form.find("input[name='" + key + "']").val(value); @@ -27,7 +29,7 @@ function deleteRow(id) { type: 'DELETE', success: function () { updateTable(); - successNoty('Deleted'); + successNoty('common.deleted'); } }); } @@ -40,7 +42,7 @@ function enable(chkbox, id) { data: 'enabled=' + enabled, success: function () { chkbox.closest('tr').fadeTo(300, enabled ? 1 : 0.3); - successNoty(enabled ? 'Enabled' : 'Disabled'); + successNoty(enabled ? 'common.enabled' : 'common.disabled'); } }); } @@ -57,7 +59,7 @@ function save() { success: function () { $('#editRow').modal('hide'); updateTable(); - successNoty('Saved'); + successNoty('common.saved'); } }); } @@ -71,10 +73,10 @@ function closeNoty() { } } -function successNoty(text) { +function successNoty(key) { closeNoty(); noty({ - text: text, + text: i18n[key], type: 'success', layout: 'bottomRight', timeout: true @@ -84,7 +86,7 @@ function successNoty(text) { function failNoty(event, jqXHR, options, jsExc) { closeNoty(); failedNote = noty({ - text: 'Failed: ' + jqXHR.statusText + "
    " + jqXHR.responseJSON, + text: i18n['common.failed'] + ': ' + jqXHR.statusText + "
    " + jqXHR.responseJSON, type: 'error', layout: 'bottomRight' }); @@ -92,13 +94,13 @@ function failNoty(event, jqXHR, options, jsExc) { function renderEditBtn(data, type, row) { if (type == 'display') { - return 'Edit'; + return ''+i18n['common.update']+''; } } function renderDeleteBtn(data, type, row) { if (type == 'display') { - return 'Delete'; + return ''+i18n['common.delete']+''; } } From 61369ad39f627b9c86bbd2eefb9d1ac42aedd000 Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 13:21:51 +0500 Subject: [PATCH 086/111] 9 09 min form login --- config/messages/app.properties | 1 + config/messages/app_ru.properties | 1 + src/main/resources/spring/spring-security.xml | 12 ++++++++++-- src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index ec76effe8f1c..e28f295bb0b0 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -2,6 +2,7 @@ app.title=Calories management app.home=Home app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application app.login=Login as +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 72390b4db34e..5cfb8edc0285 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -2,6 +2,7 @@ app.title=Подсчет калорий app.home=Главная app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery app.login=Зайти как +app.logout=Выйти users.title=Список пользователей users.edit=Редактировать пользователя 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 @@ - + + + + + + + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index e1b072afbc3a..cb20483f8784 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -9,7 +9,7 @@ From f0bac3a3fd7514a4ff072a74bb69457c555896ed Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 13:25:30 +0500 Subject: [PATCH 087/111] 9 10 jsp form login --- config/messages/app.properties | 2 +- config/messages/app_ru.properties | 2 +- .../topjava/web/RootController.java | 21 ++--- src/main/resources/spring/spring-mvc.xml | 9 ++- src/main/resources/spring/spring-security.xml | 9 ++- 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 | 13 +++ 9 files changed, 139 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 e28f295bb0b0..90b7c191e682 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -1,7 +1,7 @@ app.title=Calories management app.home=Home 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 diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 5cfb8edc0285..8bd10fbe37cb 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -1,7 +1,7 @@ app.title=Подсчет калорий app.home=Главная app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery -app.login=Зайти как +app.login=Войти app.logout=Выйти users.title=Список пользователей diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index fa1051595c90..c9c252d825ab 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -3,14 +3,15 @@ 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.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; +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.AuthorizedUser; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.util.MealsUtil; -import javax.servlet.http.HttpServletRequest; - /** * User: gkislin * Date: 22.08.2014 @@ -23,7 +24,7 @@ public class RootController { @GetMapping("/") public String root() { - return "index"; + return "redirect:meals"; } @GetMapping("/users") @@ -31,11 +32,13 @@ public String users() { return "users"; } - @PostMapping("/users") - public String setUser(HttpServletRequest request) { - int userId = Integer.valueOf(request.getParameter("userId")); - AuthorizedUser.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"; } @GetMapping("/meals") diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index e8e58817154b..fb8e24759038 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..bf431a90c013 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -17,10 +17,15 @@ + + - - + + + 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..62e151e5806f --- /dev/null +++ b/src/main/webapp/test.html @@ -0,0 +1,13 @@ + + + + + +
    + +
    + + \ No newline at end of file From a7714ccaa1677b1e3b665e3278c6411a58e00b9b Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 13:33:30 +0500 Subject: [PATCH 088/111] 9 11 auth via user service --- .../javawebinar/topjava/AuthorizedUser.java | 48 +++++++++++++++---- .../ru/javawebinar/topjava/model/Role.java | 11 ++++- .../topjava/service/UserServiceImpl.java | 16 ++++++- .../ru/javawebinar/topjava/to/UserTo.java | 20 +++++++- .../ru/javawebinar/topjava/util/UserUtil.java | 4 ++ src/main/resources/spring/spring-security.xml | 17 +++++-- 6 files changed, 97 insertions(+), 19 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index d1648a9c3dca..8ab49aa47534 100644 --- a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -1,24 +1,56 @@ 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; +import static java.util.Objects.requireNonNull; + /** * GKislin * 06.03.2015. */ -public class AuthorizedUser { - public static int id = BaseEntity.START_SEQ; +public class AuthorizedUser extends org.springframework.security.core.userdetails.User { + private static final long serialVersionUID = 1L; - public static int id() { - return id; + private final UserTo userTo; + + public AuthorizedUser(User user) { + super(user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, user.getRoles()); + this.userTo = UserUtil.asTo(user); + } + + public static AuthorizedUser safeGet() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) { + return null; + } + Object principal = auth.getPrincipal(); + return (principal instanceof AuthorizedUser) ? (AuthorizedUser) principal : null; + } + + public static AuthorizedUser get() { + AuthorizedUser user = safeGet(); + requireNonNull(user, "No authorized user found"); + return user; } - public static void setId(int id) { - AuthorizedUser.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 f0de2b4c4a81..9c5210417f0b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Role.java +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -1,10 +1,17 @@ package ru.javawebinar.topjava.model; +import org.springframework.security.core.GrantedAuthority; + /** * User: gkislin * Date: 22.08.2014 */ -public enum Role { +public enum Role implements GrantedAuthority { ROLE_USER, - ROLE_ADMIN + ROLE_ADMIN; + + @Override + public String getAuthority() { + return name(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index aa0aab149350..bd87d3c0f3e5 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -3,9 +3,12 @@ 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 org.springframework.util.Assert; +import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.to.UserTo; @@ -19,8 +22,8 @@ * GKislin * 06.03.2015. */ -@Service -public class UserServiceImpl implements UserService { +@Service("userService") +public class UserServiceImpl implements UserService, UserDetailsService { @Autowired private UserRepository repository; @@ -85,6 +88,15 @@ public void enable(int id, boolean enabled) { repository.save(user); } + @Override + public AuthorizedUser loadUserByUsername(String email) throws UsernameNotFoundException { + User u = repository.getByEmail(email); + if (u == null) { + throw new UsernameNotFoundException("User " + email + " is not found"); + } + return new AuthorizedUser(u); + } + @Override public User getWithMeals(int id) { return ExceptionUtil.checkNotFoundWithId(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 03eb3e6b49b9..278c9b647d39 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -2,10 +2,16 @@ 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 implements Serializable { + private static final long serialVersionUID = 1L; -public class UserTo { private Integer id; @NotEmpty @@ -18,14 +24,19 @@ public class UserTo { @Size(min = 5, max = 64, message = " must between 5 and 64 characters") private String password; + @Range(min = 100, max = 5000) + @NotNull(message = " must not be empty") + private Integer caloriesPerDay = UserUtil.DEFAULT_CALORIES_PER_DAY; + public UserTo() { } - public UserTo(Integer id, String name, String email, String password) { + public UserTo(Integer 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 Integer getId() { @@ -64,12 +75,17 @@ public boolean isNew() { return id == null; } + 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 06e53065bc38..e8e579b9c2d3 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java @@ -15,6 +15,10 @@ public static User createNewFromTo(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 bf431a90c013..91854e7beb52 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -30,11 +30,18 @@ - - - - - + +
    \ No newline at end of file From 3c5f1c33dd5d88cdbc3dcd335e890b923f346d6d Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 13:43:24 +0500 Subject: [PATCH 089/111] 9 12 spring security test --- pom.xml | 5 +++ .../java/ru/javawebinar/topjava/TestUtil.java | 14 +++++++ .../topjava/web/AbstractControllerTest.java | 2 + .../topjava/web/RootControllerTest.java | 39 ++++++++----------- .../web/user/AdminRestControllerTest.java | 30 +++++++++++--- .../web/user/ProfileRestControllerTest.java | 14 ++++++- 6 files changed, 74 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index 4cb0a8b72d04..cb641c6daab8 100644 --- a/pom.xml +++ b/pom.xml @@ -321,6 +321,11 @@ ${spring.version} test + + 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 059a771ccbdb..1ec27e344702 100644 --- a/src/test/java/ru/javawebinar/topjava/TestUtil.java +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -1,6 +1,11 @@ package ru.javawebinar.topjava; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +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.model.User; import java.io.UnsupportedEncodingException; @@ -18,4 +23,13 @@ public static ResultActions print(ResultActions action) throws UnsupportedEncodi public static String getContent(ResultActions action) throws UnsupportedEncodingException { return action.andReturn().getResponse().getContentAsString(); } + + public static void authorize(User user) { + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword())); + } + + public static RequestPostProcessor userHttpBasic(User user) { + return SecurityMockMvcRequestPostProcessors.httpBasic(user.getEmail(), user.getPassword()); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index a20e2668456f..b09e1ef9c424 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -17,6 +17,7 @@ import javax.annotation.PostConstruct; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static ru.javawebinar.topjava.Profiles.ACTIVE_DB; import static ru.javawebinar.topjava.Profiles.DB_IMPLEMENTATION; @@ -58,6 +59,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/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index 184a71bd7e50..949ecd8cb0fd 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,15 +1,14 @@ -package ru.javawebinar.topjava.web; +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.MealTestData.MEAL1; -import static ru.javawebinar.topjava.MealTestData.MEAL1_ID; +import static ru.javawebinar.topjava.TestUtil.authorize; +import static ru.javawebinar.topjava.UserTestData.ADMIN; import static ru.javawebinar.topjava.UserTestData.USER; -import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; /** * GKislin @@ -19,34 +18,28 @@ public class RootControllerTest extends AbstractControllerTest { @Test public void testUsers() throws Exception { + authorize(ADMIN); mockMvc.perform(get("/users")) .andDo(print()) .andExpect(status().isOk()) .andExpect(view().name("users")) - .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) - .andExpect(model().attribute("users", hasSize(2))) - .andExpect(model().attribute("users", hasItem( - allOf( - hasProperty("id", is(START_SEQ)), - hasProperty("name", is(USER.getName())) - ) - ))); + .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")); + } + + @Test + public void testUnAuth() throws Exception { + mockMvc.perform(get("/users")) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost/login")); } @Test public void testMeals() throws Exception { + authorize(USER); mockMvc.perform(get("/meals")) .andDo(print()) - .andExpect(status().isOk()) .andExpect(view().name("meals")) - .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")) - .andExpect(model().attribute("meals", hasSize(6))) - .andExpect(model().attribute("meals", hasItem( - allOf( - hasProperty("id", is(MEAL1_ID)), - hasProperty("description", is(MEAL1.getDescription())) - ) - ))); + .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")); } - } \ No newline at end of file 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 8b4bc1b49b4d..b636d5aff40d 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()) // https://jira.spring.io/browse/SPR-14472 @@ -34,20 +36,35 @@ 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(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(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(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(userHttpBasic(USER))) + .andExpect(status().isForbidden()); + } + @Test public void testUpdate() throws Exception { User updated = new User(USER); @@ -55,6 +72,7 @@ public void testUpdate() throws Exception { updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); mockMvc.perform(put(REST_URL + USER_ID) .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(updated))) .andExpect(status().isOk()); @@ -66,6 +84,7 @@ public void testCreate() throws Exception { User expected = new User(null, "New", "new@gmail.com", "newPass", Role.ROLE_USER, Role.ROLE_ADMIN); ResultActions action = mockMvc.perform(post(REST_URL) .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(expected))).andExpect(status().isCreated()); User returned = MATCHER.fromJsonAction(action); @@ -77,7 +96,8 @@ public void testCreate() throws Exception { @Test public void testGetAll() throws Exception { - TestUtil.print(mockMvc.perform(get(REST_URL)) + TestUtil.print(mockMvc.perform(get(REST_URL) + .with(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(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 830de49dafaa..2ec2a25617d6 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -14,6 +14,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.*; import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; @@ -21,15 +22,23 @@ 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(userHttpBasic(USER))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(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(userHttpBasic(USER))) .andExpect(status().isOk()); MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), userService.getAll()); } @@ -38,6 +47,7 @@ public void testDelete() throws Exception { public void testUpdate() throws Exception { User updated = new User(USER_ID, "newName", "newemail@ya.ru", "newPassword", Role.ROLE_USER); mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER)) .content(JsonUtil.writeValue(updated))) .andDo(print()) .andExpect(status().isOk()); From fef95c77a0420b87e4935ef42d8ce460c7bb86c4 Mon Sep 17 00:00:00 2001 From: afarida Date: Thu, 5 Jan 2017 13:56:38 +0500 Subject: [PATCH 090/111] 9 13 fix --- src/main/resources/db/populateDB.sql | 10 +++++++++- .../java/ru/javawebinar/topjava/SpringMain.java | 7 ++++++- src/test/java/ru/javawebinar/topjava/TestUtil.java | 8 ++++++-- .../topjava/web/RootControllerTest.java | 14 +++++++------- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index 5e0847601f61..78bd83a47296 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -1,5 +1,13 @@ -- http://stackoverflow.com/questions/13223820/postgresql-delete-all-content -TRUNCATE users CASCADE; +-- TRUNCATE public CASCADE; + +-- http://stackoverflow.com/a/4991969/548473 +-- TRUNCATE SCHEMA public AND COMMIT; + +DELETE FROM user_roles; +DELETE FROM meals; +DELETE FROM users; + ALTER SEQUENCE global_seq RESTART WITH 100000; INSERT INTO users (name, email, password) diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index c55eea7570b3..17252620c23e 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -11,6 +11,9 @@ import java.util.Arrays; import java.util.List; +import static ru.javawebinar.topjava.TestUtil.mockAuthorize; +import static ru.javawebinar.topjava.UserTestData.USER; + /** * User: gkislin * Date: 22.08.2014 @@ -23,9 +26,11 @@ public static void main(String[] args) { appCtx.load("spring/spring-app.xml", "spring/spring-db.xml", "spring/spring-mvc.xml"); appCtx.refresh(); + mockAuthorize(USER); + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); - adminUserController.create(UserTestData.USER); + adminUserController.get(UserTestData.USER_ID); System.out.println(); MealRestController mealController = appCtx.getBean(MealRestController.class); diff --git a/src/test/java/ru/javawebinar/topjava/TestUtil.java b/src/test/java/ru/javawebinar/topjava/TestUtil.java index 1ec27e344702..29cf92cd35d7 100644 --- a/src/test/java/ru/javawebinar/topjava/TestUtil.java +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -24,12 +24,16 @@ public static String getContent(ResultActions action) throws UnsupportedEncoding return action.andReturn().getResponse().getContentAsString(); } - public static void authorize(User user) { + public static void mockAuthorize(User user) { SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword())); + new UsernamePasswordAuthenticationToken(new AuthorizedUser(user), null, user.getRoles())); } public static RequestPostProcessor userHttpBasic(User user) { return SecurityMockMvcRequestPostProcessors.httpBasic(user.getEmail(), user.getPassword()); } + + public static RequestPostProcessor userAuth(User user) { + return SecurityMockMvcRequestPostProcessors.authentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword())); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index 949ecd8cb0fd..dd4c6fa11d88 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,12 +1,11 @@ -package ru.javawebinar.topjava.web.user; +package ru.javawebinar.topjava.web; import org.junit.Test; -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.*; -import static ru.javawebinar.topjava.TestUtil.authorize; +import static ru.javawebinar.topjava.TestUtil.userAuth; import static ru.javawebinar.topjava.UserTestData.ADMIN; import static ru.javawebinar.topjava.UserTestData.USER; @@ -14,12 +13,13 @@ * GKislin * 10.04.2015. */ + public class RootControllerTest extends AbstractControllerTest { @Test public void testUsers() throws Exception { - authorize(ADMIN); - mockMvc.perform(get("/users")) + mockMvc.perform(get("/users") + .with(userAuth(ADMIN))) .andDo(print()) .andExpect(status().isOk()) .andExpect(view().name("users")) @@ -36,8 +36,8 @@ public void testUnAuth() throws Exception { @Test public void testMeals() throws Exception { - authorize(USER); - mockMvc.perform(get("/meals")) + mockMvc.perform(get("/meals") + .with(userAuth(USER))) .andDo(print()) .andExpect(view().name("meals")) .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")); From b177aa20851d628260894129da5a7a0fd9e288af Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 6 Jan 2017 15:11:23 +0500 Subject: [PATCH 091/111] 10 01 HW9 binding ajax --- .../ru/javawebinar/topjava/model/Meal.java | 7 ++- .../topjava/web/RootController.java | 12 +--- .../topjava/web/meal/MealAjaxController.java | 36 ++++++++--- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 8 +++ src/main/webapp/WEB-INF/jsp/users.jsp | 8 +-- .../webapp/resources/js/mealDatatables.js | 60 +++++++++++++++++++ 6 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 src/main/webapp/resources/js/mealDatatables.js diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 0ac5adbabf08..381fad9c2527 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -2,6 +2,7 @@ import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; +import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; import javax.validation.constraints.NotNull; @@ -33,6 +34,7 @@ public class Meal extends BaseEntity { @Column(name = "date_time", nullable = false) @NotNull + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime dateTime; @Column(name = "description", nullable = false) @@ -41,7 +43,8 @@ public class Meal extends BaseEntity { @Column(name = "calories", nullable = false) @Range(min = 10, max = 5000) - protected int calories; + @NotNull + protected Integer calories; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) @@ -89,7 +92,7 @@ public void setDescription(String description) { this.description = description; } - public void setCalories(int calories) { + public void setCalories(Integer calories) { this.calories = calories; } diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index c9c252d825ab..317375c45bf3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,16 +1,11 @@ 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.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; 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.AuthorizedUser; -import ru.javawebinar.topjava.service.MealService; -import ru.javawebinar.topjava.util.MealsUtil; /** * User: gkislin @@ -19,9 +14,6 @@ @Controller public class RootController { - @Autowired - private MealService mealService; - @GetMapping("/") public String root() { return "redirect:meals"; @@ -42,9 +34,7 @@ public String login(ModelMap model, } @GetMapping("/meals") - public String meals(Model model) { - model.addAttribute("meals", - MealsUtil.getWithExceeded(mealService.getAll(AuthorizedUser.id()), AuthorizedUser.getCaloriesPerDay())); + public String meals() { return "meals"; } } diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java index a45fe6848029..fa9af83af670 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java @@ -1,13 +1,15 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.format.annotation.DateTimeFormat; +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.Meal; import ru.javawebinar.topjava.to.MealWithExceed; +import javax.validation.Valid; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; @@ -19,27 +21,45 @@ @RequestMapping(value = "/ajax/profile/meals") public class MealAjaxController extends AbstractMealController { +/* + Other validation solution: + http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-initbinder + + @InitBinder + protected void initBinder(WebDataBinder binder) { + binder.setRequiredFields("dateTime", "description", "calories"); + } +*/ + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { return super.getAll(); } + @GetMapping(value = "/{id}") + public Meal get(@PathVariable("id") int id) { + return super.get(id); + } + + @DeleteMapping(value = "/{id}") public void delete(@PathVariable("id") int id) { super.delete(id); } @PostMapping - public void updateOrCreate(@RequestParam("id") Integer id, - @RequestParam("dateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime, - @RequestParam("description") String description, - @RequestParam("calories") int calories) { - Meal meal = new Meal(id, dateTime, description, calories); + public ResponseEntity updateOrCreate(@Valid Meal meal, 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 (meal.isNew()) { super.create(meal); } else { - super.update(meal, id); + super.update(meal, meal.getId()); } + return new ResponseEntity<>(HttpStatus.OK); } @PostMapping(value = "/filter", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp index 171837386e40..876244c1495f 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,8 +1,16 @@ <%@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/jsp/jstl/core" %> + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 98e28b4ded91..3a3c479f3a66 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -73,7 +73,7 @@
    - +
    @@ -83,11 +83,7 @@ diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js new file mode 100644 index 000000000000..306b0f9186c8 --- /dev/null +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -0,0 +1,60 @@ +var ajaxUrl = 'ajax/profile/meals/'; +var datatableApi; + +function updateTable() { + $.ajax({ + type: "POST", + url: ajaxUrl + 'filter', + data: $('#filter').serialize(), + success: updateTableByData + }); +} + +$(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": makeEditable + }); +}); From 70c4e2f2de47a23b2a58ae6953f75403b6722ad0 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 6 Jan 2017 15:12:51 +0500 Subject: [PATCH 092/111] 10 02 HW9 test --- .../web/meal/MealRestControllerTest.java | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 5c81110d2ca5..cb83f22c8ea6 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.util.MealsUtil; @@ -18,6 +19,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.TestUtil.userHttpBasic; +import static ru.javawebinar.topjava.UserTestData.ADMIN; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER; import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; @@ -30,16 +34,24 @@ public class MealRestControllerTest 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().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(MATCHER.contentMatcher(MEAL1)); + .andExpect(MATCHER.contentMatcher(MealTestData.ADMIN_MEAL1)); + } + + @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).contentType(MediaType.APPLICATION_JSON)) + 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)); } @@ -48,8 +60,10 @@ public void testDelete() throws Exception { public void testUpdate() throws Exception { Meal updated = getUpdated(); - mockMvc.perform(put(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON) - .content(JsonUtil.writeValue(updated))) + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated)) + .with(userHttpBasic(USER))) .andExpect(status().isOk()); assertEquals(updated, service.get(MEAL1_ID, START_SEQ)); @@ -60,18 +74,20 @@ public void testCreate() throws Exception { Meal created = getCreated(); ResultActions action = mockMvc.perform(post(REST_URL) .contentType(MediaType.APPLICATION_JSON) - .content(JsonUtil.writeValue(created))); + .content(JsonUtil.writeValue(created)) + .with(userHttpBasic(ADMIN))); Meal 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_MEAL1), service.getAll(ADMIN_ID)); } @Test public void testGetAll() throws Exception { - mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON)) + mockMvc.perform(get(REST_URL) + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) @@ -80,7 +96,8 @@ public void testGetAll() throws Exception { @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( @@ -92,7 +109,8 @@ public void testGetBetween() throws Exception { public void testFilter() throws Exception { mockMvc.perform(get(REST_URL + "filter") .param("startDate", "2015-05-30").param("startTime", "07:00") - .param("endDate", "2015-05-31").param("endTime", "11:00")) + .param("endDate", "2015-05-31").param("endTime", "11:00") + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(MATCHER_WITH_EXCEED.contentListMatcher( @@ -103,7 +121,8 @@ 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( From 3a7632eafa3ed886834d5e869952ddacb4440ec5 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 6 Jan 2017 15:14:16 +0500 Subject: [PATCH 093/111] 10 03 HW9 datetimepicker --- .../ru/javawebinar/topjava/model/Meal.java | 2 +- .../webapp/resources/js/datatablesUtil.js | 4 ++- .../webapp/resources/js/mealDatatables.js | 32 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 381fad9c2527..24216685a60b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -34,7 +34,7 @@ public class Meal extends BaseEntity { @Column(name = "date_time", nullable = false) @NotNull - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime dateTime; @Column(name = "description", nullable = false) diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 8ea8c42c2ca6..dedc63f460a5 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -17,7 +17,9 @@ function updateRow(id) { $('#modalTitle').html(edit_title); $.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 306b0f9186c8..c649b0600b41 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -57,4 +57,36 @@ $(function () { }, "initComplete": makeEditable }); + + var startDate = $('#startDate'); + var endDate = $('#endDate'); + startDate.datetimepicker({ + timepicker: false, + format: 'Y-m-d', + formatDate: 'Y-m-d', + onShow: function (ct) { + this.setOptions({ + maxDate: endDate.val() ? endDate.val() : false + }) + } + }); + endDate.datetimepicker({ + timepicker: false, + format: 'Y-m-d', + formatDate: 'Y-m-d', + onShow: function (ct) { + this.setOptions({ + minDate: startDate.val() ? startDate.val() : false + }) + } + }); + + $('#startTime, #endTime').datetimepicker({ + datepicker: false, + format: 'H:i' + }); + + $('#dateTime').datetimepicker({ + format: 'Y-m-d H:i' + }); }); From 602d2daaf541904779021b1ef97da931b6af3372 Mon Sep 17 00:00:00 2001 From: afarida Date: Fri, 6 Jan 2017 15:16:31 +0500 Subject: [PATCH 094/111] 10 04 secure tag annotation --- pom.xml | 5 +++++ .../ru/javawebinar/topjava/web/RootController.java | 3 +++ src/main/resources/spring/spring-mvc.xml | 7 ++++++- src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp | 11 ++++++++--- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index cb641c6daab8..2737bff1cc42 100644 --- a/pom.xml +++ b/pom.xml @@ -200,6 +200,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 317375c45bf3..1f797a27dd89 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.GetMapping; @@ -19,6 +20,8 @@ public String root() { return "redirect:meals"; } +// @Secured("ROLE_ADMIN") + @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/users") public String users() { return "users"; diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index fb8e24759038..cea154959a0c 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 cb20483f8784..c05a472d52f4 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,6 +1,7 @@ <%@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" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> +<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index d7d758e29aab..2d92ec188ea2 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -5,6 +5,12 @@ function makeEditable() { $(document).ajaxError(function (event, jqXHR, options, jsExc) { failNoty(event, jqXHR, options, jsExc); }); + + var token = $("meta[name='_csrf']").attr("content"); + var header = $("meta[name='_csrf_header']").attr("content"); + $(document).ajaxSend(function(e, xhr, options) { + xhr.setRequestHeader(header, token); + }); } function add(add_title) { From 5e4da5d84accfbfaf006f881cfde6c7a338ebd82 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:15:11 +0500 Subject: [PATCH 103/111] 11 0 1 fix not found --- .../javawebinar/topjava/util/exception/NotFoundException.java | 4 ---- .../java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java | 3 ++- .../javawebinar/topjava/web/meal/MealRestControllerTest.java | 4 ++-- .../javawebinar/topjava/web/user/AdminRestControllerTest.java | 4 ++-- 4 files changed, 6 insertions(+), 9 deletions(-) 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 1c85842a3497..7a770f0db834 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -1,13 +1,9 @@ package ru.javawebinar.topjava.util.exception; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - /** * User: gkislin * Date: 19.08.2014 */ -@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No data found") // 404 public class NotFoundException extends RuntimeException { public NotFoundException(String message) { super(message); diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index d8188da43817..6164b4ac953a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -20,7 +20,8 @@ public class ExceptionInfoHandler { Logger LOG = LoggerFactory.getLogger(ExceptionInfoHandler.class); - @ResponseStatus(HttpStatus.NOT_FOUND) +// http://stackoverflow.com/a/22358422/548473 + @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(NotFoundException.class) @ResponseBody @Order(Ordered.HIGHEST_PRECEDENCE) diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 78c77863e7ab..e43fa11c622e 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -52,7 +52,7 @@ public void testGetUnauth() throws Exception { public void testGetNotFound() throws Exception { mockMvc.perform(get(REST_URL + ADMIN_MEAL_ID) .with(userHttpBasic(USER))) - .andExpect(status().isNotFound()); + .andExpect(status().isUnprocessableEntity()); } @Test @@ -60,7 +60,7 @@ public void testDeleteNotFound() throws Exception { mockMvc.perform(delete(REST_URL + ADMIN_MEAL_ID) .with(userHttpBasic(USER))) .andDo(print()) - .andExpect(status().isNotFound()); + .andExpect(status().isUnprocessableEntity()); } @Test 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 74fd349e21f7..267a59effeff 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -38,7 +38,7 @@ public void testGet() throws Exception { public void testGetNotFound() throws Exception { mockMvc.perform(get(REST_URL + 1) .with(TestUtil.userHttpBasic(ADMIN))) - .andExpect(status().isNotFound()) + .andExpect(status().isUnprocessableEntity()) .andDo(print()); } @@ -64,7 +64,7 @@ public void testDelete() throws Exception { public void testDeleteNotFound() throws Exception { mockMvc.perform(delete(REST_URL + 1) .with(TestUtil.userHttpBasic(ADMIN))) - .andExpect(status().isNotFound()) + .andExpect(status().isUnprocessableEntity()) .andDo(print()); } From c4b9865ca010c2ab4eb61153b2153dbcee841cd9 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:18:31 +0500 Subject: [PATCH 104/111] 11 0 2 other fix --- .travis.yml | 5 +++++ .../ru/javawebinar/topjava/model/User.java | 2 +- .../ru/javawebinar/topjava/to/UserTo.java | 2 +- src/main/webapp/WEB-INF/tags/inputField.tag | 3 +-- src/main/webapp/WEB-INF/web.xml | 20 +++++++++---------- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index e8e09032e8f9..6b280b2d00f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,11 @@ language: java jdk: oraclejdk8 +#https://dzone.com/articles/travis-ci-tutorial-java-projects +cache: + directories: + - $HOME/.m2 + # https://docs.travis-ci.com/user/database-setup/#PostgreSQL before_script: - psql -c 'create database topjava' -U postgres diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 7766cf86dc98..b8863bb32e6d 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -58,7 +58,7 @@ public class User extends NamedEntity { @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private Set roles; - @Column(name = "calories_per_day", columnDefinition = "default 2000") + @Column(name = "calories_per_day", columnDefinition = "int default 2000") @Digits(fraction = 0, integer = 4) private int caloriesPerDay = UserUtil.DEFAULT_CALORIES_PER_DAY; diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index 126876ea0ac2..7e09cb90c757 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -75,7 +75,7 @@ public boolean isNew() { return id == null; } - public int getCaloriesPerDay() { + public Integer getCaloriesPerDay() { return caloriesPerDay; } diff --git a/src/main/webapp/WEB-INF/tags/inputField.tag b/src/main/webapp/WEB-INF/tags/inputField.tag index 6f4f5ff5adc2..a28f667d9c93 100644 --- a/src/main/webapp/WEB-INF/tags/inputField.tag +++ b/src/main/webapp/WEB-INF/tags/inputField.tag @@ -7,8 +7,7 @@ <%@ attribute name="inputType" required="false" description="Input type" %> - -
    +
    diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index ab23986713bf..81e673bf56b1 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -36,16 +36,6 @@ / - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - springSecurityFilterChain - /* - - encodingFilter org.springframework.web.filter.CharacterEncodingFilter @@ -63,4 +53,14 @@ /* + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + From 8fdfe26f1fa7fa251a6e03cba7581f546a6d02ad Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:21:55 +0500 Subject: [PATCH 105/111] 11 01 HW10 validation --- .../topjava/util/exception/ErrorInfo.java | 16 +++++++---- .../topjava/web/ExceptionInfoHandler.java | 28 +++++++++++++++++++ .../topjava/web/meal/MealAjaxController.java | 12 +------- .../topjava/web/meal/MealRestController.java | 5 ++-- .../topjava/web/user/AdminAjaxController.java | 12 +------- .../topjava/web/user/AdminRestController.java | 5 ++-- .../web/user/ProfileRestController.java | 4 ++- .../webapp/resources/js/datatablesUtil.js | 2 +- .../web/meal/MealRestControllerTest.java | 22 +++++++++++++++ .../web/user/ProfileRestControllerTest.java | 11 ++++++++ 10 files changed, 83 insertions(+), 34 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java index c422c409e630..8f8eb49cf036 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java @@ -5,13 +5,17 @@ * Date: 19.08.2014 */ public class ErrorInfo { - public final String url; - public final String cause; - public final String detail; + private final String url; + private final String cause; + private final String[] details; public ErrorInfo(CharSequence url, Throwable ex) { - this.url = url.toString(); - this.cause = ex.getClass().getSimpleName(); - this.detail = ex.getLocalizedMessage(); + this(url, ex.getClass().getSimpleName(), ex.getLocalizedMessage()); + } + + public ErrorInfo(CharSequence requestURL, String cause, String... details) { + this.url = requestURL.toString(); + this.cause = cause; + this.details = details; } } diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 6164b4ac953a..6d664292d769 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -6,11 +6,15 @@ import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.util.exception.ErrorInfo; import ru.javawebinar.topjava.util.exception.NotFoundException; import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; /** * User: gkislin @@ -37,6 +41,22 @@ public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationExceptio return logAndGetErrorInfo(req, e, true); } + @ResponseStatus(value = HttpStatus.BAD_REQUEST) // 400 + @ExceptionHandler(BindException.class) + @ResponseBody + @Order(Ordered.HIGHEST_PRECEDENCE + 2) + public ErrorInfo bindValidationError(HttpServletRequest req, BindingResult result) { + return logAndGetValidationErrorInfo(req, result); + } + + @ResponseStatus(value = HttpStatus.BAD_REQUEST) // 400 + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseBody + @Order(Ordered.HIGHEST_PRECEDENCE + 2) + public ErrorInfo restValidationError(HttpServletRequest req, MethodArgumentNotValidException e) { + return logAndGetValidationErrorInfo(req, e.getBindingResult()); + } + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) @ResponseBody @@ -45,6 +65,14 @@ public ErrorInfo handleError(HttpServletRequest req, Exception e) { return logAndGetErrorInfo(req, e, true); } + private ErrorInfo logAndGetValidationErrorInfo(HttpServletRequest req, BindingResult result) { + String[] details = result.getFieldErrors().stream() + .map(fe -> fe.getField() + ' ' + fe.getDefaultMessage()).toArray(String[]::new); + + LOG.warn("Validation exception at request " + req.getRequestURL() + ": " + Arrays.toString(details)); + return new ErrorInfo(req.getRequestURL(), "ValidationException", details); + } + public ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException) { if (logException) { LOG.error("Exception at request " + req.getRequestURL(), e); diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java index 17f3efee4b1e..3f03e32c9e82 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java @@ -1,9 +1,6 @@ package ru.javawebinar.topjava.web.meal; -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.Meal; import ru.javawebinar.topjava.to.MealWithExceed; @@ -48,19 +45,12 @@ public void delete(@PathVariable("id") int id) { } @PostMapping - public ResponseEntity updateOrCreate(@Valid Meal meal, BindingResult result) { - // TODO change to exception handler - 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); - } + public void updateOrCreate(@Valid Meal meal) { if (meal.isNew()) { super.create(meal); } else { super.update(meal, meal.getId()); } - return new ResponseEntity<>(HttpStatus.OK); } @PostMapping(value = "/filter", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index 19f8146fb825..2ea0d619cd56 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -8,6 +8,7 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealWithExceed; +import javax.validation.Valid; import java.net.URI; import java.time.LocalDate; import java.time.LocalDateTime; @@ -35,12 +36,12 @@ public List getAll() { } @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@RequestBody Meal meal, @PathVariable("id") int id) { + public void update(@Valid @RequestBody Meal meal, @PathVariable("id") int id) { super.update(meal, id); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@RequestBody Meal meal) { + public ResponseEntity createWithLocation(@Valid @RequestBody Meal meal) { Meal created = super.create(meal); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() 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 61e9bdbd92e7..0b50bfb4c910 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -1,9 +1,6 @@ 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; @@ -35,19 +32,12 @@ public void delete(@PathVariable("id") int id) { } @PostMapping - public ResponseEntity createOrUpdate(@Valid UserTo userTo, BindingResult result) { - if (result.hasErrors()) { - // TODO change to exception handler - 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); - } + public void createOrUpdate(@Valid UserTo userTo) { if (userTo.isNew()) { super.create(UserUtil.createNewFromTo(userTo)); } else { super.update(userTo); } - return new ResponseEntity<>(HttpStatus.OK); } @PostMapping(value = "/{id}") 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 710d4de047f3..b1f49da52e42 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -6,6 +6,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.User; +import javax.validation.Valid; import java.net.URI; import java.util.List; @@ -29,7 +30,7 @@ public User get(@PathVariable("id") int id) { } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@RequestBody User user) { + public ResponseEntity createWithLocation(@Valid @RequestBody User user) { User created = super.create(user); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() @@ -48,7 +49,7 @@ public void delete(@PathVariable("id") int id) { } @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@RequestBody User user, @PathVariable("id") int id) { + public void update(@Valid @RequestBody User user, @PathVariable("id") int id) { super.update(user, id); } 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 66b0f8abbd3c..4d469897d4b1 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -6,6 +6,8 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; +import javax.validation.Valid; + /** * GKislin * 06.03.2015. @@ -26,7 +28,7 @@ public void delete() { } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@RequestBody UserTo userTo) { + public void update(@Valid @RequestBody UserTo userTo) { userTo.setId(AuthorizedUser.id()); super.update(userTo); } diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 2d92ec188ea2..e0d0453a2b72 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -95,7 +95,7 @@ function failNoty(event, jqXHR, options, jsExc) { closeNoty(); var errorInfo = $.parseJSON(jqXHR.responseText); failedNote = noty({ - text: i18n['common.failed'] + ': ' + jqXHR.statusText + "
    "+ errorInfo.cause + "
    " + errorInfo.detail, + text: i18n['common.failed'] + ': ' + jqXHR.statusText + "
    "+ errorInfo.cause + "
    " + errorInfo.details.join("
    "), type: 'error', layout: 'bottomRight' }); diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index e43fa11c622e..67d9420c1ada 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -84,6 +84,17 @@ public void testUpdate() throws Exception { assertEquals(updated, service.get(MEAL1_ID, START_SEQ)); } + @Test + public void testUpdateInvalid() throws Exception { + Meal invalid = new Meal(MEAL1_ID, null, null, 6000); + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isBadRequest()); + } + @Test public void testCreate() throws Exception { Meal created = getCreated(); @@ -99,6 +110,17 @@ public void testCreate() throws Exception { MATCHER.assertCollectionEquals(Arrays.asList(ADMIN_MEAL2, created, ADMIN_MEAL1), service.getAll(ADMIN_ID)); } + @Test + public void testCreateInvalid() throws Exception { + Meal invalid = new Meal(null, null, "Dummy", 200); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(ADMIN))) + .andDo(print()) + .andExpect(status().isBadRequest()); + } + @Test public void testGetAll() throws Exception { mockMvc.perform(get(REST_URL) 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 108fbc1423f1..96684ed191dd 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -56,4 +56,15 @@ public void testUpdate() throws Exception { MATCHER.assertEquals(UserUtil.updateFromTo(new User(USER), updatedTo), userService.getByEmail("newemail@ya.ru")); } + + @Test + public void testUpdateInvalid() throws Exception { + UserTo updatedTo = new UserTo(null, null, "password", null, 1500); + + mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER)) + .content(JsonUtil.writeValue(updatedTo))) + .andDo(print()) + .andExpect(status().isBadRequest()); + } } \ No newline at end of file From ae32d533105b40f5dd1c7b35a3b61cb233adfee0 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:24:32 +0500 Subject: [PATCH 106/111] 11 02 HW10 email duplicate --- config/messages/app.properties | 4 +- config/messages/app_ru.properties | 4 +- .../topjava/web/RootController.java | 37 +++++++++++-------- .../topjava/web/user/AdminAjaxController.java | 19 ++++++++-- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index afefb4a71ed0..fa6dce9eed8a 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -41,4 +41,6 @@ common.saved=Saved common.enabled=Enabled common.disabled=Disabled common.failed=Failed -common.add=Add \ No newline at end of file +common.add=Add + +exception.duplicate_email=User with this email already present in application. \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 73204e2140d4..292326ee8e9a 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -41,4 +41,6 @@ common.saved=Записан common.enabled=Активирован common.disabled=Деактивирован common.failed=Ошибка -common.add=Добавить \ No newline at end of file +common.add=Добавить + +exception.duplicate_email=Пользователь с такой e-почтой уже есть в приложении. \ 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 20328ac1affc..df6a5d25c1f6 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.dao.DataIntegrityViolationException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; @@ -53,15 +54,18 @@ public String profile() { @PostMapping("/profile") public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status) { - if (result.hasErrors()) { - return "profile"; - } else { - userTo.setId(AuthorizedUser.id()); - super.update(userTo); - AuthorizedUser.get().update(userTo); - status.setComplete(); - return "redirect:meals"; + if (!result.hasErrors()) { + try { + userTo.setId(AuthorizedUser.id()); + super.update(userTo); + AuthorizedUser.get().update(userTo); + status.setComplete(); + return "redirect:meals"; + } catch (DataIntegrityViolationException ex) { + result.rejectValue("email", "exception.duplicate_email"); + } } + return "profile"; } @GetMapping("/register") @@ -73,13 +77,16 @@ public String register(ModelMap model) { @PostMapping("/register") public String saveRegister(@Valid UserTo userTo, BindingResult result, SessionStatus status, ModelMap model) { - if (result.hasErrors()) { - model.addAttribute("register", true); - return "profile"; - } else { - super.create(UserUtil.createNewFromTo(userTo)); - status.setComplete(); - return "redirect:login?message=app.registered"; + if (!result.hasErrors()) { + try { + super.create(UserUtil.createNewFromTo(userTo)); + status.setComplete(); + return "redirect:login?message=app.registered"; + } catch (DataIntegrityViolationException ex) { + result.rejectValue("email", "exception.duplicate_email"); + } } + model.addAttribute("register", true); + return "profile"; } } 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 0b50bfb4c910..7ebb0152e53d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -1,5 +1,9 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; @@ -16,6 +20,9 @@ @RequestMapping("/ajax/admin/users") public class AdminAjaxController extends AbstractUserController { + @Autowired + private MessageSource messageSource; + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { return super.getAll(); @@ -33,10 +40,14 @@ public void delete(@PathVariable("id") int id) { @PostMapping public void createOrUpdate(@Valid UserTo userTo) { - if (userTo.isNew()) { - super.create(UserUtil.createNewFromTo(userTo)); - } else { - super.update(userTo); + try { + if (userTo.isNew()) { + super.create(UserUtil.createNewFromTo(userTo)); + } else { + super.update(userTo); + } + } catch (DataIntegrityViolationException e) { + throw new DataIntegrityViolationException(messageSource.getMessage("exception.duplicate_email", null, LocaleContextHolder.getLocale())); } } From a6281341d1d78695bdb725d8d40b769a5e6537b7 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:26:32 +0500 Subject: [PATCH 107/111] 11 03 HW10 change locale --- src/main/resources/spring/spring-mvc.xml | 9 +++++ .../WEB-INF/jsp/fragments/bodyHeader.jsp | 29 +++++++++------- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 11 +++--- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 4 +-- .../webapp/WEB-INF/jsp/fragments/lang.jsp | 15 ++++++++ src/main/webapp/WEB-INF/jsp/login.jsp | 32 ++++++++++------- src/main/webapp/WEB-INF/jsp/profile.jsp | 16 ++++----- src/main/webapp/WEB-INF/jsp/users.jsp | 34 +++++++++---------- .../webapp/resources/js/mealDatatables.js | 2 ++ 9 files changed, 94 insertions(+), 58 deletions(-) create mode 100644 src/main/webapp/WEB-INF/jsp/fragments/lang.jsp diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 5ea4742d6a7d..f0360f900831 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -68,9 +68,18 @@ p:cacheSeconds="60" p:defaultEncoding="UTF-8"> + + + + + + + + + \ 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 e5e9cfd81bef..309bc48f70be 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,23 +1,28 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> \ No newline at end of file +
    diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp index 876244c1495f..867414d71630 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,16 +1,17 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - \ No newline at end of file + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 895ebb6a4da9..e9f8663c4a0e 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -1,5 +1,5 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> @@ -9,7 +9,7 @@ - <fmt:message key="app.title"/> + <spring:message code="app.title"/> diff --git a/src/main/webapp/WEB-INF/jsp/fragments/lang.jsp b/src/main/webapp/WEB-INF/jsp/fragments/lang.jsp new file mode 100644 index 000000000000..8880d9e1882f --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/fragments/lang.jsp @@ -0,0 +1,15 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/login.jsp b/src/main/webapp/WEB-INF/jsp/login.jsp index 4e77c0937de4..9a78c693266f 100644 --- a/src/main/webapp/WEB-INF/jsp/login.jsp +++ b/src/main/webapp/WEB-INF/jsp/login.jsp @@ -1,6 +1,6 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> @@ -8,17 +8,23 @@ @@ -32,7 +38,7 @@
    - +

    @@ -41,7 +47,7 @@

    Admin login: admin@gmail.com / admin

    -

    »

    +

    »

    Стек технологий: Spring Security, Spring MVC, Spring Data JPA, diff --git a/src/main/webapp/WEB-INF/jsp/profile.jsp b/src/main/webapp/WEB-INF/jsp/profile.jsp index 11577cc88ed7..2e2b13026c01 100644 --- a/src/main/webapp/WEB-INF/jsp/profile.jsp +++ b/src/main/webapp/WEB-INF/jsp/profile.jsp @@ -17,12 +17,12 @@

    - - + + - ${userTo.name} - + ${userTo.name} +

    @@ -30,16 +30,16 @@ - + - + - + - +
    diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index a9aca3988acb..94740c20792c 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -1,6 +1,6 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> @@ -13,19 +13,19 @@
    -

    +

    - ')"> + ')">
    - - - - - + + + + + @@ -49,32 +49,32 @@
    - +
    - "> + ">
    - +
    - "> + ">
    - +
    - "> + ">
    - +
    @@ -84,10 +84,8 @@ - - diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index c649b0600b41..2d9caadeecff 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -58,6 +58,8 @@ $(function () { "initComplete": makeEditable }); + $.datetimepicker.setLocale(localeCode); + var startDate = $('#startDate'); var endDate = $('#endDate'); startDate.datetimepicker({ From 3543f82d9d7adfacbf8995f1e74033070e9fbffc Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:27:57 +0500 Subject: [PATCH 108/111] 11 04 search i18n --- config/messages/app.properties | 2 +- config/messages/app_ru.properties | 2 +- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 2 +- src/main/webapp/resources/js/datatablesUtil.js | 18 ++++++++++++++++++ src/main/webapp/resources/js/mealDatatables.js | 10 ++-------- src/main/webapp/resources/js/userDatatables.js | 10 ++-------- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index fa6dce9eed8a..37fa6faefdc8 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -34,7 +34,7 @@ common.select=Select common.delete=Delete common.update=Update common.save=Save -common.cancel=Cancel +common.search=Search common.deleted=Deleted common.saved=Saved diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 292326ee8e9a..d75e0a32590c 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -34,7 +34,7 @@ common.select=Выбрать common.delete=Удалить common.update=Обновить common.save=Сохранить -common.cancel=Отмена +common.search=Искать common.deleted=Удален common.saved=Записан diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp index 867414d71630..f2bbffc338e8 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -9,7 +9,7 @@ diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index e0d0453a2b72..3d12cb93a2ac 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -13,6 +13,24 @@ function makeEditable() { }); } +// https://api.jquery.com/jquery.extend/#jQuery-extend-deep-target-object1-objectN +function extendsOpts(opts) { + $.extend(true, opts, + { + "ajax": { + "url": ajaxUrl, + "dataSrc": "" + }, + "paging": false, + "info": true, + "language": { + "search": i18n["common.search"] + } + } + ); + return opts; +} + function add(add_title) { $('#modalTitle').html(add_title); form.find(":input").val(""); diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index 2d9caadeecff..e815c757bd26 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -11,13 +11,7 @@ function updateTable() { } $(function () { - datatableApi = $('#datatable').DataTable({ - "ajax": { - "url": ajaxUrl, - "dataSrc": "" - }, - "paging": false, - "info": true, + datatableApi = $('#datatable').DataTable(extendsOpts({ "columns": [ { "data": "dateTime", @@ -56,7 +50,7 @@ $(function () { $(row).addClass(data.exceed ? 'exceeded' : 'normal'); }, "initComplete": makeEditable - }); + })); $.datetimepicker.setLocale(localeCode); diff --git a/src/main/webapp/resources/js/userDatatables.js b/src/main/webapp/resources/js/userDatatables.js index 3eb4e68bf74d..2f5556a94eac 100644 --- a/src/main/webapp/resources/js/userDatatables.js +++ b/src/main/webapp/resources/js/userDatatables.js @@ -6,13 +6,7 @@ function updateTable() { } $(function () { - datatableApi = $('#datatable').DataTable({ - "ajax": { - "url": ajaxUrl, - "dataSrc": "" - }, - "paging": false, - "info": true, + datatableApi = $('#datatable').DataTable(extendsOpts({ "columns": [ { "data": "name" @@ -70,5 +64,5 @@ $(function () { } }, "initComplete": makeEditable - }); + })); }); \ No newline at end of file From 200aba0d83a008b574e035127abf4886eb7eb3a4 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:28:51 +0500 Subject: [PATCH 109/111] 11 05 JsonView --- src/main/java/ru/javawebinar/topjava/View.java | 10 ++++++++++ src/main/java/ru/javawebinar/topjava/model/User.java | 3 +++ .../topjava/web/user/AdminAjaxController.java | 4 ++++ 3 files changed, 17 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/View.java diff --git a/src/main/java/ru/javawebinar/topjava/View.java b/src/main/java/ru/javawebinar/topjava/View.java new file mode 100644 index 000000000000..b72b5ab01549 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/View.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava; + +/** + * gkislin + * 04.08.2016 + */ +public class View { + public static class REST {} + public static class UI {} +} diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index b8863bb32e6d..dc537b8d766c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,12 +1,14 @@ package ru.javawebinar.topjava.model; import org.hibernate.annotations.BatchSize; +import com.fasterxml.jackson.annotation.JsonView; 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 org.springframework.util.CollectionUtils; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.util.UserUtil; import javax.persistence.*; @@ -42,6 +44,7 @@ public class User extends NamedEntity { @Column(name = "password", nullable = false) @NotEmpty @Length(min = 5) + @JsonView(View.REST.class) private String password; @Column(name = "enabled", nullable = false) 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 7ebb0152e53d..dc686ec69cc8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import com.fasterxml.jackson.annotation.JsonView; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; @@ -7,6 +8,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; @@ -24,11 +26,13 @@ public class AdminAjaxController extends AbstractUserController { private MessageSource messageSource; @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + @JsonView(View.UI.class) public List getAll() { return super.getAll(); } @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + @JsonView(View.UI.class) public User get(@PathVariable("id") int id) { return super.get(id); } From c6a3a72ae8bf9ce93f94fbf57983da1f20cf0027 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:30:12 +0500 Subject: [PATCH 110/111] 11 06 XSS --- pom.xml | 5 +++++ .../java/ru/javawebinar/topjava/model/Meal.java | 2 ++ .../javawebinar/topjava/model/NamedEntity.java | 2 ++ .../java/ru/javawebinar/topjava/model/User.java | 5 ++++- .../java/ru/javawebinar/topjava/to/UserTo.java | 4 ++++ .../web/meal/MealRestControllerTest.java | 17 ++++++++++++++--- 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 2737bff1cc42..c7297876cabb 100644 --- a/pom.xml +++ b/pom.xml @@ -223,6 +223,11 @@ hibernate-validator ${hibernate-validator.version} + + org.jsoup + jsoup + 1.10.1 + org.hibernate hibernate-ehcache diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 24216685a60b..4f0044144ec4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -2,6 +2,7 @@ import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; @@ -39,6 +40,7 @@ public class Meal extends BaseEntity { @Column(name = "description", nullable = false) @NotEmpty + @SafeHtml private String description; @Column(name = "calories", nullable = false) diff --git a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java index e593d37981e7..3246563c9a33 100644 --- a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.model; import org.hibernate.validator.constraints.NotEmpty; +import org.hibernate.validator.constraints.SafeHtml; import javax.persistence.Column; import javax.persistence.MappedSuperclass; @@ -14,6 +15,7 @@ public class NamedEntity extends BaseEntity { @NotEmpty @Column(name = "name", nullable = false) + @SafeHtml 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 dc537b8d766c..b0db4899619f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,12 +1,13 @@ package ru.javawebinar.topjava.model; -import org.hibernate.annotations.BatchSize; import com.fasterxml.jackson.annotation.JsonView; +import org.hibernate.annotations.BatchSize; 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 org.hibernate.validator.constraints.SafeHtml; import org.springframework.util.CollectionUtils; import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.util.UserUtil; @@ -39,12 +40,14 @@ public class User extends NamedEntity { @Column(name = "email", nullable = false, unique = true) @Email @NotEmpty + @SafeHtml private String email; @Column(name = "password", nullable = false) @NotEmpty @Length(min = 5) @JsonView(View.REST.class) + @SafeHtml private String password; @Column(name = "enabled", nullable = false) diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index 7e09cb90c757..e668a987ce73 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -3,6 +3,7 @@ import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import ru.javawebinar.topjava.util.UserUtil; import javax.validation.constraints.NotNull; @@ -15,13 +16,16 @@ public class UserTo implements Serializable { private Integer id; @NotEmpty + @SafeHtml private String name; @Email @NotEmpty + @SafeHtml private String email; @Size(min = 5, max = 64, message = " must between 5 and 64 characters") + @SafeHtml private String password; @Range(min = 100, max = 5000) diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 67d9420c1ada..8dd0df824150 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -11,6 +11,7 @@ import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; +import java.time.LocalDateTime; import java.util.Arrays; import static org.junit.Assert.assertEquals; @@ -19,10 +20,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.ADMIN; -import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; -import static ru.javawebinar.topjava.UserTestData.USER; +import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; public class MealRestControllerTest extends AbstractControllerTest { @@ -95,6 +95,17 @@ public void testUpdateInvalid() throws Exception { .andExpect(status().isBadRequest()); } + @Test + public void testUpdateHtmlUnsafe() throws Exception { + Meal invalid = new Meal(MEAL1_ID, LocalDateTime.now(), "", 200); + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isBadRequest()); + } + @Test public void testCreate() throws Exception { Meal created = getCreated(); From 079ada72aa3d92eda8a65a6e5638cf2b55998cb3 Mon Sep 17 00:00:00 2001 From: afarida Date: Mon, 16 Jan 2017 14:32:47 +0500 Subject: [PATCH 111/111] 11 07 heroku --- Procfile | 1 + hr.bat | 2 + pom.xml | 30 +++++++++ settings.xml | 11 ++++ .../java/ru/javawebinar/topjava/Profiles.java | 3 +- .../web/user/AbstractUserController.java | 24 ++++++- src/main/resources/db/heroku.properties | 5 ++ src/main/resources/spring/spring-db.xml | 24 +++++++ .../web/user/HerokuRestControllerTest.java | 62 +++++++++++++++++++ system.properties | 1 + 10 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 Procfile create mode 100644 hr.bat create mode 100644 settings.xml create mode 100644 src/main/resources/db/heroku.properties create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/HerokuRestControllerTest.java create mode 100644 system.properties diff --git a/Procfile b/Procfile new file mode 100644 index 000000000000..4afeb7900918 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: java $JAVA_OPTS -Dspring.profiles.active="datajpa,heroku" -DTOPJAVA_ROOT="." -jar target/dependency/webapp-runner.jar --port $PORT target/*.war \ No newline at end of file diff --git a/hr.bat b/hr.bat new file mode 100644 index 000000000000..1ea30584df86 --- /dev/null +++ b/hr.bat @@ -0,0 +1,2 @@ +call mvn -B -s settings.xml -DskipTests=true clean package +call java -Dspring.profiles.active="datajpa,heroku" -DDATABASE_URL="postgres://user:password@localhost:5432/topjava" -jar target/dependency/webapp-runner.jar target/*.war \ No newline at end of file diff --git a/pom.xml b/pom.xml index c7297876cabb..24f37b9defed 100644 --- a/pom.xml +++ b/pom.xml @@ -367,6 +367,36 @@ true + + heroku + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + package + + copy + + + + + com.github.jsimone + webapp-runner + 8.0.33.1 + webapp-runner.jar + + + + + + + + + diff --git a/settings.xml b/settings.xml new file mode 100644 index 000000000000..cb1c8aa69b1b --- /dev/null +++ b/settings.xml @@ -0,0 +1,11 @@ + + + + + + postgres + heroku + + diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index 4ce31d6f690a..c48e6fa2bcd6 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -10,7 +10,8 @@ public class Profiles { HSQLDB = "hsqldb", JDBC = "jdbc", JPA = "jpa", - DATAJPA = "datajpa"; + DATAJPA = "datajpa", + HEROKU = "heroku"; public static final String ACTIVE_DB = POSTGRES; public static final String DB_IMPLEMENTATION = DATAJPA; 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 54bf429d9338..af3e4276ee81 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -1,12 +1,17 @@ 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 org.springframework.core.env.Environment; +import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.model.BaseEntity; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; import ru.javawebinar.topjava.to.UserTo; +import javax.validation.ValidationException; +import java.util.Arrays; import java.util.List; /** @@ -18,6 +23,19 @@ public abstract class AbstractUserController { @Autowired private UserService service; + private boolean systemUserForbiddenModification; + + @Autowired + public void setEnvironment(Environment environment) { + systemUserForbiddenModification = Arrays.asList(environment.getActiveProfiles()).contains(Profiles.HEROKU); + } + + public void checkModificationAllowed(Integer id) { + if (systemUserForbiddenModification && id < BaseEntity.START_SEQ + 2) { + throw new ValidationException("Admin/User modification is not allowed.

    Register » your own please."); + } + } + public List getAll() { log.info("getAll"); return service.getAll(); @@ -35,17 +53,20 @@ public User create(User user) { } public void delete(int id) { + checkModificationAllowed(id); log.info("delete " + id); service.delete(id); } public void update(User user, int id) { + checkModificationAllowed(id); user.setId(id); log.info("update " + user); service.update(user); } public void update(UserTo userTo) { + checkModificationAllowed(userTo.getId()); log.info("update " + userTo); service.update(userTo); } @@ -56,6 +77,7 @@ public User getByMail(String email) { } public void enable(int id, boolean enabled) { + checkModificationAllowed(id); log.info((enabled ? "enable " : "disable ") + id); service.enable(id, enabled); } diff --git a/src/main/resources/db/heroku.properties b/src/main/resources/db/heroku.properties new file mode 100644 index 000000000000..c8146ba6f139 --- /dev/null +++ b/src/main/resources/db/heroku.properties @@ -0,0 +1,5 @@ +jpa.showSql=false +hibernate.format_sql=false +hibernate.use_sql_comments=false +database.init=false +jdbc.initLocation=initDB.sql \ 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 62e5d7a65f0c..77885a496c29 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -66,6 +66,30 @@ + + + + + + + + +