From 73651e099e89f997acf8059f9f1e79ff95c43be9 Mon Sep 17 00:00:00 2001 From: Grigory Kislin Date: Tue, 21 Feb 2017 16:28:00 +0300 Subject: [PATCH 01/91] Add description --- README.md | 161 ++++++++++++++++++++++++++++++++++++++++++++++++ ReleaseNotes.md | 82 ++++++++++++++++++++++++ cv.md | 83 +++++++++++++++++++++++++ description.md | 67 ++++++++++++++++++++ 4 files changed, 393 insertions(+) create mode 100644 README.md create mode 100644 ReleaseNotes.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..37f722cffed5 --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +Java Enterprise Online Project +=============================== +Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: +Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. + + Когда вы слышите что-то, вы забываете это. + Когда вы видите что-то, вы запоминаете это. + Но только когда вы начинаете делать это, + вы начинаете понимать это + + Старинная китайская поговорка + +## Описание и план проекта +### Демо разрабатываемого приложения +### [Изменения проекта (Release Notes)](ReleaseNotes.md) +### Требования к участникам, Wiki +### Составление резюме, подготовка к интервью, поиск работы + +Вводное занятие +=============== +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Осваиваем Java Enterprise. Трудоустройство. Ответы на вопросы. +- Слайды презентации +- Java Tools and Technologies Landscape for 2014 +- Java Tools and Technologies Landscape Report 2016 +- Из юниоров в разработчики: получаем первую работу + +#### Spring Pet-Clinic +- Spring PetClinic Sample Application +- Demo +- Presentation + +## ![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) +- Java 8: Lambda выражения +- Java 8: Потоки +- Pуководство по Java 8 Stream +- Лямбда-выражения в Java 8 +- A Guide to Java 8 +- Шпаргалка Java Stream API +- Алексея Владыкин: Элементы функционального программирования в Java +- Yakov Fain о новом в Java 8 +- stream.map vs forEach + +``` +Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) +``` + +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Замечания к HW0 +- Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно при правках в рабочем проекте обязятельно нужно проконсультироваться/проревьюится у авторов кода (находится по истории VCS) +- Наследовать `UserMealWithExceed` от `UserMeal` я не буду, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. +- Правильная реализация должна быть простой и красивой, можно сделать 2-мя способами: через стримы и через циклы. Сложность должна быть O(N), т.е. без вложенных стримов и циклов. +- При реализации через циклы посмотрите в `Map` на методы `getOrDefault` или `merge` +- При реализации через `Stream` заменяйте `forEach` оператором `stream.map(..)` +- Объявляйте переменные непосредственно перед использованием (если возможно - сразу с инициализацией). При объявлении коллекций используйте тип переменной - интерфейс (Map, List, ..) +- Если IDEA предлагает оптимизацию (желтым подчеркивает), нарпимер заменить лямбду на метод-референс, соглашайтесь (Alt+Enter) +- Пользуйтесь форматированием кода в IDEA: `Alt+Ctrl+L` +- Перед check-in проверяйте чендж-лист (курсор на файл и Ctrl+D): не оставляйте в коде ничего лишнего (закомментированный код, TODO и пр.). Если файл не меняется (например только пробелы или переводы строк), делайте ему revert. +- `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование. + +----- + +### Полезные ресурсы +> ВНИМАНИЕ: основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! + +Все остальное - опционально. + +#### HTML, JavaScript, CSS +- HTML, CSS, JAVASCRIPT, SQL, JQUERY, BOOTSTRAP +- Введение в программирование на JavaScript +- Стандарты кодирования для HTML, CSS и JavaScript’a +- Основы работы с HTML/CSS/JavaScript +- JavaScript - Основы +- Основы JavaScript +- Bootstrap 3 - Основы +- jQuery для начинающих + +#### Java (базовые вещи) +- Интуит. Программирование на Java +- 1й урок MasterJava: Многопоточность +- Основы Java garbage collection +- Размер Java объектов +- Введение в Java Reflection API +- Структуры данных в картинках +- Обзор java.util.concurrent.* +- Синхронизация потоков +- String literal pool +- Маленькие хитрости Java +- A Guide to Java 8 + +#### Сервлеты +- Как создать Servlet? Полное руководство. +- Простое CRUD приложение на JSP и сервлетах + +#### JDBC, SQL +- Основы SQL на примере задачи +- Уроки по JDBC +- Learn SQL +- Интуит. Основы 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/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 000000000000..5af9f258a2d9 --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,82 @@ +# TopJava Release Notes + +### Topjava 9 +- добавил + - выбор профиля базы через `ActiveProfilesResolver`/`AllActiveProfileResolver` на основе драйвера базы в classpath + - видео Cascade. Auto generate DDL. + - проверку на правильность id в Ajax/Rest контроллерах (treat IDs in REST body) + - тесты на валидацию входных значений контроллеров и зависимость на имплементацию + - Bootstrap Glyphicons +- рефакторинг + - переименовал `TimeUtil` в `DateTimeUtil` + - переименовал `ExceptionUtil` в `ValidationUtil` + - заменил валидацию `@NotEmpty` на `@NotBlank` + - заменил `CascadeType.REMOVE` на `@OnDelete` + - изменил `JdbcUserRepositoryImpl.getAll()` + - обновил jQuery до 3.x, исключил из зависимостей webjars ненужные jQuery + - cделал загрузку скриптов асинхронной + - фильтр еды сделал в [Bootstrap Panels](http://getbootstrap.com/components/#panels) + - вместо `Persistable` ввел интерфейс `HasId` и наследую от него как Entity, так и TO + +### Topjava 8 +- добавил: + - [защиту от XSS (Cross-Site Scripting)](http://stackoverflow.com/a/40644276/548473) + - интеграцию с Dependency Ci и Travis Ci + - локализацию календаря + - сводку по результатам тестов + - примеры запросов curl в `config/curl.md` + - DataTables/Bootstrap 3 integration + - тесты на профиль деплоя Heroku (общее количество JUnit тестов стало 102) +- удалил зависимость `jul-to-slf4j` +- рефакторинг + - переименовал все классы `UserMeal**` в `Meal**`, JSP + - переименовал `LoggedUser` в `AuthorizedUser` + - починил работа с PK Hibernate в случае ленивой загрузки (баг HHH-3718) + - поменял в `BaseEntity` `equals/hashCode/implements Persistable` + - в `InMemoryMealRepositoryImpl` выделил метод `getAllStream` + - перенес проверки пердусловий `Assert` из `InMemory` репозиториев в сервисы + - переименовал классы _Proxy*_ на более адекватные _Crud*_ + - поменял реализацию `JpaMealRepositoryImpl.get`, добавил в JPA модель `@BatchSize` + - вместо `@RequestMapping` ввел Spring 4.3 аннотации `@Get/Post/...Mapping` + - поменял авторизацию в тестах не-REST контроллеров + - перенес вызовы `UserUtil.prepareToSave` из `AbstractUserController` в `UserServiceImpl` + - зарефакторил обработку ошибок (`ExceptionInfoHandler`) + +### Topjava 7 +- добавил: + - [JPA 2.1 EntityGraph](https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs002.htm) + - [Jackson @JsonView](https://habrahabr.ru/post/307392/) + - валидацию объектов REST + - [i18n в JavaScript](http://stackoverflow.com/a/6242840/548473) + - проверку предусловий и видео Методы улучшения качества кода + - интеграцию с проверкой кода в Codacy + - [сравнение вермени исполнения запросов при различных meals индексах](https://drive.google.com/open?id=0B9Ye2auQ_NsFX3RLcnJCWmQ2Y0U) +- tomcat7-maven-plugin плагин перключили на Tomcat 8 (cargo-maven2-plugin) +- рефакторинг + - обработка ошибок сделал с array + - матчеров тестирования (сделал автоматические обертки и сравнение на основе передаваемого компаратора) + - вынес форматирование даты в `functions.tld` + +### Topjava 3-6 +- добавил + - [выпускной проект](https://drive.google.com/open?id=0B9Ye2auQ_NsFcG83dEVDVTVMamc) + - в таблицу meals составной индекс + - константы `Profiles.ACTIVE_DB`, `Profiles.DB_IMPLEMENTATION` + - проверки и тесты на `NotFound` для `UserMealService.getWithUser` и `UserService.getWithMeals` + - в MockMvc фильтр CharacterEncodingFilter + - защиту от межсайтовой подделки запроса, видео Межсайтовая подделка запроса (CSRF) + - ограничение на диапазон дат для фильтра еды +- рефакторинг + - UserMealsUtil, ProfileRestController, компараторов в репозитоии + - `LoggedUser` отнаследовал от `org.springframework.security.core.userdetails.User` + - переименовал `DbTest` в `AbstractServiceTest` и перенес сюда `@ActiveProfiles` + - сделал выполнение скриптов в тестах через аннотацию `@Sql` + - вместо использования id и селектора сделал обработчик `onclick` + - изменил формат ввода даты в форме без 'T' +- убрал + - `LoggerWrapper` + - Dandelion обертку к datatables +- обновил + - Hibernate до 5.x и Hibernate Validator, добавились новые зависимости и `jackson-datatype-hibernate5` + - datatables API (1.10) + - Postgres драйвер. Новый драйвер поддерживает Java 8 Time API, разделил реализацию JdbcMealRepositoryImpl на Java8 (Postgresql) и Timestamp (HSQL) diff --git a/cv.md b/cv.md new file mode 100644 index 000000000000..dd5874a3ee1a --- /dev/null +++ b/cv.md @@ -0,0 +1,83 @@ +## Составление резюме, подготовка к интервью, поиск работы + +![cv](https://cloud.githubusercontent.com/assets/13649199/10877471/93ea86b8-8157-11e5-9bfa-95e3fba75c58.jpg) + +- Научиться программировать сложнее, чем кажется + +### Составление резюме: +- Выбрать шаблон для резюме +- Идеальное резюме Junior’а +- Как продать свое резюме в 2 раза дороже +- Как правильно составить резюме +- Резюме программистов. Часть 1 (плохие) +- Резюме программистов. Часть 2 (хорошие) +- Как составить резюме на английском +- ОФОРМЛЕНИЕ IT-РЕЗЮМЕ для USA + +### Наши истории (делимся опытом и успехом) + +### Тесты/задачи онлайн: +- game: test Java skills +- Codility lesson tests +- Quizful- тесты онлайн +- Multiple-Choice Questions in Java +- LeetCode Online Judge +- Sphere online judge +- Codility programmers lessons +- Hackerrank practice coding + +### Интервью: +- Михаил Портнов. Собеседование на работу: как продать себя грамотно +- Михаил Портнов. Какие вопросы мы задаем на собеседовании? +- Михаил Портнов. Собеседование на работу: жизненный путь +- Канал: Резюме, поиск работы, интервью +- Яков Файн: Как стать профессиональным Java разработчиком +- Ответы на вопросы на собеседовании Junior Java Developer +- Список вопросов с ответами для собеседования по 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 0d6d69d47aa8408ce3810bd2638dd36ae2b2ac54 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 21 Feb 2017 19:45:39 +0300 Subject: [PATCH 02/91] Update description.md --- description.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/description.md b/description.md index cbb102fa72aa..895d3b155bfb 100644 --- a/description.md +++ b/description.md @@ -53,15 +53,18 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - jackson-datatype-hibernate. Тестирование через матчеры. - Тестирование через SoapUi. UTF-8 - WebJars. -- Bootstrap. Datatables. +- Bootstrap. jQuery datatables. - AJAX. jQuery. Notifications. - Spring Security - Spring Binding/Validation -- Работа с Datatables через Ajax. +- Работа с datatables через Ajax. - Spring Security Test - Encoding password - CSRF (добавление в проект защиты от межсайтовой подделки запроса) - form-login. Spring Security Taglib - Handler interceptor - Spring Exception Handling +- Смена локали +- Фильтрация JSON через @JsonView +- Защита от XSS (Cross Site Scripting) - Деплой в Heroku From eea2ea7efd92e520327d4fdc6c9615692b34b6d6 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 24 Feb 2017 01:43:19 +0300 Subject: [PATCH 03/91] Update ReleaseNotes.md --- ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 5af9f258a2d9..c430cbac8f69 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -17,6 +17,7 @@ - cделал загрузку скриптов асинхронной - фильтр еды сделал в [Bootstrap Panels](http://getbootstrap.com/components/#panels) - вместо `Persistable` ввел интерфейс `HasId` и наследую от него как Entity, так и TO + - сделал универсальную обработку исключений дублирования email и dateTime ### Topjava 8 - добавил: From 00ac1d73af70e8062d4c29d1dc67aa8b9b713459 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 24 Feb 2017 16:55:40 +0300 Subject: [PATCH 04/91] Update cv.md --- cv.md | 1 - 1 file changed, 1 deletion(-) diff --git a/cv.md b/cv.md index dd5874a3ee1a..7034da33b8a5 100644 --- a/cv.md +++ b/cv.md @@ -6,7 +6,6 @@ ### Составление резюме: - Выбрать шаблон для резюме -- Идеальное резюме Junior’а - Как продать свое резюме в 2 раза дороже - Как правильно составить резюме - Резюме программистов. Часть 1 (плохие) From 3dbfd5ee05b7e0f51051500c5f72e58ab37bb42b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 24 Feb 2017 19:04:39 +0300 Subject: [PATCH 05/91] Update cv.md --- cv.md | 1 - 1 file changed, 1 deletion(-) diff --git a/cv.md b/cv.md index 7034da33b8a5..8a731b5fd8c5 100644 --- a/cv.md +++ b/cv.md @@ -19,7 +19,6 @@ - game: test Java skills - Codility lesson tests - Quizful- тесты онлайн -- Multiple-Choice Questions in Java - LeetCode Online Judge - Sphere online judge - Codility programmers lessons From 408b9c4a47e55ec764b83f2f336ecb0a9d4ae3ea Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 25 Feb 2017 15:46:24 +0300 Subject: [PATCH 06/91] Update cv.md --- cv.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cv.md b/cv.md index 8a731b5fd8c5..09991593aa9c 100644 --- a/cv.md +++ b/cv.md @@ -16,6 +16,7 @@ ### Наши истории (делимся опытом и успехом) ### Тесты/задачи онлайн: +- [Java Programming Test](https://tests4geeks.com/java) - game: test Java skills - Codility lesson tests - Quizful- тесты онлайн From 9b673d501b8dbe6b1e38d586fc531ebf02654aec Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 9 Mar 2017 00:05:30 +0300 Subject: [PATCH 07/91] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 37f722cffed5..78785efd6f33 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + - Пользуйтесь форматированием кода в IDEA: `Alt+Ctrl+L` - Перед check-in проверяйте чендж-лист (курсор на файл и Ctrl+D): не оставляйте в коде ничего лишнего (закомментированный код, TODO и пр.). Если файл не меняется (например только пробелы или переводы строк), делайте ему revert. - `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование. +- Результаты, возвращаемые `UserMealsUtil.getFilteredWithExceeded` мы будем использовать [в нашем приложении](http://topjava.herokuapp.com/) для фильтрации по времени и отображения еды правильным цветом. ----- From c410a3fe6ed4c47e62415cfc3ce96afa662d56d5 Mon Sep 17 00:00:00 2001 From: Drag Date: Tue, 14 Mar 2017 14:43:08 +0200 Subject: [PATCH 08/91] Prepare to HW0 --- .gitignore | 4 +-- .../javawebinar/topjava/model/UserMeal.java | 33 +++++++++++++++++ .../topjava/model/UserMealWithExceed.java | 24 +++++++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 13 +++++++ .../topjava/util/UserMealsUtil.java | 35 +++++++++++++++++++ 5 files changed, 106 insertions(+), 3 deletions(-) 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/.gitignore b/.gitignore index 6ca05c4a5184..c7757bd0ebc7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,4 @@ out target *.iml log - - - +*.patch 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 988de5bb3f09b578d228d6f2304db83c7cae1ab4 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 23 Mar 2017 10:30:10 +0200 Subject: [PATCH 09/91] 1 1 HW1 stream --- .../model/{UserMeal.java => Meal.java} | 14 +++++- .../topjava/model/MealWithExceed.java | 34 ++++++++++++++ .../topjava/model/UserMealWithExceed.java | 24 ---------- .../javawebinar/topjava/util/MealsUtil.java | 47 +++++++++++++++++++ .../topjava/util/UserMealsUtil.java | 35 -------------- 5 files changed, 93 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..2bbfa96f9b97 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealWithExceed; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * GKislin + * 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 mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExceeded.forEach(System.out::println); + } + + 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 -> + new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), + caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } +} \ 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 5984edceaac0603942648c909ac9454792a89769 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 23 Mar 2017 10:40:09 +0200 Subject: [PATCH 10/91] 1 2 HW0 cycle --- .../javawebinar/topjava/util/MealsUtil.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 2bbfa96f9b97..d6476fc082ee 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -7,9 +7,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -28,6 +26,8 @@ public static void main(String[] args) { ); List mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); mealsWithExceeded.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) { @@ -39,9 +39,25 @@ public static List getFilteredWithExceeded(List meals, Loc return meals.stream() .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) - .map(meal -> - new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), - caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .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 mealsWithExceeded = new ArrayList<>(); + meals.forEach(meal -> { + if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { + mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + }); + return mealsWithExceeded; + } + + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } } \ No newline at end of file From 270fbb80909202ea162d43eab476d63ca1658789 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 23 Mar 2017 10:48:32 +0200 Subject: [PATCH 11/91] 1 3 switch to war --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 18 ++++++++++++++++++ src/main/webapp/index.html | 14 ++++++++++++++ src/main/webapp/users.jsp | 10 ++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index c8a1c78f3b29..6f0e7fd7cf47 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..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..056f090e4f9b --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,14 @@ + + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

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

Home

+

User list

+ + From efaa6d2af31c0d5f0dbcdad8a5cb10b27b37fea1 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 23 Mar 2017 12:45:11 +0200 Subject: [PATCH 12/91] 1 4 add servlet api --- pom.xml | 8 ++++++++ .../javawebinar/topjava/web/UserServlet.java | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/pom.xml b/pom.xml index 6f0e7fd7cf47..1eae3f7c442b 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,14 @@ + + + + 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..e22204115907 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,18 @@ +package ru.javawebinar.topjava.web; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class UserServlet extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.getRequestDispatcher("/users.jsp").forward(request, response); + } +} From 8f82bdbbc07909cc696c52e0fecfdc5af2a05e1b Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 23 Mar 2017 12:54:33 +0200 Subject: [PATCH 13/91] 1 5 forward to redirect --- pom.xml | 2 +- src/main/java/ru/javawebinar/topjava/web/UserServlet.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1eae3f7c442b..619913341a69 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ topjava - install + package org.apache.maven.plugins diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index e22204115907..1e11c6157e28 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -13,6 +13,7 @@ public class UserServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - request.getRequestDispatcher("/users.jsp").forward(request, response); +// request.getRequestDispatcher("/users.jsp").forward(request, response); + response.sendRedirect("users.jsp"); } } From bdf5f131501366e76831fcce25641a4ef3a39769 Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 24 Mar 2017 13:26:50 +0200 Subject: [PATCH 14/91] 1 6 logging --- pom.xml | 25 ++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 8 +++++ src/main/resources/logback.xml | 29 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 619913341a69..68c2a884063a 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ 1.8 UTF-8 UTF-8 + + + 1.2.2 + 1.7.25 @@ -34,6 +38,27 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 1e11c6157e28..3371178a94f6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,18 +1,26 @@ package ru.javawebinar.topjava.web; +import org.slf4j.Logger; + import 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 users"); + // request.getRequestDispatcher("/users.jsp").forward(request, response); response.sendRedirect("users.jsp"); } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..e9b900b26669 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From f9cfe7016fd14d24bd43c180527f3e39ed50d3b7 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 30 Mar 2017 12:01:30 +0300 Subject: [PATCH 15/91] 2 1 HW1 --- pom.xml | 6 +++ .../topjava/model/MealWithExceed.java | 16 +++++++ .../topjava/util/DateTimeUtil.java | 21 +++++++++ .../javawebinar/topjava/util/MealsUtil.java | 31 ++++++++----- .../ru/javawebinar/topjava/util/TimeUtil.java | 13 ------ .../javawebinar/topjava/web/MealServlet.java | 26 +++++++++++ .../javawebinar/topjava/web/UserServlet.java | 7 ++- 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/meals.jsp | 46 +++++++++++++++++++ 11 files changed, 164 insertions(+), 29 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/meals.jsp diff --git a/pom.xml b/pom.xml index 68c2a884063a..964d8f8f235e 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,12 @@ 3.1.0 provided + + + javax.servlet + jstl + 1.2 + diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index d14128b1f61f..ca27ce04b6d9 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -22,6 +22,22 @@ public MealWithExceed(LocalDateTime dateTime, String description, int calories, this.exceed = exceed; } + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExceed() { + return exceed; + } + @Override public String toString() { return "UserMealWithExceed{" + diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java new file mode 100644 index 000000000000..1a0629a94589 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +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 DateTimeUtil { + 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 String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index d6476fc082ee..6f518ddda693 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -15,19 +15,26 @@ * 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 mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + List mealsWithExceeded = getFilteredWithExceeded(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); mealsWithExceeded.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(List meals, int caloriesPerDay) { + return getFilteredWithExceeded(meals, LocalTime.MIN, LocalTime.MAX, caloriesPerDay); } public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -38,7 +45,7 @@ public static List getFilteredWithExceeded(List meals, Loc ); return meals.stream() - .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .filter(meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)) .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) .collect(Collectors.toList()); } @@ -50,7 +57,7 @@ public static List getFilteredWithExceededByCycle(List mea final List mealsWithExceeded = new ArrayList<>(); meals.forEach(meal -> { - if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { + if (DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)) { mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); } }); diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java deleted file mode 100644 index 02399b7288cf..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ /dev/null @@ -1,13 +0,0 @@ -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/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java new file mode 100644 index 000000000000..1bcbbeaab24e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.util.MealsUtil; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class MealServlet extends HttpServlet { + private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + LOG.info("getAll"); + request.setAttribute("meals", MealsUtil.getWithExceeded(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 3371178a94f6..07ede01d611e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -18,10 +18,9 @@ public class UserServlet extends HttpServlet { private static final Logger LOG = getLogger(UserServlet.class); + @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - LOG.debug("redirect to users"); - -// request.getRequestDispatcher("/users.jsp").forward(request, response); - response.sendRedirect("users.jsp"); + LOG.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); } } diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld new file mode 100644 index 000000000000..d138fecdbfb5 --- /dev/null +++ b/src/main/webapp/WEB-INF/tld/functions.tld @@ -0,0 +1,16 @@ + + + + 1.0 + functions + http://topjava.javawebinar.ru/functions + + + formatDateTime + ru.javawebinar.topjava.util.DateTimeUtil + java.lang.String toString(java.time.LocalDateTime) + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 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 056f090e4f9b..a04f2d537f14 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/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 000000000000..03e2e8b69409 --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,46 @@ +<%@ 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

    +
    + + + + + + + + + + + + + + + + +
    DateDescriptionCalories
    + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}
    +
    + + \ No newline at end of file From b1fb0913dfc9509c447f862ec2cd9b677552e1be Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 30 Mar 2017 12:02:04 +0300 Subject: [PATCH 16/91] 2 2 HW1 optional --- .../ru/javawebinar/topjava/model/Meal.java | 29 +++++++++ .../topjava/model/MealWithExceed.java | 14 +++- .../InMemoryMealRepositoryImpl.java | 47 ++++++++++++++ .../topjava/repository/MealRepository.java | 19 ++++++ .../javawebinar/topjava/util/MealsUtil.java | 6 +- .../javawebinar/topjava/web/MealServlet.java | 65 +++++++++++++++++-- src/main/webapp/meal.jsp | 51 +++++++++++++++ src/main/webapp/meals.jsp | 5 ++ 8 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/MealRepository.java create mode 100644 src/main/webapp/meal.jsp 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 ca27ce04b6d9..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,13 +17,18 @@ 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; } @@ -40,8 +47,9 @@ public boolean isExceed() { @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/repository/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java new file mode 100644 index 000000000000..20ca8168757e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 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/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/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 6f518ddda693..bceab4d19fa7 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -33,11 +33,11 @@ public static void main(String[] args) { System.out.println(getFilteredWithExceededByCycle(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY)); } - public static List getWithExceeded(List meals, int caloriesPerDay) { + 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)) @@ -65,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/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 1bcbbeaab24e..7daedb9059be 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,13 +2,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.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.time.temporal.ChronoUnit; +import java.util.Objects; /** * User: gkislin @@ -17,10 +24,60 @@ 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(); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String id = request.getParameter("id"); + + Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); + + LOG.info(meal.isNew() ? "Create {}" : "Update {}", meal); + repository.save(meal); + response.sendRedirect("meals"); + } + @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - LOG.info("getAll"); - request.setAttribute("meals", MealsUtil.getWithExceeded(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); - request.getRequestDispatcher("/meals.jsp").forward(request, response); + String action = request.getParameter("action"); + + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); + LOG.info("Delete {}", id); + repository.delete(id); + response.sendRedirect("meals"); + break; + case "create": + case "update": + final Meal meal = action.equals("create") ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : + repository.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/meal.jsp").forward(request, response); + break; + case "all": + default: + LOG.info("getAll"); + request.setAttribute("meals", + MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.valueOf(paramId); } -} +} \ No newline at end of file diff --git a/src/main/webapp/meal.jsp b/src/main/webapp/meal.jsp new file mode 100644 index 000000000000..7541b419caf7 --- /dev/null +++ b/src/main/webapp/meal.jsp @@ -0,0 +1,51 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
    +

    Home

    +

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

    +
    + +
    + +
    +
    DateTime:
    +
    +
    +
    +
    Description:
    +
    +
    +
    +
    Calories:
    +
    +
    + + +
    +
    + + diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 03e2e8b69409..45b470903183 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -19,6 +19,7 @@

    Home

    Meal list

    + Add Meal
    @@ -26,6 +27,8 @@ + + @@ -38,6 +41,8 @@ + +
    Date Description Calories
    ${meal.description} ${meal.calories}UpdateDelete
    From 90f34f7d04db610efa5ff82de09122667c742025 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 30 Mar 2017 14:04:03 +0300 Subject: [PATCH 17/91] 2 3 app layers --- .../javawebinar/topjava/AuthorizedUser.java | 14 +++ .../javawebinar/topjava/model/BaseEntity.java | 24 +++++ .../topjava/model/NamedEntity.java | 27 ++++++ .../ru/javawebinar/topjava/model/Role.java | 6 ++ .../ru/javawebinar/topjava/model/User.java | 94 +++++++++++++++++++ .../topjava/repository/UserRepository.java | 20 ++++ .../topjava/service/MealService.java | 4 + .../topjava/service/MealServiceImpl.java | 9 ++ .../topjava/service/UserService.java | 22 +++++ .../topjava/service/UserServiceImpl.java | 45 +++++++++ .../topjava/util/ValidationUtil.java | 42 +++++++++ .../util/exception/NotFoundException.java | 7 ++ .../topjava/web/meal/MealRestController.java | 8 ++ .../web/user/AbstractUserController.java | 49 ++++++++++ .../topjava/web/user/AdminRestController.java | 38 ++++++++ .../web/user/ProfileRestController.java | 19 ++++ 16 files changed, 428 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/AuthorizedUser.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/BaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/NamedEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/Role.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/User.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/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/ValidationUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java new file mode 100644 index 000000000000..177a05931540 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -0,0 +1,14 @@ +package ru.javawebinar.topjava; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class AuthorizedUser { + + public static int id() { + return 1; + } + + public static int getCaloriesPerDay() { + return DEFAULT_CALORIES_PER_DAY; + } +} \ No newline at end of file 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..f15234bd6353 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.model; + +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); + } +} \ No newline at end of file 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..820df2de5088 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.model; + +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; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/Role.java b/src/main/java/ru/javawebinar/topjava/model/Role.java new file mode 100644 index 000000000000..84d62071ad9c --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -0,0 +1,6 @@ +package ru.javawebinar.topjava.model; + +public enum Role { + ROLE_USER, + ROLE_ADMIN +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java new file mode 100644 index 000000000000..f0971e1ea1ed --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,94 @@ +package ru.javawebinar.topjava.model; + +import java.util.Date; +import java.util.EnumSet; +import java.util.Set; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class User extends NamedEntity { + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public User() { + } + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + } + + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + super(id, name); + this.email = email; + this.password = password; + this.caloriesPerDay = caloriesPerDay; + this.enabled = enabled; + this.roles = roles; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getRegistered() { + return registered; + } + + public void setRegistered(Date registered) { + this.registered = registered; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getCaloriesPerDay() { + return caloriesPerDay; + } + + public void setCaloriesPerDay(int caloriesPerDay) { + this.caloriesPerDay = caloriesPerDay; + } + + public boolean isEnabled() { + return enabled; + } + + public Set getRoles() { + return roles; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "User (" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + + ')'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java new file mode 100644 index 000000000000..c37b84d5fd77 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +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(); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 000000000000..b63fc9b1df00 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,4 @@ +package ru.javawebinar.topjava.service; + +public interface MealService { +} \ 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 new file mode 100644 index 000000000000..9017380f392b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.repository.MealRepository; + +public class MealServiceImpl implements MealService { + + private MealRepository repository; + +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java new file mode 100644 index 000000000000..b48ae667efab --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.service; + + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +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); +} \ 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 new file mode 100644 index 000000000000..fb632cf8c024 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +public class UserServiceImpl implements UserService { + + private UserRepository repository; + + @Override + public User save(User user) { + return repository.save(user); + } + + @Override + public void delete(int id) { + checkNotFoundWithId(repository.delete(id), id); + } + + @Override + public User get(int id) throws NotFoundException { + return checkNotFoundWithId(repository.get(id), id); + } + + @Override + public User getByEmail(String email) throws NotFoundException { + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + + @Override + public List getAll() { + return repository.getAll(); + } + + @Override + public void update(User user) { + repository.save(user); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java new file mode 100644 index 000000000000..cd702f58b852 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.util; + + +import ru.javawebinar.topjava.model.BaseEntity; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +public class ValidationUtil { + + 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); + } + } + + public static void checkNew(BaseEntity entity) { + if (!entity.isNew()) { + throw new IllegalArgumentException(entity + " must be new (id=null)"); + } + } + + public static void checkIdConsistent(BaseEntity entity, int id) { +// http://stackoverflow.com/a/32728226/548473 + if (entity.isNew()) { + entity.setId(id); + } else if (entity.getId() != id) { + throw new IllegalArgumentException(entity + " must be with id=" + id); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java new file mode 100644 index 000000000000..f1e9b0e46376 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.util.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java new file mode 100644 index 000000000000..ab4e8ea8bb8e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.web.meal; + +import ru.javawebinar.topjava.service.MealService; + +public class MealRestController { + private MealService service; + +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java new file mode 100644 index 000000000000..5ba6545cb77b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,49 @@ +package ru.javawebinar.topjava.web.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.UserService; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +public abstract class AbstractUserController { + protected final Logger LOG = LoggerFactory.getLogger(getClass()); + + private UserService service; + + public List getAll() { + LOG.info("getAll"); + return service.getAll(); + } + + public User get(int id) { + LOG.info("get " + id); + return service.get(id); + } + + public User create(User user) { + LOG.info("create " + user); + checkNew(user); + return service.save(user); + } + + public void delete(int id) { + LOG.info("delete " + id); + service.delete(id); + } + + public void update(User user, int id) { + LOG.info("update " + user); + checkIdConsistent(user, id); + service.update(user); + } + + public User getByMail(String email) { + LOG.info("getByEmail " + email); + return service.getByEmail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java new file mode 100644 index 000000000000..ae3374468e41 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +public class AdminRestController extends AbstractUserController { + + @Override + public List getAll() { + return super.getAll(); + } + + @Override + public User get(int id) { + return super.get(id); + } + + @Override + public User create(User user) { + return super.create(user); + } + + @Override + public void delete(int id) { + super.delete(id); + } + + @Override + public void update(User user, int id) { + super.update(user, id); + } + + @Override + public User getByMail(String email) { + return super.getByMail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java new file mode 100644 index 000000000000..865f4c015d8d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.AuthorizedUser; +import ru.javawebinar.topjava.model.User; + +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 From ed913a7fa7e4bd66fa8fa839eb1a99144a2afd7d Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 1 Apr 2017 12:13:22 +0300 Subject: [PATCH 18/91] 2 4 add spring context --- pom.xml | 15 +++++++ .../ru/javawebinar/topjava/SpringMain.java | 19 ++++++++ .../InMemoryMealRepositoryImpl.java | 3 +- .../mock/MockUserRepositoryImpl.java | 43 +++++++++++++++++++ .../javawebinar/topjava/web/MealServlet.java | 2 +- src/main/resources/spring/spring-app.xml | 7 +++ 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.java rename src/main/java/ru/javawebinar/topjava/repository/{ => mock}/InMemoryMealRepositoryImpl.java (90%) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java create mode 100644 src/main/resources/spring/spring-app.xml diff --git a/pom.xml b/pom.xml index 964d8f8f235e..711746d72c41 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 4.3.7.RELEASE + 1.2.2 1.7.25 @@ -60,6 +62,19 @@ runtime + + + org.springframework + spring-context + ${spring.version} + + + commons-logging + commons-logging + + + + javax.servlet 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..bb4a84fa88b4 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Arrays; + +public class SpringMain { + public static void main(String[] args) { + ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + +// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); + UserRepository userRepository = appCtx.getBean(UserRepository.class); + userRepository.getAll(); + appCtx.close(); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java similarity index 90% rename from src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java rename to src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 20ca8168757e..f6ead5176644 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -1,6 +1,7 @@ -package ru.javawebinar.topjava.repository; +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; 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..a1f78f381e64 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Collections; +import java.util.List; + +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 7daedb9059be..d3c0b7cb7345 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -3,7 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.InMemoryMealRepositoryImpl; +import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..e2e1f0314c0d --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file From 8feb2d07b9493aa5d651f8503c8b042180835931 Mon Sep 17 00:00:00 2001 From: Drag Date: Sun, 2 Apr 2017 13:12:18 +0300 Subject: [PATCH 19/91] 2 5 dependency injection --- src/main/java/ru/javawebinar/topjava/SpringMain.java | 7 +++++++ .../ru/javawebinar/topjava/service/UserServiceImpl.java | 4 ++++ src/main/resources/spring/spring-app.xml | 3 +++ 3 files changed, 14 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index bb4a84fa88b4..06730866877b 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -2,7 +2,10 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.service.UserService; import java.util.Arrays; @@ -14,6 +17,10 @@ public static void main(String[] args) { // UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); UserRepository userRepository = appCtx.getBean(UserRepository.class); userRepository.getAll(); + + UserService userService = appCtx.getBean(UserService.class); + userService.save(new User(null, "userName", "email", "password", Role.ROLE_ADMIN)); + appCtx.close(); } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index fb632cf8c024..115809a3a55e 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -13,6 +13,10 @@ public class UserServiceImpl implements UserService { private UserRepository repository; + public void setRepository(UserRepository repository) { + this.repository = repository; + } + @Override public User save(User user) { return repository.save(user); diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index e2e1f0314c0d..7b4fa1689425 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -4,4 +4,7 @@ + + + \ No newline at end of file From 70fbf5069a35b1724fcc4190f38876206bf5fd21 Mon Sep 17 00:00:00 2001 From: Drag Date: Sun, 2 Apr 2017 13:13:15 +0300 Subject: [PATCH 20/91] 2 6 annotation processing --- .../ru/javawebinar/topjava/SpringMain.java | 20 ++++++---------- .../mock/MockUserRepositoryImpl.java | 2 ++ .../topjava/service/UserServiceImpl.java | 8 +++---- .../web/user/AbstractUserController.java | 2 ++ .../topjava/web/user/AdminRestController.java | 2 ++ .../web/user/ProfileRestController.java | 2 ++ src/main/resources/spring/spring-app.xml | 23 +++++++++++++++---- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 06730866877b..9fa857a7c46a 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -4,23 +4,17 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.UserRepository; -import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.web.user.AdminRestController; import java.util.Arrays; public class SpringMain { public static void main(String[] args) { - ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); - System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); - -// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); - UserRepository userRepository = appCtx.getBean(UserRepository.class); - userRepository.getAll(); - - UserService userService = appCtx.getBean(UserService.class); - userService.save(new User(null, "userName", "email", "password", Role.ROLE_ADMIN)); - - appCtx.close(); + // java 7 Automatic resource management + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(null, "userName", "email", "password", Role.ROLE_ADMIN)); + } } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java index a1f78f381e64..f3704e62c2fc 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -2,12 +2,14 @@ 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; +@Repository public class MockUserRepositoryImpl implements UserRepository { private static final Logger LOG = LoggerFactory.getLogger(MockUserRepositoryImpl.class); diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 115809a3a55e..e4dd5ce6b5af 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 ru.javawebinar.topjava.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -9,14 +11,12 @@ import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; +@Service public class UserServiceImpl implements UserService { + @Autowired private UserRepository repository; - public void setRepository(UserRepository repository) { - this.repository = repository; - } - @Override public User save(User user) { return repository.save(user); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index 5ba6545cb77b..b1cf8b23e56e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.beans.factory.annotation.Autowired; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.User; @@ -13,6 +14,7 @@ public abstract class AbstractUserController { protected final Logger LOG = LoggerFactory.getLogger(getClass()); + @Autowired private UserService service; public List getAll() { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index ae3374468e41..b37a8ed6c8a5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,9 +1,11 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.model.User; import java.util.List; +@Controller public class AdminRestController extends AbstractUserController { @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index 865f4c015d8d..eb4c0f3f3ada 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,8 +1,10 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; +@Controller public class ProfileRestController extends AbstractUserController { public User get() { diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 7b4fa1689425..96e40e902b66 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -1,10 +1,23 @@ + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - + + + + + + + + + - - - \ No newline at end of file From 88089c40b463a9b6cb1b6894a86239ddcc79244e Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 6 Apr 2017 20:53:05 +0300 Subject: [PATCH 21/91] 3 0 1 correction --- .../repository/mock/MockUserRepositoryImpl.java | 8 ++++---- .../javawebinar/topjava/service/UserServiceImpl.java | 2 +- .../ru/javawebinar/topjava/util/DateTimeUtil.java | 6 +----- .../java/ru/javawebinar/topjava/web/MealServlet.java | 8 ++------ .../topjava/web/user/AbstractUserController.java | 12 ++++++------ 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java index f3704e62c2fc..93f4a193fd7e 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -15,19 +15,19 @@ public class MockUserRepositoryImpl implements UserRepository { @Override public boolean delete(int id) { - LOG.info("delete " + id); + LOG.info("delete {}", id); return true; } @Override public User save(User user) { - LOG.info("save " + user); + LOG.info("save {}", user); return user; } @Override public User get(int id) { - LOG.info("get " + id); + LOG.info("get {}", id); return null; } @@ -39,7 +39,7 @@ public List getAll() { @Override public User getByEmail(String email) { - LOG.info("getByEmail " + email); + LOG.info("getByEmail {}", email); return null; } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index e4dd5ce6b5af..dd121d8ec1b0 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -23,7 +23,7 @@ public User save(User user) { } @Override - public void delete(int id) { + public void delete(int id) throws NotFoundException { checkNotFoundWithId(repository.delete(id), id); } diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 1a0629a94589..5de28849657a 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -4,12 +4,8 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; -/** - * GKislin - * 07.01.2015. - */ public class DateTimeUtil { - public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + private 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; diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index d3c0b7cb7345..ddb705b459e3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -3,8 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.ServletConfig; @@ -17,10 +17,6 @@ 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); @@ -60,7 +56,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t break; case "create": case "update": - final Meal meal = action.equals("create") ? + final Meal meal = "create".equals(action) ? new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : repository.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 b1cf8b23e56e..c978d15fe6e1 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -1,8 +1,8 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.beans.factory.annotation.Autowired; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; @@ -23,29 +23,29 @@ public List getAll() { } public User get(int id) { - LOG.info("get " + id); + LOG.info("get {}", id); return service.get(id); } public User create(User user) { - LOG.info("create " + user); + LOG.info("create {}", user); checkNew(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) { - LOG.info("update " + user); + LOG.info("update {}", user); checkIdConsistent(user, id); service.update(user); } public User getByMail(String email) { - LOG.info("getByEmail " + email); + LOG.info("getByEmail {}", email); return service.getByEmail(email); } } \ No newline at end of file From d63cf68c930921b18ab153ecc284c3c5a3df6f40 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 6 Apr 2017 20:54:04 +0300 Subject: [PATCH 22/91] 3 0 2 constructor di --- .../javawebinar/topjava/service/UserServiceImpl.java | 10 ++++++---- .../topjava/web/user/AbstractUserController.java | 8 +++++--- .../topjava/web/user/AdminRestController.java | 7 +++++++ .../topjava/web/user/ProfileRestController.java | 7 +++++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index dd121d8ec1b0..6b540886e866 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,8 +1,7 @@ package ru.javawebinar.topjava.service; -import ru.javawebinar.topjava.model.User; -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.NotFoundException; @@ -14,8 +13,11 @@ @Service public class UserServiceImpl implements UserService { - @Autowired - private UserRepository repository; + private final UserRepository repository; + + public UserServiceImpl(UserRepository repository) { + this.repository = repository; + } @Override public User save(User user) { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index c978d15fe6e1..b97de4a998e3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -2,7 +2,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; @@ -14,8 +13,11 @@ public abstract class AbstractUserController { protected final Logger LOG = LoggerFactory.getLogger(getClass()); - @Autowired - private UserService service; + private final UserService service; + + public AbstractUserController(UserService service) { + this.service = service; + } public List getAll() { LOG.info("getAll"); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index b37a8ed6c8a5..aba649e40542 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,13 +1,20 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.UserService; import java.util.List; @Controller public class AdminRestController extends AbstractUserController { + @Autowired + public AdminRestController(UserService service) { + super(service); + } + @Override public List getAll() { return super.getAll(); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index eb4c0f3f3ada..528327938972 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,12 +1,19 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.UserService; @Controller public class ProfileRestController extends AbstractUserController { + @Autowired + public ProfileRestController(UserService service) { + super(service); + } + public User get() { return super.get(AuthorizedUser.id()); } From b89213e288ec178466297aad2039b35aceb8614b Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 6 Apr 2017 22:08:21 +0300 Subject: [PATCH 23/91] 3 01 HW2 repository --- .../topjava/repository/MealRepository.java | 18 +++-- .../mock/InMemoryMealRepositoryImpl.java | 65 ++++++++++++++----- .../mock/InMemoryUserRepositoryImpl.java | 60 +++++++++++++++++ .../mock/MockUserRepositoryImpl.java | 45 ------------- .../topjava/util/DateTimeUtil.java | 5 +- .../javawebinar/topjava/web/MealServlet.java | 9 +-- 6 files changed, 130 insertions(+), 72 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..2e5864b39816 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -2,18 +2,26 @@ import ru.javawebinar.topjava.model.Meal; -import java.util.Collection; +import java.time.LocalDateTime; +import java.util.List; /** * GKislin * 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 + List getAll(int userId); + + // ORDERED dateTime + List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); } 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..908c741bf0b5 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -2,47 +2,82 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.DateTimeUtil; import ru.javawebinar.topjava.util.MealsUtil; -import java.util.Collection; +import java.time.LocalDateTime; +import java.time.Month; +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; +import java.util.stream.Stream; + +import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.ADMIN_ID; +import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; -/** - * GKislin - * 15.09.2015. - */ 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); + if (meal.isNew()) { meal.setId(counter.incrementAndGet()); + } else if (get(meal.getId(), userId) == null) { + return null; } - repository.put(meal.getId(), meal); + Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); + meals.put(meal.getId(), 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) { - return repository.get(id); + public Meal get(int id, int userId) { + Map meals = repository.get(userId); + return meals == null ? null : meals.get(id); } @Override - public Collection getAll() { - return repository.values(); + public List getAll(int userId) { + return getAllAsStream(userId).collect(Collectors.toList()); + } + + @Override + public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Objects.requireNonNull(startDateTime); + Objects.requireNonNull(endDateTime); + return getAllAsStream(userId) + .filter(um -> DateTimeUtil.isBetween(um.getDateTime(), startDateTime, endDateTime)) + .collect(Collectors.toList()); + } + + private Stream getAllAsStream(int userId) { + Map meals = repository.get(userId); + return meals == null ? + Stream.empty() : meals.values().stream().sorted(MEAL_COMPARATOR); } } 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..f570752f6840 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -0,0 +1,60 @@ +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; + +@Repository +public class InMemoryUserRepositoryImpl implements UserRepository { + + private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName).thenComparing(User::getEmail); + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + @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 93f4a193fd7e..000000000000 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ /dev/null @@ -1,45 +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; - -@Repository -public class MockUserRepositoryImpl implements UserRepository { - private static final Logger LOG = LoggerFactory.getLogger(MockUserRepositoryImpl.class); - - @Override - public boolean delete(int id) { - LOG.info("delete {}", id); - return true; - } - - @Override - public User save(User user) { - LOG.info("save {}", user); - return user; - } - - @Override - public User get(int id) { - LOG.info("get {}", id); - return null; - } - - @Override - public List getAll() { - LOG.info("getAll"); - return Collections.emptyList(); - } - - @Override - public User getByEmail(String email) { - LOG.info("getByEmail {}", email); - return null; - } -} diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 5de28849657a..fd4cda764374 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,14 +1,13 @@ package ru.javawebinar.topjava.util; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class DateTimeUtil { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; + public static > boolean isBetween(T value, T start, T end) { + return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; } public static String toString(LocalDateTime ldt) { diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index ddb705b459e3..a6cd9de6a908 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.MealRepository; import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; @@ -39,7 +40,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,14 +52,14 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t case "delete": int id = getId(request); LOG.info("Delete {}", id); - repository.delete(id); + repository.delete(id, AuthorizedUser.id()); response.sendRedirect("meals"); break; case "create": case "update": final Meal meal = "create".equals(action) ? new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : - repository.get(getId(request)); + repository.get(getId(request), AuthorizedUser.id()); request.setAttribute("meal", meal); request.getRequestDispatcher("/meal.jsp").forward(request, response); break; @@ -66,7 +67,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t default: LOG.info("getAll"); request.setAttribute("meals", - MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + MealsUtil.getWithExceeded(repository.getAll(AuthorizedUser.id()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } From 5e3e02d5e748edb454cdb61d22d80f99d6c31d92 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 6 Apr 2017 22:09:09 +0300 Subject: [PATCH 24/91] 3 02 HW2 meal layers --- .../ru/javawebinar/topjava/SpringMain.java | 14 ++++ .../ru/javawebinar/topjava/model/Meal.java | 18 +---- .../mock/InMemoryMealRepositoryImpl.java | 2 + .../topjava/service/MealService.java | 23 ++++++ .../topjava/service/MealServiceImpl.java | 45 +++++++++++- .../topjava/{model => to}/MealWithExceed.java | 2 +- .../topjava/util/DateTimeUtil.java | 4 ++ .../javawebinar/topjava/util/MealsUtil.java | 6 +- .../topjava/web/meal/MealRestController.java | 71 ++++++++++++++++++- src/main/webapp/meals.jsp | 2 +- 10 files changed, 162 insertions(+), 25 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 9fa857a7c46a..4aed4a14bf12 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; public class SpringMain { public static void main(String[] args) { @@ -15,6 +21,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(null, "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..74e0de04e1e8 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -8,9 +8,7 @@ * GKislin * 11.01.2015. */ -public class Meal { - private Integer id; - +public class Meal extends BaseEntity { private final LocalDateTime dateTime; private final String description; @@ -22,20 +20,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 +46,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 908c741bf0b5..a659983450b5 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -1,5 +1,6 @@ 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.DateTimeUtil; @@ -19,6 +20,7 @@ import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.ADMIN_ID; import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; +@Repository public class InMemoryMealRepositoryImpl implements MealRepository { private static final Comparator MEAL_COMPARATOR = Comparator.comparing(Meal::getDateTime).reversed(); diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index b63fc9b1df00..edd39036c245 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -1,4 +1,27 @@ 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.List; + public interface MealService { + Meal get(int id, int userId) throws NotFoundException; + + void delete(int id, int userId) throws NotFoundException; + + default List getBetweenDates(LocalDate startDate, LocalDate endDate, int userId) { + return getBetweenDateTimes(LocalDateTime.of(startDate, LocalTime.MIN), LocalDateTime.of(endDate, LocalTime.MAX), userId); + } + + List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId); + + List getAll(int userId); + + Meal update(Meal meal, int userId) throws NotFoundException; + + Meal save(Meal meal, int userId); } \ 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 9017380f392b..3774c92bcce5 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -1,9 +1,52 @@ 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 java.time.LocalDateTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +@Service public class MealServiceImpl implements MealService { - private MealRepository repository; + private final MealRepository repository; + + @Autowired + public MealServiceImpl(MealRepository repository) { + this.repository = repository; + } + + @Override + public Meal get(int id, int userId) { + return checkNotFoundWithId(repository.get(id, userId), id); + } + + @Override + public void delete(int id, int userId) { + checkNotFoundWithId(repository.delete(id, userId), id); + } + + @Override + public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return repository.getBetween(startDateTime, endDateTime, userId); + } + + @Override + public List getAll(int userId) { + return repository.getAll(userId); + } + + @Override + public Meal update(Meal meal, int userId) { + return checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + @Override + public Meal save(Meal meal, int userId) { + return repository.save(meal, userId); + } } \ No newline at end of file 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/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index fd4cda764374..8f57ba58cad3 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,11 +1,15 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class DateTimeUtil { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + 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; } diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index bceab4d19fa7..ed0c476e693c 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; @@ -10,10 +10,6 @@ import java.util.*; import java.util.stream.Collectors; -/** - * GKislin - * 31.05.2015. - */ public class MealsUtil { public static final List MEALS = Arrays.asList( new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index ab4e8ea8bb8e..376d2629b5f0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,8 +1,77 @@ 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.DateTimeUtil; +import ru.javawebinar.topjava.util.MealsUtil; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +@Controller public class MealRestController { - private MealService service; + private static final Logger LOG = LoggerFactory.getLogger(MealRestController.class); + + private final MealService service; + + @Autowired + public MealRestController(MealService service) { + this.service = 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 Meal create(Meal meal) { + int userId = AuthorizedUser.id(); + LOG.info("create {} for User {}", meal, userId); + checkNew(meal); + return service.save(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = AuthorizedUser.id(); + LOG.info("update {} for User {}", meal, userId); + checkIdConsistent(meal, id); + service.update(meal, userId); + } + + public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { + int userId = AuthorizedUser.id(); + LOG.info("getBetween dates({} - {}) time({} - {}) for User {}", startDate, endDate, startTime, endTime, userId); + return MealsUtil.getFilteredWithExceeded( + service.getBetweenDates( + startDate != null ? startDate : DateTimeUtil.MIN_DATE, + endDate != null ? endDate : DateTimeUtil.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/webapp/meals.jsp b/src/main/webapp/meals.jsp index 45b470903183..942a24513dc5 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -32,7 +32,7 @@ - + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> From 66a52a878cddc7f38e67ad50b758b7aab2aeada6 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 6 Apr 2017 22:09:29 +0300 Subject: [PATCH 25/91] 3 03 HW2 optional MealServlet --- .../javawebinar/topjava/web/MealServlet.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index a6cd9de6a908..2dc855519a5e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,11 +2,10 @@ 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.MealRepository; -import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; -import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.web.meal.MealRestController; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -21,12 +20,20 @@ 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(); } @Override @@ -39,8 +46,13 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) request.getParameter("description"), Integer.valueOf(request.getParameter("calories"))); - LOG.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal, AuthorizedUser.id()); + if (meal.isNew()) { + LOG.info("Create {}", meal); + mealController.create(meal); + } else { + LOG.info("Update {}", meal); + mealController.update(meal, getId(request)); + } response.sendRedirect("meals"); } @@ -52,22 +64,21 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t case "delete": int id = getId(request); LOG.info("Delete {}", id); - repository.delete(id, AuthorizedUser.id()); + mealController.delete(id); response.sendRedirect("meals"); break; case "create": case "update": final Meal meal = "create".equals(action) ? new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : - repository.get(getId(request), AuthorizedUser.id()); + mealController.get(getId(request)); request.setAttribute("meal", meal); request.getRequestDispatcher("/meal.jsp").forward(request, response); break; case "all": default: LOG.info("getAll"); - request.setAttribute("meals", - MealsUtil.getWithExceeded(repository.getAll(AuthorizedUser.id()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.setAttribute("meals", mealController.getAll()); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } From 28b3171291e70c748c06df0d4456931f04eda4bd Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 6 Apr 2017 22:09:51 +0300 Subject: [PATCH 26/91] 3 04 HW2 optional filter --- .../topjava/util/DateTimeUtil.java | 11 ++++++ .../javawebinar/topjava/web/MealServlet.java | 36 ++++++++++++------- src/main/webapp/css/style.css | 24 +++++++++++++ src/main/webapp/meal.jsp | 19 +--------- src/main/webapp/meals.jsp | 30 +++++++++++----- 5 files changed, 81 insertions(+), 39 deletions(-) create mode 100644 src/main/webapp/css/style.css diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 8f57ba58cad3..7b89340de1f5 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.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; public class DateTimeUtil { @@ -17,4 +20,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 2dc855519a5e..28a3fd519b0a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -5,6 +5,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.DateTimeUtil; import ru.javawebinar.topjava.web.meal.MealRestController; import javax.servlet.ServletConfig; @@ -13,7 +14,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.Objects; @@ -39,21 +42,30 @@ public void destroy() { @Override 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"); - if (meal.isNew()) { - LOG.info("Create {}", meal); - mealController.create(meal); - } else { - LOG.info("Update {}", meal); - mealController.update(meal, getId(request)); + } else if ("filter".equals(action)) { + LocalDate startDate = DateTimeUtil.parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = DateTimeUtil.parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = DateTimeUtil.parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = DateTimeUtil.parseLocalTime(request.getParameter("endTime")); + request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); } - response.sendRedirect("meals"); } @Override 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/meal.jsp b/src/main/webapp/meal.jsp index 7541b419caf7..a549aef98ced 100644 --- a/src/main/webapp/meal.jsp +++ b/src/main/webapp/meal.jsp @@ -4,24 +4,7 @@ Meal - +
    diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 942a24513dc5..090370b650e8 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -5,20 +5,32 @@ Meal list - +

    Home

    Meal list

    +
    +
    +
    From Date:
    +
    +
    +
    +
    To Date:
    +
    +
    +
    +
    From Time:
    +
    +
    +
    +
    To Time:
    +
    +
    + +
    +
    Add Meal
    From 1e95ab2aa540957dab9222a810e00d5b84c5338f Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 6 Apr 2017 22:10:06 +0300 Subject: [PATCH 27/91] 3 05 HW2 optional select user --- .../java/ru/javawebinar/topjava/AuthorizedUser.java | 7 ++++++- .../java/ru/javawebinar/topjava/web/UserServlet.java | 12 +++++++----- src/main/webapp/index.html | 12 ++++++++---- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index 177a05931540..07886b10ab6e 100644 --- a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -3,9 +3,14 @@ import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; 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 07ede01d611e..69aa1d958db7 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; @@ -10,14 +11,15 @@ 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 doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + int userId = Integer.valueOf(request.getParameter("userId")); + AuthorizedUser.setId(userId); + response.sendRedirect("meals"); + } + @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOG.debug("forward to users"); diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index a04f2d537f14..fd121f219ba6 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -7,9 +7,13 @@

    Проект Java Enterprise (Topjava)


    - +
    + Meal list of  + + + From e9623155620ab0b889da375234a1405a10ef3e90 Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 8 Apr 2017 15:32:17 +0300 Subject: [PATCH 28/91] 3 06 bean life cycle --- .../mock/InMemoryMealRepositoryImpl.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 a659983450b5..e7c536cc11ad 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -1,11 +1,15 @@ package ru.javawebinar.topjava.repository.mock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.DateTimeUtil; import ru.javawebinar.topjava.util.MealsUtil; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.time.LocalDateTime; import java.time.Month; import java.util.Comparator; @@ -22,6 +26,7 @@ @Repository public class InMemoryMealRepositoryImpl implements MealRepository { + private static final Logger LOG = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); private static final Comparator MEAL_COMPARATOR = Comparator.comparing(Meal::getDateTime).reversed(); @@ -50,6 +55,16 @@ public Meal save(Meal meal, int userId) { return meal; } + @PostConstruct + public void postConstruct() { + LOG.info("+++ PostConstruct"); + } + + @PreDestroy + public void preDestroy() { + LOG.info("+++ PreDestroy"); + } + @Override public boolean delete(int id, int userId) { Map meals = repository.get(userId); From 7e99567941666dc30a393314b79183b9dc2813fe Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 8 Apr 2017 20:36:06 +0300 Subject: [PATCH 29/91] 3 07 add junit --- pom.xml | 19 +++++++ .../ru/javawebinar/topjava/SpringMain.java | 0 .../ru/javawebinar/topjava/UserTestData.java | 12 ++++ .../mock/InMemoryMealRepositoryImpl.java | 4 +- .../mock/InMemoryUserRepositoryImpl.java | 2 - .../web/InMemoryAdminRestControllerTest.java | 55 +++++++++++++++++++ 6 files changed, 88 insertions(+), 4 deletions(-) rename src/{main => test}/java/ru/javawebinar/topjava/SpringMain.java (100%) create mode 100644 src/test/java/ru/javawebinar/topjava/UserTestData.java rename src/{main => test}/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (94%) rename src/{main => test}/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java (95%) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index 711746d72c41..0e909d6b8563 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ 1.2.2 1.7.25 + + + 4.12 @@ -36,6 +39,14 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + -Dfile.encoding=UTF-8 + + @@ -88,6 +99,14 @@ jstl 1.2 + + + + junit + junit + ${junit.version} + test + diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java similarity index 100% rename from src/main/java/ru/javawebinar/topjava/SpringMain.java rename to src/test/java/ru/javawebinar/topjava/SpringMain.java 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..196b09362719 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +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 94% 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 e7c536cc11ad..9be901290701 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -21,8 +21,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -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; @Repository public class InMemoryMealRepositoryImpl implements MealRepository { diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java similarity index 95% rename from src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java index f570752f6840..a7e83524dc32 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -16,8 +16,6 @@ public class InMemoryUserRepositoryImpl implements UserRepository { private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName).thenComparing(User::getEmail); - public static final int USER_ID = 1; - public static final int ADMIN_ID = 2; private Map repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); 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 36e0ac32636dba83f52b31fdae33e55cd3a0a00a Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 8 Apr 2017 20:43:10 +0300 Subject: [PATCH 30/91] 3 08 add spring test --- pom.xml | 7 +++ ...InMemoryAdminRestControllerSpringTest.java | 50 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java diff --git a/pom.xml b/pom.xml index 0e909d6b8563..88b24219a886 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,13 @@ ${junit.version} test + + org.springframework + spring-test + ${spring.version} + test + + diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java new file mode 100644 index 000000000000..4d98c0418e8d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -0,0 +1,50 @@ +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.SpringRunner; +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; + +@ContextConfiguration("classpath:spring/spring-app.xml") +@RunWith(SpringRunner.class) +public class InMemoryAdminRestControllerSpringTest { + + @Autowired + private AdminRestController controller; + + @Autowired + private UserRepository repository; + + @Before + public void setUp() throws Exception { + repository.getAll().forEach(u -> repository.delete(u.getId())); + repository.save(USER); + repository.save(ADMIN); + } + + @Test + public void testDelete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { + controller.delete(10); + } +} From 49a2e88fd5fdb2f0df5c99c2e81861f63464df35 Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 8 Apr 2017 21:34:37 +0300 Subject: [PATCH 31/91] 3 09 add postgresql --- pom.xml | 9 +++++++++ src/main/resources/db/postgres.properties | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 src/main/resources/db/postgres.properties diff --git a/pom.xml b/pom.xml index 88b24219a886..c08724e6b3a9 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,8 @@ 1.2.2 1.7.25 + + 42.0.0 4.12 @@ -86,6 +88,13 @@ + + + org.postgresql + postgresql + ${postgresql.version} + + javax.servlet 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 eb1d1439ca161dfd9d298c0a0df9684945ed83e9 Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 8 Apr 2017 21:48:19 +0300 Subject: [PATCH 32/91] 3 10 db implementation --- pom.xml | 5 ++ .../jdbc/JdbcUserRepositoryImpl.java | 82 +++++++++++++++++++ src/main/resources/db/initDB.sql | 25 ++++++ src/main/resources/db/populateDB.sql | 13 +++ src/main/resources/spring/spring-db.xml | 25 ++++++ 5 files changed, 150 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java create mode 100644 src/main/resources/db/initDB.sql create mode 100644 src/main/resources/db/populateDB.sql create mode 100644 src/main/resources/spring/spring-db.xml diff --git a/pom.xml b/pom.xml index c08724e6b3a9..5cc5cbb55267 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,11 @@ + + org.springframework + spring-jdbc + ${spring.version} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java new file mode 100644 index 000000000000..aa869a59a4fd --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -0,0 +1,82 @@ +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; + +@Repository +public class JdbcUserRepositoryImpl implements UserRepository { + + private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); + + private final JdbcTemplate jdbcTemplate; + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private final SimpleJdbcInsert insertUser; + + @Autowired + public JdbcUserRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertUser = new SimpleJdbcInsert(dataSource) + .withTableName("users") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + @Override + public User save(User user) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", user.getId()) + .addValue("name", user.getName()) + .addValue("email", user.getEmail()) + .addValue("password", user.getPassword()) + .addValue("registered", user.getRegistered()) + .addValue("enabled", user.isEnabled()) + .addValue("caloriesPerDay", user.getCaloriesPerDay()); + + if (user.isNew()) { + Number newKey = insertUser.executeAndReturnKey(map); + user.setId(newKey.intValue()); + } else { + 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); + List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + return DataAccessUtils.singleResult(users); + } + + @Override + public List getAll() { + return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + } +} diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql new file mode 100644 index 000000000000..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); diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml new file mode 100644 index 000000000000..a1f6b09a7464 --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 27612a0039c63f25822789b2fdcf9828b5d1dd2c Mon Sep 17 00:00:00 2001 From: Drag Date: Sun, 9 Apr 2017 22:57:52 +0300 Subject: [PATCH 33/91] 3 11 test UserService --- .../javawebinar/topjava/model/BaseEntity.java | 20 ++++ .../ru/javawebinar/topjava/model/User.java | 4 + .../jdbc/JdbcMealRepositoryImpl.java | 37 ++++++++ .../javawebinar/topjava/util/DbPopulator.java | 23 +++++ src/main/resources/spring/spring-app.xml | 2 +- src/main/resources/spring/spring-db.xml | 5 + .../ru/javawebinar/topjava/MealTestData.java | 10 ++ .../ru/javawebinar/topjava/UserTestData.java | 21 ++++- .../topjava/matcher/ModelMatcher.java | 72 ++++++++++++++ .../topjava/service/UserServiceTest.java | 94 +++++++++++++++++++ 10 files changed, 285 insertions(+), 3 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/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 f15234bd6353..bf80ce6d4de1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.model; public class BaseEntity { + public static final int START_SEQ = 100000; + protected Integer id; public BaseEntity() { @@ -21,4 +23,22 @@ 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; + return id != null && id.equals(that.id); + } + + @Override + public int hashCode() { + return (id == null) ? 0 : id; + } + } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index f0971e1ea1ed..39192fe2d38b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -23,6 +23,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, 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..3e48b40b46bb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -0,0 +1,37 @@ +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; + +@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..e04432818ad6 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java @@ -0,0 +1,23 @@ +package ru.javawebinar.topjava.util; + +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; + +public class DbPopulator extends ResourceDatabasePopulator { + private static final ResourceLoader RESOURCE_LOADER = new DefaultResourceLoader(); + + private final DataSource dataSource; + + public DbPopulator(String scriptLocation, DataSource dataSource) { + super(RESOURCE_LOADER.getResource(scriptLocation)); + this.dataSource = dataSource; + } + + 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 index a1f6b09a7464..a9a60329eaee 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -6,6 +6,11 @@ + + + + + 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..c0973e8eb538 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.matcher.ModelMatcher; +import ru.javawebinar.topjava.model.Meal; + +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 196b09362719..e1151110c95e 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -1,12 +1,29 @@ package ru.javawebinar.topjava; import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.matcher.ModelMatcher; import ru.javawebinar.topjava.model.User; +import java.util.Objects; + +import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ; + 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()) + ) + ); } 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..6d688e5c26b9 --- /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; + +/** + * This test Matcher assert equality of beans and collections + * + * It wrap every entity by Wrapper before apply to Assert.assertEquals + * in order to compare them by custom comparator. + * Default comparator is 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) -> expected == 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..969e022cbdcc --- /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 + private 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 02827757a6fd257ad7abc4291ff5764bca4056ac Mon Sep 17 00:00:00 2001 From: Drag Date: Sun, 9 Apr 2017 23:02:40 +0300 Subject: [PATCH 34/91] 3 12 test logging --- pom.xml | 7 ++++++ .../topjava/service/UserServiceTest.java | 5 +++++ src/test/resources/logback-test.xml | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/test/resources/logback-test.xml diff --git a/pom.xml b/pom.xml index 5cc5cbb55267..7e7dd6e7115d 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,13 @@ runtime + + org.slf4j + jul-to-slf4j + ${slf4j.version} + runtime + + ch.qos.logback logback-classic diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 969e022cbdcc..8f3de6ec2839 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -3,6 +3,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ContextConfiguration; @@ -25,6 +26,10 @@ @RunWith(SpringJUnit4ClassRunner.class) public class UserServiceTest { + static { + SLF4JBridgeHandler.install(); + } + @Autowired private UserService service; diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 000000000000..6b1fa3682055 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + true + + + + + UTF-8 + %-5level %logger{0} - %msg%n + + + + + + + + + + + + \ No newline at end of file From f8c5ce2028dc79bbb29efc92ad96edbb268502cd Mon Sep 17 00:00:00 2001 From: Drag Date: Sun, 9 Apr 2017 23:03:38 +0300 Subject: [PATCH 35/91] 3 13 fix servlet --- src/main/java/ru/javawebinar/topjava/AuthorizedUser.java | 4 +++- src/main/java/ru/javawebinar/topjava/web/MealServlet.java | 2 +- src/main/webapp/index.html | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index 07886b10ab6e..c058757ab404 100644 --- a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -1,9 +1,11 @@ package ru.javawebinar.topjava; +import ru.javawebinar.topjava.model.BaseEntity; + import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; 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 28a3fd519b0a..ced3463747ce 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -29,7 +29,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 fd121f219ba6..a9a4e7837e24 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -10,8 +10,8 @@

    Проект Meal list of  From 8f94cbf20049b6b39446fc58cb471cf4cb7b1d40 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 13 Apr 2017 15:24:37 +0300 Subject: [PATCH 36/91] 4 0 fix --- .../ru/javawebinar/topjava/service/UserServiceImpl.java | 2 ++ src/main/java/ru/javawebinar/topjava/util/DbPopulator.java | 3 +-- src/main/java/ru/javawebinar/topjava/web/MealServlet.java | 7 ------- .../javawebinar/topjava/web/meal/MealRestController.java | 4 ++-- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 6b540886e866..764632d6eb04 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,5 +1,6 @@ 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; @@ -15,6 +16,7 @@ public class UserServiceImpl implements UserService { private final UserRepository repository; + @Autowired public UserServiceImpl(UserRepository repository) { this.repository = repository; } diff --git a/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java b/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java index e04432818ad6..bf9a58d5796c 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java +++ b/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java @@ -2,7 +2,6 @@ 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; @@ -18,6 +17,6 @@ public DbPopulator(String scriptLocation, DataSource dataSource) { } public void execute() { - DatabasePopulatorUtils.execute(this, dataSource); + execute(dataSource); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index ced3463747ce..9e3cd1660be1 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,7 +1,5 @@ package ru.javawebinar.topjava.web; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Meal; @@ -21,7 +19,6 @@ import java.util.Objects; public class MealServlet extends HttpServlet { - private static final Logger LOG = LoggerFactory.getLogger(MealServlet.class); private ConfigurableApplicationContext springContext; private MealRestController mealController; @@ -50,10 +47,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) 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"); @@ -75,7 +70,6 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t switch (action == null ? "all" : action) { case "delete": int id = getId(request); - LOG.info("Delete {}", id); mealController.delete(id); response.sendRedirect("meals"); break; @@ -89,7 +83,6 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t break; case "all": default: - LOG.info("getAll"); request.setAttribute("meals", mealController.getAll()); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; 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 376d2629b5f0..a8208a9c0917 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -49,15 +49,15 @@ public List getAll() { public Meal create(Meal meal) { int userId = AuthorizedUser.id(); - LOG.info("create {} for User {}", meal, userId); checkNew(meal); + LOG.info("create {} for User {}", meal, userId); return service.save(meal, userId); } public void update(Meal meal, int id) { int userId = AuthorizedUser.id(); - LOG.info("update {} for User {}", meal, userId); checkIdConsistent(meal, id); + LOG.info("update {} for User {}", meal, userId); service.update(meal, userId); } From 4fa45ef6553abc61bf9ccb0f3edbdbb1fe66f1dc Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 13 Apr 2017 15:36:29 +0300 Subject: [PATCH 37/91] 4 1 HW3 --- .../ru/javawebinar/topjava/model/Meal.java | 21 ++++++- .../jdbc/JdbcMealRepositoryImpl.java | 61 +++++++++++++++++-- .../jdbc/JdbcUserRepositoryImpl.java | 15 ++--- src/main/resources/db/initDB.sql | 11 ++++ src/main/resources/db/populateDB.sql | 11 ++++ 5 files changed, 100 insertions(+), 19 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 3e48b40b46bb..fa514658576b 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -1,37 +1,88 @@ 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; @Repository public class JdbcMealRepositoryImpl implements MealRepository { + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); + + private final JdbcTemplate jdbcTemplate; + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private final SimpleJdbcInsert insertMeal; + + @Autowired + public JdbcMealRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertMeal = new SimpleJdbcInsert(dataSource) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + @Override public Meal save(Meal meal, int userId) { - return null; + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", meal.getId()) + .addValue("description", meal.getDescription()) + .addValue("calories", meal.getCalories()) + .addValue("date_time", meal.getDateTime()) + .addValue("user_id", userId); + + if (meal.isNew()) { + Number newId = insertMeal.executeAndReturnKey(map); + meal.setId(newId.intValue()); + } else { + if (namedParameterJdbcTemplate.update("" + + "UPDATE meals " + + " SET description=:description, calories=:calories, date_time=:date_time " + + " WHERE id=:id AND user_id=:user_id" + , map) == 0) { + return null; + } + } + return meal; } @Override public boolean delete(int id, int userId) { - return 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/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java index aa869a59a4fd..03061174f7b9 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -4,7 +4,7 @@ import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; @@ -37,22 +37,15 @@ public JdbcUserRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, @Override public User save(User user) { - MapSqlParameterSource map = new MapSqlParameterSource() - .addValue("id", user.getId()) - .addValue("name", user.getName()) - .addValue("email", user.getEmail()) - .addValue("password", user.getPassword()) - .addValue("registered", user.getRegistered()) - .addValue("enabled", user.isEnabled()) - .addValue("caloriesPerDay", user.getCaloriesPerDay()); + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); if (user.isNew()) { - Number newKey = insertUser.executeAndReturnKey(map); + Number newKey = insertUser.executeAndReturnKey(parameterSource); user.setId(newKey.intValue()); } else { namedParameterJdbcTemplate.update( "UPDATE users SET name=:name, email=:email, password=:password, " + - "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map); + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource); } return user; } 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 14e85ecac3700af3cd2ff716d82aed5bfc32b5c1 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 13 Apr 2017 15:48:20 +0300 Subject: [PATCH 38/91] 4 2 HW3 optional --- .../javawebinar/topjava/util/MealsUtil.java | 18 ---- .../ru/javawebinar/topjava/MealTestData.java | 28 ++++++ .../mock/InMemoryMealRepositoryImpl.java | 12 --- .../topjava/service/MealServiceTest.java | 95 +++++++++++++++++++ 4 files changed, 123 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 ed0c476e693c..ef82ce369fd4 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -4,31 +4,13 @@ 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; 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 mealsWithExceeded = getFilteredWithExceeded(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsWithExceeded.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 c0973e8eb538..9d362edc78bf 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -3,8 +3,36 @@ 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; + 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 9be901290701..f8f44252ed6e 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -6,12 +6,10 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.DateTimeUtil; -import ru.javawebinar.topjava.util.MealsUtil; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDateTime; -import java.time.Month; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -21,9 +19,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; - @Repository public class InMemoryMealRepositoryImpl implements MealRepository { private static final Logger LOG = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); @@ -34,13 +29,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..f44f89b972de --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -0,0 +1,95 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +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 { + + static { + SLF4JBridgeHandler.install(); + } + + @Autowired + private MealService service; + + @Autowired + private DbPopulator dbPopulator; + + @Before + public void setUp() throws Exception { + dbPopulator.execute(); + } + + @Test + public void testDelete() throws Exception { + service.delete(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 testUpdateNotFound() throws Exception { + service.update(MEAL1, 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 3c0b77a2e83a52fd248de7452bfc87812f3c9c28 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 13 Apr 2017 16:26:21 +0300 Subject: [PATCH 39/91] Codacy Badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 78785efd6f33..f122bec518d8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ Java Enterprise Online Project =============================== +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/1c5405904a094a648ae0bc859a37dd7e)](https://www.codacy.com/app/DragFAQ/topjava?utm_source=github.com&utm_medium=referral&utm_content=DragFAQ/topjava&utm_campaign=badger) + Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise: Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins. From 45e6a36a7db3b129dcd296a5dbd2f96c8f59d67b Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 13 Apr 2017 17:50:26 +0300 Subject: [PATCH 40/91] 4 3 improve code --- .travis.yml | 17 +++++++++++++++++ .../topjava/service/MealServiceImpl.java | 5 +++++ .../topjava/service/UserServiceImpl.java | 4 ++++ .../javawebinar/topjava/util/DateTimeUtil.java | 3 +++ .../topjava/util/ValidationUtil.java | 2 ++ .../web/user/AbstractUserController.java | 14 +++++++------- src/main/webapp/meal.jsp | 1 + src/main/webapp/meals.jsp | 1 + src/main/webapp/users.jsp | 1 + .../topjava/matcher/ModelMatcher.java | 8 ++++---- .../mock/InMemoryMealRepositoryImpl.java | 5 ----- .../mock/InMemoryUserRepositoryImpl.java | 3 --- 12 files changed, 45 insertions(+), 19 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..a17534219221 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +# https://docs.travis-ci.com/user/languages/java/ +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 + - psql -c 'create user "user"; grant all privileges on database topjava to "user"' -U postgres + +# https://docs.travis-ci.com/user/notifications#Configuring-email-notifications +#notifications: +# email: false \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 3774c92bcce5..a8d324673f88 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ru.javawebinar.topjava.model.Meal; +import org.springframework.util.Assert; import ru.javawebinar.topjava.repository.MealRepository; import java.time.LocalDateTime; @@ -32,6 +33,8 @@ public void delete(int id, int userId) { @Override public List 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); } @@ -42,11 +45,13 @@ public List getAll(int userId) { @Override public Meal update(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); return 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); } } \ 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 764632d6eb04..3fa606c44586 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.NotFoundException; @@ -23,6 +24,7 @@ public UserServiceImpl(UserRepository repository) { @Override public User save(User user) { + Assert.notNull(user, "user must not be null"); return repository.save(user); } @@ -38,6 +40,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 checkNotFound(repository.getByEmail(email), "email=" + email); } @@ -48,6 +51,7 @@ public List getAll() { @Override public void update(User user) { + Assert.notNull(user, "user must not be null"); repository.save(user); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 7b89340de1f5..11269a5ede40 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -13,6 +13,9 @@ public class DateTimeUtil { public static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + private DateTimeUtil() { + } + 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/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index cd702f58b852..94490ea8d6b5 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -5,6 +5,8 @@ import ru.javawebinar.topjava.util.exception.NotFoundException; public class ValidationUtil { + private ValidationUtil() { + } public static void checkNotFoundWithId(boolean found, int id) { checkNotFound(found, "id=" + id); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index b97de4a998e3..39b8d4fdfa34 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -11,7 +11,7 @@ import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; public abstract class AbstractUserController { - protected final Logger LOG = LoggerFactory.getLogger(getClass()); + protected final Logger log = LoggerFactory.getLogger(getClass()); private final UserService service; @@ -20,34 +20,34 @@ public AbstractUserController(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) { - LOG.info("create {}", user); + log.info("create {}", user); checkNew(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) { - LOG.info("update {}", user); + log.info("update {}", user); checkIdConsistent(user, id); service.update(user); } public User getByMail(String email) { - LOG.info("getByEmail {}", email); + log.info("getByEmail {}", email); return service.getByEmail(email); } } \ No newline at end of file diff --git a/src/main/webapp/meal.jsp b/src/main/webapp/meal.jsp index a549aef98ced..259d80f3ee9d 100644 --- a/src/main/webapp/meal.jsp +++ b/src/main/webapp/meal.jsp @@ -3,6 +3,7 @@ + Meal diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 090370b650e8..2a74fc4503d4 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -4,6 +4,7 @@ <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> + Meal list diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp index fbd40d3d7219..2efb8cef01ec 100644 --- a/src/main/webapp/users.jsp +++ b/src/main/webapp/users.jsp @@ -1,6 +1,7 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> + User list diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index 6d688e5c26b9..74f9ac6f81d4 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 f8f44252ed6e..94c877778b97 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -13,7 +13,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,8 +30,6 @@ public class InMemoryMealRepositoryImpl implements MealRepository { @Override public Meal save(Meal meal, int userId) { - Objects.requireNonNull(meal); - if (meal.isNew()) { meal.setId(counter.incrementAndGet()); } else if (get(meal.getId(), userId) == null) { @@ -72,8 +69,6 @@ public List getAll(int userId) { @Override public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { - Objects.requireNonNull(startDateTime); - Objects.requireNonNull(endDateTime); return getAllAsStream(userId) .filter(um -> DateTimeUtil.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 a7e83524dc32..5dcb5be5567e 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -7,7 +7,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; @@ -22,7 +21,6 @@ public class InMemoryUserRepositoryImpl implements UserRepository { @Override public User save(User user) { - Objects.requireNonNull(user); if (user.isNew()) { user.setId(counter.incrementAndGet()); } @@ -49,7 +47,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 44270acc9b710f9e0a5ad55e19d1e997429b74dd Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 13 Apr 2017 18:14:24 +0300 Subject: [PATCH 41/91] 4 4 init and populate db --- .../javawebinar/topjava/util/DbPopulator.java | 22 ------------------- src/main/resources/db/postgres.properties | 2 ++ src/main/resources/spring/spring-db.xml | 14 +++++++----- .../topjava/service/MealServiceTest.java | 13 +++-------- .../topjava/service/UserServiceTest.java | 13 +++-------- 5 files changed, 16 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 bf9a58d5796c..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/DbPopulator.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.javawebinar.topjava.util; - -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.ResourceLoader; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; - -import javax.sql.DataSource; - -public class DbPopulator extends ResourceDatabasePopulator { - private static final ResourceLoader RESOURCE_LOADER = new DefaultResourceLoader(); - - private final DataSource dataSource; - - public DbPopulator(String scriptLocation, DataSource dataSource) { - super(RESOURCE_LOADER.getResource(scriptLocation)); - this.dataSource = dataSource; - } - - public void execute() { - execute(dataSource); - } -} diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 44dd64eb26b8..d1aecfa9f430 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,3 +5,5 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password + +database.init=true \ 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 a9a60329eaee..22ad27239647 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,15 +1,17 @@ + xmlns:jdbc="http://www.springframework.org/schema/jdbc" + 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"> - - - - + + + + Date: Thu, 13 Apr 2017 18:20:54 +0300 Subject: [PATCH 42/91] 4 5 create mock test ctx --- src/test/java/ru/javawebinar/topjava/SpringMain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 4aed4a14bf12..a9083a6d5b54 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -17,7 +17,7 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management - 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(new User(null, "userName", "email", "password", Role.ROLE_ADMIN)); From 779c3fe0799ecb5017ddf3e6d30314f51235ce87 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 13 Apr 2017 18:21:46 +0300 Subject: [PATCH 43/91] 4 5 create mock test ctx --- src/main/resources/spring/spring-app.xml | 2 -- src/main/resources/spring/spring-db.xml | 2 ++ .../topjava/web/InMemoryAdminRestControllerSpringTest.java | 2 +- .../topjava/web/InMemoryAdminRestControllerTest.java | 2 +- src/test/resources/spring/mock.xml | 7 +++++++ 5 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/spring/mock.xml diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 04810da82e92..eff98648bc5a 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -14,8 +14,6 @@ - - diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 22ad27239647..c239b0136f45 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -8,6 +8,8 @@ + + diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java index 4d98c0418e8d..42b03578127d 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -18,7 +18,7 @@ import static ru.javawebinar.topjava.UserTestData.ADMIN; import static ru.javawebinar.topjava.UserTestData.USER; -@ContextConfiguration("classpath:spring/spring-app.xml") +@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/mock.xml"}) @RunWith(SpringRunner.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 9e32ea20cf00f6965df6e03722896202d0d9a0b6 Mon Sep 17 00:00:00 2001 From: Drag Date: Tue, 18 Apr 2017 13:01:21 +0300 Subject: [PATCH 44/91] 4 6 add jpa --- pom.xml | 28 ++++++++- .../javawebinar/topjava/model/BaseEntity.java | 13 +++- .../ru/javawebinar/topjava/model/Meal.java | 13 ++++ .../topjava/model/NamedEntity.java | 11 +++- .../ru/javawebinar/topjava/model/User.java | 26 +++++++- .../repository/jpa/JpaMealRepositoryImpl.java | 37 +++++++++++ .../repository/jpa/JpaUserRepositoryImpl.java | 61 +++++++++++++++++++ src/main/resources/db/postgres.properties | 6 +- src/main/resources/spring/spring-db.xml | 26 +++++++- 9 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java diff --git a/pom.xml b/pom.xml index 7e7dd6e7115d..029618e440e3 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,10 @@ 42.0.0 4.12 + + + 5.2.9.Final + 5.4.1.Final @@ -96,7 +100,7 @@ org.springframework - spring-jdbc + spring-orm ${spring.version} @@ -107,6 +111,28 @@ ${postgresql.version} + + org.hibernate + hibernate-core + ${hibernate.version} + + + javax.transaction + jta + 1.1 + runtime + + + org.hibernate + hibernate-validator + ${hibernate-validator.version} + + + javax.el + javax.el-api + 3.0.0 + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index bf80ce6d4de1..e6a59425acbb 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,11 +1,18 @@ package ru.javawebinar.topjava.model; +import javax.persistence.*; + +@MappedSuperclass +@Access(AccessType.FIELD) public class BaseEntity { public static final int START_SEQ = 100000; + @Id + @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") protected Integer id; - public BaseEntity() { + protected BaseEntity() { } protected BaseEntity(Integer id) { @@ -41,4 +48,8 @@ public int hashCode() { return (id == null) ? 0 : id; } + @Override + public String toString() { + return String.format("Entity %s (%s)", getClass().getName(), id); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java 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 820df2de5088..528a70133ed2 100644 --- a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -1,7 +1,16 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.NotBlank; + +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; + + +@MappedSuperclass public class NamedEntity extends BaseEntity { + @NotBlank + @Column(name = "name", nullable = false) protected String name; public NamedEntity() { @@ -22,6 +31,6 @@ public String getName() { @Override public String toString() { - return name; + return String.format("Entity %s (%s, '%s')", getClass().getName(), id, name); } } \ 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 39192fe2d38b..c45f85e64bdd 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,23 +1,45 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.Email; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotBlank; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; import java.util.Date; import java.util.EnumSet; import java.util.Set; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; +@Entity +@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) public class User extends NamedEntity { + @Column(name = "email", nullable = false, unique = true) + @Email + @NotBlank private String email; + @Column(name = "password", nullable = false) + @NotBlank + @Length(min = 5) private String password; + @Column(name = "enabled", nullable = false) private boolean enabled = true; + @Column(name = "registered", columnDefinition = "timestamp default now()") private Date registered = new Date(); + @Enumerated(EnumType.STRING) + @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) + @Column(name = "role") + @ElementCollection(fetch = FetchType.EAGER) private Set roles; + @Column(name = "calories_per_day", columnDefinition = "int default 2000") + @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; public User() { @@ -86,13 +108,13 @@ public String getPassword() { @Override public String toString() { - return "User (" + + return "User{" + "id=" + id + ", email=" + email + ", name=" + name + ", enabled=" + enabled + ", roles=" + roles + ", caloriesPerDay=" + caloriesPerDay + - ')'; + '}'; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java new file mode 100644 index 000000000000..052d875e8b71 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -0,0 +1,37 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class 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 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..380b8702127d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -0,0 +1,61 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import java.util.List; + +@Repository +public class JpaUserRepositoryImpl implements UserRepository { + +/* + @Autowired + private SessionFactory sessionFactory; + + private Session openSession() { + return sessionFactory.getCurrentSession(); + } +*/ + + @PersistenceContext + private EntityManager em; + + @Override + 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 + 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; + } + + @Override + public User getByEmail(String email) { + return null; + } + + @Override + public List getAll() { + return null; + } +} diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index d1aecfa9f430..0293ef30a3c8 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -6,4 +6,8 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password -database.init=true \ No newline at end of file +database.init=true + +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index c239b0136f45..4c72d7b6f3b3 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,5 +1,6 @@ - + @@ -24,6 +25,28 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file From 1fa380151cd535406718d55ad23b2b6ce46a857b Mon Sep 17 00:00:00 2001 From: Drag Date: Tue, 18 Apr 2017 13:06:11 +0300 Subject: [PATCH 45/91] 4 7 add named query and transaction --- .../java/ru/javawebinar/topjava/model/User.java | 9 +++++++++ .../repository/jpa/JpaUserRepositoryImpl.java | 17 ++++++++++++----- src/main/resources/spring/spring-db.xml | 10 +++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index c45f85e64bdd..4411c595121c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -12,10 +12,19 @@ import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; +@NamedQueries({ + @NamedQuery(name = User.DELETE, query = "DELETE FROM User u WHERE u.id=:id"), + @NamedQuery(name = User.BY_EMAIL, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email=?1"), + @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"), +}) @Entity @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) public class User extends NamedEntity { + public static final String DELETE = "User.delete"; + public static final String BY_EMAIL = "User.getByEmail"; + public static final String ALL_SORTED = "User.getAllSorted"; + @Column(name = "email", nullable = false, unique = true) @Email @NotBlank diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java index 380b8702127d..d8af528cb42d 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -1,15 +1,17 @@ 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 javax.persistence.Query; import java.util.List; @Repository +@Transactional(readOnly = true) public class JpaUserRepositoryImpl implements UserRepository { /* @@ -25,6 +27,7 @@ private Session openSession() { private EntityManager em; @Override + @Transactional public User save(User user) { if (user.isNew()) { em.persist(user); @@ -40,22 +43,26 @@ public User get(int 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"); + + Query query = em.createQuery("DELETE FROM User u WHERE u.id=:id"); return query.setParameter("id", id).executeUpdate() != 0; +*/ + return em.createNamedQuery(User.DELETE).setParameter("id", id).executeUpdate() != 0; } @Override public User getByEmail(String email) { - return null; + List users = em.createNamedQuery(User.BY_EMAIL, User.class).setParameter(1, email).getResultList(); + return DataAccessUtils.singleResult(users); } @Override public List getAll() { - return null; + return em.createNamedQuery(User.ALL_SORTED, User.class).getResultList(); } } diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 4c72d7b6f3b3..7e4ca66b02b1 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -3,9 +3,11 @@ xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" + xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd - http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> @@ -45,6 +47,12 @@ + + + + + + + - + - + From d19740224685aa5e65d08eed5f6417b65b7112bd Mon Sep 17 00:00:00 2001 From: Drag Date: Tue, 18 Apr 2017 13:40:51 +0300 Subject: [PATCH 47/91] HW 4.1 --- .../ru/javawebinar/topjava/model/Meal.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 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..322378b783be 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,7 +1,9 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import org.hibernate.validator.constraints.NotBlank; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -10,14 +12,24 @@ * GKislin * 11.01.2015. */ +@Entity +@Table(name = "meals") public class Meal extends BaseEntity { + @Column(name = "date_time", nullable = false, columnDefinition = "timestamp default now()") + @NotBlank private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotBlank private String description; + @Column(name = "calories", nullable = false) + @NotBlank + @Range(min = 10, max = 10000) private int calories; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE}) + @JoinColumn(name = "user_id", nullable = false) private User user; public Meal() { From c8200c7acbd54ed5c51770c9298d68a953b2c31a Mon Sep 17 00:00:00 2001 From: Drag Date: Tue, 18 Apr 2017 16:47:41 +0300 Subject: [PATCH 48/91] HW 4.2 --- .../ru/javawebinar/topjava/model/Meal.java | 13 ++++- .../repository/jpa/JpaMealRepositoryImpl.java | 51 +++++++++++++++++-- .../ru/javawebinar/topjava/MealTestData.java | 14 ++++- 3 files changed, 70 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 322378b783be..acd2d08c222c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -12,11 +12,21 @@ * GKislin * 11.01.2015. */ +@NamedQueries({ + @NamedQuery(name = Meal.DELETE, query = "DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:user_id"), + @NamedQuery(name = Meal.GET_BETWEEN, query = "SELECT m FROM Meal m WHERE m.user.id=:user_id AND m.dateTime BETWEEN " + + ":start_date AND :end_date ORDER BY m.dateTime DESC"), + @NamedQuery(name = Meal.ALL_SORTED, query = "SELECT m FROM Meal m WHERE m.user.id=:user_id ORDER BY m.dateTime DESC"), +}) @Entity @Table(name = "meals") public class Meal extends BaseEntity { + + public static final String DELETE = "Meal.delete"; + public static final String GET_BETWEEN = "Meal.getBetween"; + public static final String ALL_SORTED = "Meal.getAllSorted"; + @Column(name = "date_time", nullable = false, columnDefinition = "timestamp default now()") - @NotBlank private LocalDateTime dateTime; @Column(name = "description", nullable = false) @@ -24,7 +34,6 @@ public class Meal extends BaseEntity { private String description; @Column(name = "calories", nullable = false) - @NotBlank @Range(min = 10, max = 10000) private int calories; 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 052d875e8b71..aded86ae0467 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -1,37 +1,78 @@ package ru.javawebinar.topjava.repository.jpa; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.MealRepository; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @Repository +@Transactional(readOnly = true) public class JpaMealRepositoryImpl implements MealRepository { + @PersistenceContext + private EntityManager em; + @Override + @Transactional public Meal save(Meal meal, int userId) { - return null; + if (meal.isNew()) { + User ref = em.getReference(User.class, userId); + meal.setUser(ref); + em.persist(meal); + return meal; + } else { + Meal dbMeal = em.find(Meal.class, meal.getId()); + if (dbMeal != null && dbMeal.getUser().getId() == userId) { + dbMeal.setDateTime(meal.getDateTime()); + dbMeal.setDescription(meal.getDescription()); + dbMeal.setCalories(meal.getCalories()); + return em.merge(dbMeal); + } + else + return null; + } } @Override + @Transactional public boolean delete(int id, int userId) { - return false; + return em + .createNamedQuery(Meal.DELETE) + .setParameter("id", id) + .setParameter("user_id", userId) + .executeUpdate() != 0; } @Override public Meal get(int id, int userId) { - return null; + Meal dbMeal = em.find(Meal.class, id); + if (dbMeal != null && dbMeal.getUser().getId() == userId) + return dbMeal; + else + return null; } @Override public List getAll(int userId) { - return null; + return em + .createNamedQuery(Meal.ALL_SORTED, Meal.class) + .setParameter("user_id", userId) + .getResultList(); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return em + .createNamedQuery(Meal.GET_BETWEEN, Meal.class) + .setParameter("user_id", userId) + .setParameter("start_date", startDate) + .setParameter("end_date", endDate) + .getResultList(); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index 9d362edc78bf..cb0846a7e8ee 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -25,6 +25,16 @@ public class MealTestData { 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); + static { + MEAL1.setUser(UserTestData.USER); + MEAL2.setUser(UserTestData.USER); + MEAL3.setUser(UserTestData.USER); + MEAL4.setUser(UserTestData.USER); + MEAL5.setUser(UserTestData.USER); + MEAL6.setUser(UserTestData.USER); + ADMIN_MEAL1.setUser(UserTestData.ADMIN); + ADMIN_MEAL2.setUser(UserTestData.ADMIN); + } public static final List MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); @@ -33,6 +43,8 @@ public static Meal getCreated() { } public static Meal getUpdated() { - return new Meal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); + Meal result = new Meal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); + result.setUser(UserTestData.USER); + return result; } } From c27cc93f9cdc7c53347b264b152bebcce28dc641 Mon Sep 17 00:00:00 2001 From: Drag Date: Tue, 18 Apr 2017 21:21:42 +0300 Subject: [PATCH 49/91] HW 4.3 --- .../topjava/service/MealServiceTest.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 6f5679866972..f9e76f069870 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,7 +1,13 @@ package ru.javawebinar.topjava.service; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; @@ -31,40 +37,63 @@ public class MealServiceTest { SLF4JBridgeHandler.install(); } + private static final Logger logger = LoggerFactory.getLogger(MealServiceTest.class); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TestWatcher watchman = new TestWatcher() { + private long start; + + @Override + protected void starting(Description description) { + start = System.currentTimeMillis(); + } + + @Override + protected void finished(Description description) { + long end = System.currentTimeMillis(); + logger.info(description.getMethodName() + " - " + (end - start) + "ms"); + } + }; + @Autowired private MealService service; @Test - public void testDelete() throws Exception { + public void testDelete() { service.delete(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 { + @Test + public void testDeleteNotFound() { + thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @Test - public void testSave() throws Exception { + public void testSave() { 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 { + public void testGet() { Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); MATCHER.assertEquals(ADMIN_MEAL1, actual); } - @Test(expected = NotFoundException.class) + @Test public void testGetNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @Test - public void testUpdate() throws Exception { + public void testUpdate() { Meal updated = getUpdated(); service.update(updated, USER_ID); MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); @@ -76,12 +105,12 @@ public void testUpdateNotFound() throws Exception { } @Test - public void testGetAll() throws Exception { + public void testGetAll() { MATCHER.assertCollectionEquals(MEALS, service.getAll(USER_ID)); } @Test - public void testGetBetween() throws Exception { + public void testGetBetween() { MATCHER.assertCollectionEquals(Arrays.asList(MEAL3, MEAL2, MEAL1), service.getBetweenDates(LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID)); } From 5b529ce4493bda0a555c486910a6c526b3002605 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 20 Apr 2017 21:41:05 +0300 Subject: [PATCH 50/91] Revert "HW 4.3" This reverts commit c27cc93f9cdc7c53347b264b152bebcce28dc641. --- .../topjava/service/MealServiceTest.java | 47 ++++--------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index f9e76f069870..6f5679866972 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,13 +1,7 @@ package ru.javawebinar.topjava.service; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; import org.junit.runner.RunWith; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; @@ -37,63 +31,40 @@ public class MealServiceTest { SLF4JBridgeHandler.install(); } - private static final Logger logger = LoggerFactory.getLogger(MealServiceTest.class); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Rule - public TestWatcher watchman = new TestWatcher() { - private long start; - - @Override - protected void starting(Description description) { - start = System.currentTimeMillis(); - } - - @Override - protected void finished(Description description) { - long end = System.currentTimeMillis(); - logger.info(description.getMethodName() + " - " + (end - start) + "ms"); - } - }; - @Autowired private MealService service; @Test - public void testDelete() { + public void testDelete() throws Exception { service.delete(MEAL1_ID, USER_ID); MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID)); } - @Test - public void testDeleteNotFound() { - thrown.expect(NotFoundException.class); + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { service.delete(MEAL1_ID, 1); } @Test - public void testSave() { + 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() { + public void testGet() throws Exception { Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); MATCHER.assertEquals(ADMIN_MEAL1, actual); } - @Test + @Test(expected = NotFoundException.class) public void testGetNotFound() throws Exception { - thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @Test - public void testUpdate() { + public void testUpdate() throws Exception { Meal updated = getUpdated(); service.update(updated, USER_ID); MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); @@ -105,12 +76,12 @@ public void testUpdateNotFound() throws Exception { } @Test - public void testGetAll() { + public void testGetAll() throws Exception { MATCHER.assertCollectionEquals(MEALS, service.getAll(USER_ID)); } @Test - public void testGetBetween() { + 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)); } From 0680cd850303a8eade7d9cdcb4b576d3813c4265 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 20 Apr 2017 21:41:54 +0300 Subject: [PATCH 51/91] Revert "Revert "HW 4.3"" This reverts commit 5b529ce4493bda0a555c486910a6c526b3002605. --- .../topjava/service/MealServiceTest.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 6f5679866972..f9e76f069870 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,7 +1,13 @@ package ru.javawebinar.topjava.service; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; @@ -31,40 +37,63 @@ public class MealServiceTest { SLF4JBridgeHandler.install(); } + private static final Logger logger = LoggerFactory.getLogger(MealServiceTest.class); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TestWatcher watchman = new TestWatcher() { + private long start; + + @Override + protected void starting(Description description) { + start = System.currentTimeMillis(); + } + + @Override + protected void finished(Description description) { + long end = System.currentTimeMillis(); + logger.info(description.getMethodName() + " - " + (end - start) + "ms"); + } + }; + @Autowired private MealService service; @Test - public void testDelete() throws Exception { + public void testDelete() { service.delete(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 { + @Test + public void testDeleteNotFound() { + thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @Test - public void testSave() throws Exception { + public void testSave() { 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 { + public void testGet() { Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); MATCHER.assertEquals(ADMIN_MEAL1, actual); } - @Test(expected = NotFoundException.class) + @Test public void testGetNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @Test - public void testUpdate() throws Exception { + public void testUpdate() { Meal updated = getUpdated(); service.update(updated, USER_ID); MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); @@ -76,12 +105,12 @@ public void testUpdateNotFound() throws Exception { } @Test - public void testGetAll() throws Exception { + public void testGetAll() { MATCHER.assertCollectionEquals(MEALS, service.getAll(USER_ID)); } @Test - public void testGetBetween() throws Exception { + public void testGetBetween() { MATCHER.assertCollectionEquals(Arrays.asList(MEAL3, MEAL2, MEAL1), service.getBetweenDates(LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID)); } From 6956bfe30e153ec0082001965b13a5b9ec508633 Mon Sep 17 00:00:00 2001 From: Drag Date: Tue, 18 Apr 2017 13:40:51 +0300 Subject: [PATCH 52/91] HW 4.1 --- .../ru/javawebinar/topjava/model/Meal.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 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..322378b783be 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,7 +1,9 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import org.hibernate.validator.constraints.NotBlank; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -10,14 +12,24 @@ * GKislin * 11.01.2015. */ +@Entity +@Table(name = "meals") public class Meal extends BaseEntity { + @Column(name = "date_time", nullable = false, columnDefinition = "timestamp default now()") + @NotBlank private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotBlank private String description; + @Column(name = "calories", nullable = false) + @NotBlank + @Range(min = 10, max = 10000) private int calories; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE}) + @JoinColumn(name = "user_id", nullable = false) private User user; public Meal() { From fc6e4b795767d5792f19fad5f5295263bf4353ea Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 20 Apr 2017 21:55:00 +0300 Subject: [PATCH 53/91] Revert: HW 4.1 --- .../ru/javawebinar/topjava/model/Meal.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 322378b783be..6e3c3b79ccdc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,9 +1,7 @@ package ru.javawebinar.topjava.model; -import org.hibernate.validator.constraints.NotBlank; -import org.hibernate.validator.constraints.Range; - -import javax.persistence.*; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -12,24 +10,14 @@ * GKislin * 11.01.2015. */ -@Entity -@Table(name = "meals") public class Meal extends BaseEntity { - @Column(name = "date_time", nullable = false, columnDefinition = "timestamp default now()") - @NotBlank private LocalDateTime dateTime; - @Column(name = "description", nullable = false) - @NotBlank private String description; - @Column(name = "calories", nullable = false) - @NotBlank - @Range(min = 10, max = 10000) private int calories; - @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE}) - @JoinColumn(name = "user_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) private User user; public Meal() { From aec7b0765e6323fde131cf3cc13de17790671b88 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 20 Apr 2017 22:03:11 +0300 Subject: [PATCH 54/91] HW 4 31 --- .../repository/jpa/JpaMealRepositoryImpl.java | 51 ++----------------- .../ru/javawebinar/topjava/MealTestData.java | 14 +---- .../topjava/service/MealServiceTest.java | 47 ++++------------- 3 files changed, 15 insertions(+), 97 deletions(-) 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 aded86ae0467..052d875e8b71 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -1,78 +1,37 @@ package ru.javawebinar.topjava.repository.jpa; import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.MealRepository; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @Repository -@Transactional(readOnly = true) public class JpaMealRepositoryImpl implements MealRepository { - @PersistenceContext - private EntityManager em; - @Override - @Transactional public Meal save(Meal meal, int userId) { - if (meal.isNew()) { - User ref = em.getReference(User.class, userId); - meal.setUser(ref); - em.persist(meal); - return meal; - } else { - Meal dbMeal = em.find(Meal.class, meal.getId()); - if (dbMeal != null && dbMeal.getUser().getId() == userId) { - dbMeal.setDateTime(meal.getDateTime()); - dbMeal.setDescription(meal.getDescription()); - dbMeal.setCalories(meal.getCalories()); - return em.merge(dbMeal); - } - else - return null; - } + return null; } @Override - @Transactional public boolean delete(int id, int userId) { - return em - .createNamedQuery(Meal.DELETE) - .setParameter("id", id) - .setParameter("user_id", userId) - .executeUpdate() != 0; + return false; } @Override public Meal get(int id, int userId) { - Meal dbMeal = em.find(Meal.class, id); - if (dbMeal != null && dbMeal.getUser().getId() == userId) - return dbMeal; - else - return null; + return null; } @Override public List getAll(int userId) { - return em - .createNamedQuery(Meal.ALL_SORTED, Meal.class) - .setParameter("user_id", userId) - .getResultList(); + return null; } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return em - .createNamedQuery(Meal.GET_BETWEEN, Meal.class) - .setParameter("user_id", userId) - .setParameter("start_date", startDate) - .setParameter("end_date", endDate) - .getResultList(); + return null; } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index cb0846a7e8ee..9d362edc78bf 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -25,16 +25,6 @@ public class MealTestData { 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); - static { - MEAL1.setUser(UserTestData.USER); - MEAL2.setUser(UserTestData.USER); - MEAL3.setUser(UserTestData.USER); - MEAL4.setUser(UserTestData.USER); - MEAL5.setUser(UserTestData.USER); - MEAL6.setUser(UserTestData.USER); - ADMIN_MEAL1.setUser(UserTestData.ADMIN); - ADMIN_MEAL2.setUser(UserTestData.ADMIN); - } public static final List MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); @@ -43,8 +33,6 @@ public static Meal getCreated() { } public static Meal getUpdated() { - Meal result = new Meal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); - result.setUser(UserTestData.USER); - return result; + return new Meal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); } } diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index f9e76f069870..6f5679866972 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,13 +1,7 @@ package ru.javawebinar.topjava.service; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; import org.junit.runner.RunWith; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; @@ -37,63 +31,40 @@ public class MealServiceTest { SLF4JBridgeHandler.install(); } - private static final Logger logger = LoggerFactory.getLogger(MealServiceTest.class); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Rule - public TestWatcher watchman = new TestWatcher() { - private long start; - - @Override - protected void starting(Description description) { - start = System.currentTimeMillis(); - } - - @Override - protected void finished(Description description) { - long end = System.currentTimeMillis(); - logger.info(description.getMethodName() + " - " + (end - start) + "ms"); - } - }; - @Autowired private MealService service; @Test - public void testDelete() { + public void testDelete() throws Exception { service.delete(MEAL1_ID, USER_ID); MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID)); } - @Test - public void testDeleteNotFound() { - thrown.expect(NotFoundException.class); + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { service.delete(MEAL1_ID, 1); } @Test - public void testSave() { + 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() { + public void testGet() throws Exception { Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); MATCHER.assertEquals(ADMIN_MEAL1, actual); } - @Test + @Test(expected = NotFoundException.class) public void testGetNotFound() throws Exception { - thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @Test - public void testUpdate() { + public void testUpdate() throws Exception { Meal updated = getUpdated(); service.update(updated, USER_ID); MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); @@ -105,12 +76,12 @@ public void testUpdateNotFound() throws Exception { } @Test - public void testGetAll() { + public void testGetAll() throws Exception { MATCHER.assertCollectionEquals(MEALS, service.getAll(USER_ID)); } @Test - public void testGetBetween() { + 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)); } From 863a540c18126622f0f8c89d10b0fa73fed606dd Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 20 Apr 2017 22:11:14 +0300 Subject: [PATCH 55/91] 5 0 fix refactoring --- .travis.yml | 5 ++++ pom.xml | 6 ----- .../javawebinar/topjava/model/BaseEntity.java | 4 ++- .../ru/javawebinar/topjava/model/Meal.java | 4 --- .../ru/javawebinar/topjava/model/User.java | 2 +- .../repository/jpa/JpaUserRepositoryImpl.java | 2 +- .../topjava/matcher/ModelMatcher.java | 26 +++++++++---------- .../mock/InMemoryMealRepositoryImpl.java | 4 +-- .../mock/InMemoryUserRepositoryImpl.java | 4 +-- .../topjava/service/UserServiceTest.java | 6 +++-- 10 files changed, 29 insertions(+), 34 deletions(-) diff --git a/.travis.yml b/.travis.yml index a17534219221..65c75d2dd58f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,11 @@ before_script: - psql -c 'create database topjava' -U postgres - psql -c 'create user "user"; grant all privileges on database topjava to "user"' -U postgres +# https://docs.travis-ci.com/user/customizing-the-build#Building-Specific-Branches +branches: + only: + - master + # https://docs.travis-ci.com/user/notifications#Configuring-email-notifications #notifications: # email: false \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3045740bb997..434dbde6e055 100644 --- a/pom.xml +++ b/pom.xml @@ -122,12 +122,6 @@ hibernate-core ${hibernate.version} - - javax.transaction - jta - 1.1 - runtime - org.hibernate hibernate-validator diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index e6a59425acbb..ebfea6b0ce3a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -3,12 +3,14 @@ import javax.persistence.*; @MappedSuperclass + +// http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) public class BaseEntity { public static final int START_SEQ = 100000; @Id - @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1) + @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") protected Integer id; diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 6e3c3b79ccdc..7798aadbb809 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -6,10 +6,6 @@ import java.time.LocalDateTime; import java.time.LocalTime; -/** - * GKislin - * 11.01.2015. - */ public class Meal extends BaseEntity { private LocalDateTime dateTime; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 4411c595121c..57bc4d1475e9 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -35,7 +35,7 @@ public class User extends NamedEntity { @Length(min = 5) private String password; - @Column(name = "enabled", nullable = false) + @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") private boolean enabled = true; @Column(name = "registered", columnDefinition = "timestamp default now()") diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java index d8af528cb42d..f974628dafb8 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -49,7 +49,7 @@ 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"); + 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; diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java index 74f9ac6f81d4..f51ac5e86697 100644 --- a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java +++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java @@ -8,29 +8,29 @@ /** * This test Matcher assert equality of beans and collections - * - * It wrap every entity by Wrapper before apply to Assert.assertEquals - * in order to compare them by custom comparator. - * Default comparator is String.valueOf(entity) + *

    + * It wraps every entity by Wrapper before apply to Assert.assertEquals + * and every element of collection before call Collection equals + * in order to compare them by custom Equality. + *

    + * Default equality is String.valueOf(entity) * * @param : Entity */ public class ModelMatcher { - private static final Comparator DEFAULT_COMPARATOR = - (Object expected, Object actual) -> expected == actual || String.valueOf(expected).equals(String.valueOf(actual)); - private Comparator comparator; + private Equality equality; - public interface Comparator { - boolean compare(T expected, T actual); + public interface Equality { + boolean areEqual(T expected, T actual); } public ModelMatcher() { - this((Comparator) DEFAULT_COMPARATOR); + this((T expected, T actual) -> expected == actual || String.valueOf(expected).equals(String.valueOf(actual))); } - public ModelMatcher(Comparator comparator) { - this.comparator = comparator; + public ModelMatcher(Equality equality) { + this.equality = equality; } private class Wrapper { @@ -45,7 +45,7 @@ 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; + return entity != null ? equality.areEqual(entity, that.entity) : that.entity == null; } @Override 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 94c877778b97..388e8162bbda 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -22,8 +22,6 @@ public class InMemoryMealRepositoryImpl implements MealRepository { private static final Logger LOG = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); - 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); @@ -77,7 +75,7 @@ public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateT private Stream getAllAsStream(int userId) { Map meals = repository.get(userId); return meals == null ? - Stream.empty() : meals.values().stream().sorted(MEAL_COMPARATOR); + Stream.empty() : meals.values().stream().sorted(Comparator.comparing(Meal::getDateTime).reversed()); } } 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 5dcb5be5567e..a8464fd23657 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -14,8 +14,6 @@ @Repository public class InMemoryUserRepositoryImpl implements UserRepository { - private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName).thenComparing(User::getEmail); - private Map repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); @@ -41,7 +39,7 @@ public User get(int id) { @Override public List getAll() { return repository.values().stream() - .sorted(USER_COMPARATOR) + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) .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 index 70ee04f14734..5c03e5f22b3b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -8,7 +8,7 @@ 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 org.springframework.test.context.junit4.SpringRunner; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -23,11 +23,13 @@ "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" }) -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class UserServiceTest { static { + // Only for postgres driver logging + // It uses java.util.logging and logged via jul-to-slf4j bridge SLF4JBridgeHandler.install(); } From eb128e16f915aa97646484782e9bd8f71772ff71 Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 21 Apr 2017 11:19:53 +0300 Subject: [PATCH 56/91] 5 1 HW4 --- .../ru/javawebinar/topjava/model/Meal.java | 30 ++++++++++++- .../repository/jpa/JpaMealRepositoryImpl.java | 43 ++++++++++++++++--- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 7798aadbb809..9e293ffe1c9d 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,19 +1,45 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import org.hibernate.validator.constraints.NotBlank; +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; +@SuppressWarnings("JpaQlInspection") +@NamedQueries({ + @NamedQuery(name = Meal.ALL_SORTED, query = "SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC"), + @NamedQuery(name = Meal.DELETE, query = "DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId"), + @NamedQuery(name = Meal.GET_BETWEEN, query = "SELECT m FROM Meal m " + + "WHERE m.user.id=:userId AND m.dateTime 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 ALL_SORTED = "Meal.getAll"; + public static final String DELETE = "Meal.delete"; + public static final String GET_BETWEEN = "Meal.getBetween"; + + @Column(name = "date_time", nullable = false) + @NotNull private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotBlank private String description; + @Column(name = "calories", nullable = false) + @Range(min = 10, max = 5000) private int calories; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + @NotNull private User user; public Meal() { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java index 052d875e8b71..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,37 +1,70 @@ package ru.javawebinar.topjava.repository.jpa; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.MealRepository; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; +/** + * User: gkisline + * Date: 26.08.2014 + */ + @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; + Meal meal = em.find(Meal.class, id); + return meal != null && meal.getUser().getId() == userId ? meal : null; } @Override public List getAll(int userId) { - return null; + return em.createNamedQuery(Meal.ALL_SORTED, Meal.class) + .setParameter("userId", userId) + .getResultList(); } @Override public List 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 cc455f2092d9fa04138754e6bbea3116b026dfcc Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 21 Apr 2017 11:38:59 +0300 Subject: [PATCH 57/91] 5 2 fix hibernate issue --- .../javawebinar/topjava/model/BaseEntity.java | 20 +++++++++++++------ .../ru/javawebinar/topjava/model/Meal.java | 2 +- .../topjava/model/NamedEntity.java | 2 +- .../ru/javawebinar/topjava/model/User.java | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index ebfea6b0ce3a..a3811e72c905 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,7 +1,13 @@ package ru.javawebinar.topjava.model; +import org.hibernate.Hibernate; + import javax.persistence.*; +/** + * Do not manipulate new (transient) entries in HashSet/HashMap without overriding hashCode + * http://stackoverflow.com/questions/5031614 + */ @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @@ -12,7 +18,9 @@ public class BaseEntity { @Id @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") - protected Integer id; + // PROPERTY access for id due to bug: https://hibernate.atlassian.net/browse/HHH-3718 + @Access(value = AccessType.PROPERTY) + private Integer id; protected BaseEntity() { } @@ -30,7 +38,7 @@ public Integer getId() { } public boolean isNew() { - return (this.id == null); + return (getId() == null); } @Override @@ -38,20 +46,20 @@ 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; - return id != null && id.equals(that.id); + return getId() != null && getId().equals(that.getId()); } @Override public int hashCode() { - return (id == null) ? 0 : id; + return (getId() == null) ? 0 : getId(); } @Override public String toString() { - return String.format("Entity %s (%s)", getClass().getName(), id); + return String.format("Entity %s (%s)", getClass().getName(), getId()); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 9e293ffe1c9d..69f02f7c9008 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -99,7 +99,7 @@ public void setUser(User user) { @Override public String toString() { return "Meal{" + - "id=" + id + + "id=" + getId() + ", dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + diff --git a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java index 528a70133ed2..78609d6cb98c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java @@ -31,6 +31,6 @@ public String getName() { @Override public String toString() { - return String.format("Entity %s (%s, '%s')", getClass().getName(), id, name); + return String.format("Entity %s (%s, '%s')", getClass().getName(), getId(), name); } } \ 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 57bc4d1475e9..17fb789de0f9 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -118,7 +118,7 @@ public String getPassword() { @Override public String toString() { return "User{" + - "id=" + id + + "id=" + getId() + ", email=" + email + ", name=" + name + ", enabled=" + enabled + From 00bf36da9f801a76439682a75528a767e140e767 Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 21 Apr 2017 11:40:50 +0300 Subject: [PATCH 58/91] 5 3 HW4 optional --- .../topjava/service/MealServiceTest.java | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 6f5679866972..9eb22b1229f7 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,19 +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.slf4j.bridge.SLF4JBridgeHandler; 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 org.springframework.test.context.junit4.SpringRunner; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.time.LocalDate; import java.time.Month; import java.util.Arrays; +import java.util.concurrent.TimeUnit; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; @@ -23,14 +31,40 @@ "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" }) -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { + private static final Logger LOG = LoggerFactory.getLogger(MealServiceTest.class); + private static StringBuilder results = new StringBuilder(); static { + // needed only for java.util.logging (postgres driver) SLF4JBridgeHandler.install(); } + @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 printResult() { + LOG.info("\n---------------------------------" + + "\nTest Duration, ms" + + "\n---------------------------------\n" + + results + + "---------------------------------\n"); + } + @Autowired private MealService service; @@ -40,8 +74,9 @@ public void testDelete() throws Exception { MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID)); } - @Test(expected = NotFoundException.class) + @Test public void testDeleteNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @@ -58,8 +93,9 @@ public void testGet() throws Exception { MATCHER.assertEquals(ADMIN_MEAL1, actual); } - @Test(expected = NotFoundException.class) + @Test public void testGetNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @@ -70,8 +106,10 @@ public void testUpdate() throws Exception { MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID)); } - @Test(expected = NotFoundException.class) + @Test public void testUpdateNotFound() throws Exception { + thrown.expect(NotFoundException.class); + thrown.expectMessage("Not found entity with id=" + MEAL1_ID); service.update(MEAL1, ADMIN_ID); } @@ -83,6 +121,8 @@ public void testGetAll() throws Exception { @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)); + service.getBetweenDates( + LocalDate.of(2015, Month.MAY, 30), + LocalDate.of(2015, Month.MAY, 30), USER_ID)); } } \ No newline at end of file From 701985300e0e9af17f1725f68a1fbc217fed53f4 Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 21 Apr 2017 16:20:35 +0300 Subject: [PATCH 59/91] 5 4 profiles connection pool --- pom.xml | 43 +++++++++++++------ .../java/ru/javawebinar/topjava/Profiles.java | 19 ++++++++ src/main/resources/db/hsqldb.properties | 1 - src/main/resources/db/postgres.properties | 1 - src/main/resources/spring/spring-db.xml | 37 ++++++++++------ .../topjava/service/MealServiceTest.java | 3 ++ .../topjava/service/UserServiceTest.java | 3 ++ 7 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/Profiles.java diff --git a/pom.xml b/pom.xml index 434dbde6e055..a0a1def99c02 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ UTF-8 UTF-8 + 8.5.5 4.3.7.RELEASE @@ -104,19 +105,6 @@ ${spring.version} - - - org.postgresql - postgresql - ${postgresql.version} - - - org.hsqldb - hsqldb - 2.3.4 - - - org.hibernate hibernate-core @@ -164,6 +152,35 @@ + + hsqldb + + + org.hsqldb + hsqldb + 2.3.4 + + + + + postgres + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + provided + + + + true + + diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java new file mode 100644 index 000000000000..b2004c50fcea --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava; + +/** + * User: gkislin + * Date: 19.08.2014 + */ +public class Profiles { + public static final String + POSTGRES_DB = "postgres", + HSQL_DB = "hsqldb"; + + public static final String ACTIVE_DB = POSTGRES_DB; + + public static final String + JDBC = "jdbc", + JPA = "jpa"; + + public static final String REPOSITORY_IMPLEMENTATION = JPA; +} 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/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 8569a84b55f3..d26c20cc1fce 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -9,11 +9,6 @@ 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"> - - - - - @@ -21,15 +16,6 @@ - - - - - - - - @@ -66,4 +52,27 @@ --> + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 9eb22b1229f7..0baafcbfc1a5 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -11,10 +11,12 @@ import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -33,6 +35,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(Profiles.ACTIVE_DB) public class MealServiceTest { private static final Logger LOG = LoggerFactory.getLogger(MealServiceTest.class); private static StringBuilder results = new StringBuilder(); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 5c03e5f22b3b..523cad42caf3 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -5,10 +5,12 @@ import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -25,6 +27,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(Profiles.ACTIVE_DB) public class UserServiceTest { static { From 8dac85bacb760aa2e4d165e61329601240d45375 Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 21 Apr 2017 17:19:26 +0300 Subject: [PATCH 60/91] 5 5 spring data jpa --- pom.xml | 16 +++++-- .../javawebinar/topjava/model/BaseEntity.java | 5 ++- .../datajpa/CrudMealRepository.java | 7 ++++ .../datajpa/CrudUserRepository.java | 33 +++++++++++++++ .../datajpa/DataJpaMealRepositoryImpl.java | 41 ++++++++++++++++++ .../datajpa/DataJpaUserRepositoryImpl.java | 42 +++++++++++++++++++ src/main/resources/spring/spring-db.xml | 16 ++++++- 7 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java diff --git a/pom.xml b/pom.xml index a0a1def99c02..b2448043329b 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 8.5.5 4.3.7.RELEASE + 1.11.1.RELEASE 1.2.2 @@ -100,9 +101,9 @@ - org.springframework - spring-orm - ${spring.version} + org.springframework.data + spring-data-jpa + ${spring-data-jpa.version} @@ -184,5 +185,14 @@ + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index a3811e72c905..5ef82d46ec40 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.model; import org.hibernate.Hibernate; +import org.springframework.data.domain.Persistable; import javax.persistence.*; @@ -12,7 +13,7 @@ // 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 @@ -33,10 +34,12 @@ public void setId(Integer id) { this.id = id; } + @Override public Integer getId() { return id; } + @Override public boolean isNew() { return (getId() == null); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java new file mode 100644 index 000000000000..a3659675c910 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.javawebinar.topjava.model.Meal; + +public interface CrudMealRepository extends JpaRepository { +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java new file mode 100644 index 000000000000..9e4974884ad0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -0,0 +1,33 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +@Transactional(readOnly = true) +public interface CrudUserRepository extends JpaRepository { + @Transactional + @Modifying +// @Query(name = User.DELETE) + @Query("DELETE FROM User u WHERE u.id=:id") + int delete(@Param("id") int id); + + @Override + @Transactional + User save(User user); + + @Override + User findOne(Integer id); + + @Override + List findAll(Sort sort); + + User getByEmail(String email); + +} diff --git a/src/main/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..1b00b263969d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -0,0 +1,41 @@ +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; + +@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/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..249d61937aad --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.List; + +@Repository +public class DataJpaUserRepositoryImpl implements UserRepository { + private static final Sort SORT_NAME_EMAIL = new Sort("name", "email"); + + @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(SORT_NAME_EMAIL); + } +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index d26c20cc1fce..ac3cb1081f25 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -4,12 +4,17 @@ 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"> - + + + + @@ -75,4 +80,11 @@ p:username="${database.username}" p:password="${database.password}"/> + + + + + + + \ No newline at end of file From 742601b64f872e3ac3c0b0e2d9a86ceb9cef3785 Mon Sep 17 00:00:00 2001 From: Drag Date: Sun, 23 Apr 2017 19:27:39 +0300 Subject: [PATCH 61/91] 5 6 spring cache --- pom.xml | 12 +- .../topjava/service/UserService.java | 2 + .../topjava/service/UserServiceImpl.java | 12 + 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 | 21 + .../topjava/service/UserServiceTest.java | 6 + 8 files changed, 500 insertions(+), 1 deletion(-) 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 b2448043329b..fecb0d583fa8 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,9 @@ 5.2.9.Final 5.4.1.Final + + + 2.10.3 @@ -91,7 +94,7 @@ org.springframework - spring-context + spring-context-support ${spring.version} @@ -116,12 +119,19 @@ hibernate-validator ${hibernate-validator.version} + javax.el javax.el-api 3.0.0 + + net.sf.ehcache + ehcache + ${ehcache.version} + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index b48ae667efab..1dae83438e7b 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -19,4 +19,6 @@ public interface UserService { List getAll(); void update(User user); + + void evictCache(); } \ 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 3fa606c44586..8c72442f90f2 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; @@ -22,12 +24,14 @@ public UserServiceImpl(UserRepository repository) { this.repository = 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) throws NotFoundException { checkNotFoundWithId(repository.delete(id), id); @@ -44,14 +48,22 @@ public User getByEmail(String email) throws NotFoundException { return 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() { + // only for evict cache + } } \ No newline at end of file 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..c97ed199a023 --- /dev/null +++ b/src/main/resources/spring/spring-tools.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + \ 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 523cad42caf3..25bd0316259c 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.service; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.bridge.SLF4JBridgeHandler; @@ -39,6 +40,11 @@ public class UserServiceTest { @Autowired private 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 ddb505ed57448b2ddbe24bdb15fe286c5ad9ccd9 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 13:37:03 +0300 Subject: [PATCH 62/91] 6 0 fix --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index fecb0d583fa8..185318afe0d9 100644 --- a/pom.xml +++ b/pom.xml @@ -95,7 +95,6 @@ org.springframework spring-context-support - ${spring.version} commons-logging @@ -156,7 +155,6 @@ org.springframework spring-test - ${spring.version} test From 02085d58e00ce3fd745bf06fb89e404db617aa4d Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 14:15:17 +0300 Subject: [PATCH 63/91] 6 01 HW5 data jpa --- .../datajpa/CrudMealRepository.java | 25 ++++++++++++++++++- .../datajpa/DataJpaMealRepositoryImpl.java | 24 ++++++++++++------ src/test/resources/logback-test.xml | 4 +-- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java index a3659675c910..853daceeae25 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -1,7 +1,30 @@ package ru.javawebinar.topjava.repository.datajpa; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; +import java.time.LocalDateTime; +import java.util.List; + +@Transactional(readOnly = true) public interface CrudMealRepository extends JpaRepository { -} + + @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); +} \ No newline at end of file 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 1b00b263969d..24d6ef040edb 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; @@ -12,30 +13,39 @@ public class DataJpaMealRepositoryImpl implements MealRepository { @Autowired - private CrudMealRepository crudRepository; + private CrudMealRepository crudMealRepository; + + @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 crudMealRepository.save(meal); } @Override public boolean delete(int id, int userId) { - return false; + return crudMealRepository.delete(id, userId) != 0; } @Override public Meal get(int id, int userId) { - return null; + Meal meal = crudMealRepository.findOne(id); + return meal != null && meal.getUser().getId() == userId ? meal : null; } @Override public List getAll(int userId) { - return null; + return crudMealRepository.getAll(userId); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return crudMealRepository.getBetween(startDate, endDate, userId); } } diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 6b1fa3682055..afe896f21924 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -11,8 +11,8 @@ - - + + From e55f73697493d7ab3a6483e8d449c28b4654c9d2 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 14:16:41 +0300 Subject: [PATCH 64/91] 6 02 HW5 profiles --- .../java/ru/javawebinar/topjava/Profiles.java | 3 +- src/main/resources/spring/spring-db.xml | 84 ++++++++++--------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index b2004c50fcea..a5c6914968a2 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -13,7 +13,8 @@ public class Profiles { public static final String JDBC = "jdbc", - JPA = "jpa"; + JPA = "jpa", + DATAJPA = "datajpa"; public static final String REPOSITORY_IMPLEMENTATION = JPA; } diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index ac3cb1081f25..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 b9af7a02c97f116804ceb0d2997ddf6efdfcff34 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 14:17:54 +0300 Subject: [PATCH 65/91] 6 03 HW5 tests --- ...Test.java => AbstractMealServiceTest.java} | 55 +--------------- .../topjava/service/AbstractServiceTest.java | 62 +++++++++++++++++++ ...Test.java => AbstractUserServiceTest.java} | 25 +------- .../datajpa/DataJpaMealServiceTest.java | 10 +++ .../datajpa/DataJpaUserServiceTest.java | 10 +++ .../service/jdbc/JdbcMealServiceTest.java | 10 +++ .../service/jdbc/JdbcUserServiceTest.java | 10 +++ .../service/jpa/JpaMealServiceTest.java | 10 +++ .../service/jpa/JpaUserServiceTest.java | 10 +++ 9 files changed, 125 insertions(+), 77 deletions(-) rename src/test/java/ru/javawebinar/topjava/service/{MealServiceTest.java => AbstractMealServiceTest.java} (53%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java rename src/test/java/ru/javawebinar/topjava/service/{UserServiceTest.java => AbstractUserServiceTest.java} (73%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java similarity index 53% rename from src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 0baafcbfc1a5..c44325741149 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,72 +1,19 @@ 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.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.Profiles; 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(SpringRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.ACTIVE_DB) -public class MealServiceTest { - private static final Logger LOG = LoggerFactory.getLogger(MealServiceTest.class); - private static StringBuilder results = new StringBuilder(); - - static { - // needed only for java.util.logging (postgres driver) - SLF4JBridgeHandler.install(); - } - - @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 printResult() { - LOG.info("\n---------------------------------" + - "\nTest Duration, ms" + - "\n---------------------------------\n" + - results + - "---------------------------------\n"); - } +public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired private MealService service; 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..cecf58981ead --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -0,0 +1,62 @@ +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.slf4j.bridge.SLF4JBridgeHandler; +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; + +@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("%-95s %7d", description.getDisplayName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result).append('\n'); + LOG.info(result + " ms\n"); + } + }; + + static { + // needed only for java.util.logging (postgres driver) + SLF4JBridgeHandler.install(); + } + + @AfterClass + public static void printResult() { + LOG.info("\n---------------------------------" + + "\nTest Duration, ms" + + "\n---------------------------------\n" + + results + + "---------------------------------\n"); + 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 73% rename from src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 25bd0316259c..789fd99ff086 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -2,16 +2,8 @@ import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -22,20 +14,7 @@ import static ru.javawebinar.topjava.UserTestData.*; -@ContextConfiguration({ - "classpath:spring/spring-app.xml", - "classpath:spring/spring-db.xml" -}) -@RunWith(SpringRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.ACTIVE_DB) -public class UserServiceTest { - - static { - // Only for postgres driver logging - // It uses java.util.logging and logged via jul-to-slf4j bridge - SLF4JBridgeHandler.install(); - } +public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private UserService service; @@ -44,7 +23,7 @@ public class UserServiceTest { 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)); 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..e28cc6d6bc53 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; + +@ActiveProfiles(DATAJPA) +public class DataJpaMealServiceTest extends AbstractMealServiceTest { +} diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java new file mode 100644 index 000000000000..a0757e739339 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; + +@ActiveProfiles(DATAJPA) +public class DataJpaUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/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 8a12093ffac3d6c7f65655e8a815c2613989f8de Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 14:43:49 +0300 Subject: [PATCH 66/91] 6 04 HW5 optional fix jdbc profiles --- .../java/ru/javawebinar/topjava/Profiles.java | 2 +- .../jdbc/JdbcMealRepositoryImpl.java | 41 +++++++++++++++---- .../javawebinar/topjava/web/MealServlet.java | 9 ++-- .../ru/javawebinar/topjava/SpringMain.java | 9 ++-- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index a5c6914968a2..d6418855b781 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -9,7 +9,7 @@ public class Profiles { POSTGRES_DB = "postgres", HSQL_DB = "hsqldb"; - public static final String ACTIVE_DB = POSTGRES_DB; + public static final String ACTIVE_DB = HSQL_DB; public static final String JDBC = "jdbc", 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 fa514658576b..a835e1861e74 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,32 +10,54 @@ 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; @Repository -public class JdbcMealRepositoryImpl implements MealRepository { +public abstract class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); - private final JdbcTemplate jdbcTemplate; + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; - private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private SimpleJdbcInsert insertMeal; - private final SimpleJdbcInsert insertMeal; + protected abstract T toDbDateTime(LocalDateTime ldt); @Autowired - public JdbcMealRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + private void setDataSource(DataSource dataSource) { this.insertMeal = new SimpleJdbcInsert(dataSource) .withTableName("meals") .usingGeneratedKeyColumns("id"); + } - this.jdbcTemplate = jdbcTemplate; - this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + @Repository + @Profile(Profiles.POSTGRES_DB) + public static class Java8JdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { + @Override + protected LocalDateTime toDbDateTime(LocalDateTime ldt) { + return ldt; + } + } + + @Repository + @Profile(Profiles.HSQL_DB) + public static class TimestampJdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { + + @Override + protected Timestamp toDbDateTime(LocalDateTime ldt) { + return Timestamp.valueOf(ldt); + } } @Override @@ -43,7 +66,7 @@ public Meal save(Meal meal, int userId) { .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()) { @@ -83,6 +106,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 9e3cd1660be1..7ec770f4aa1d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.web; -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.DateTimeUtil; import ru.javawebinar.topjava.web.meal.MealRestController; @@ -20,13 +20,16 @@ public class MealServlet extends HttpServlet { - 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.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); + springContext.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.REPOSITORY_IMPLEMENTATION); + 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 a9083a6d5b54..5b494fbbcb01 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -1,7 +1,6 @@ package ru.javawebinar.topjava; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.GenericXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.MealWithExceed; @@ -17,7 +16,11 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml","spring/mock.xml")) { + try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) { + appCtx.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.REPOSITORY_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(new User(null, "userName", "email", "password", Role.ROLE_ADMIN)); From 1aa49102defc0a78fb74e2f46173557098aab24a Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 14:46:08 +0300 Subject: [PATCH 67/91] 6 05 update hsqldb --- pom.xml | 2 +- .../jdbc/JdbcMealRepositoryImpl.java | 41 ++++--------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/pom.xml b/pom.xml index 185318afe0d9..2581fe5bb4ce 100644 --- a/pom.xml +++ b/pom.xml @@ -167,7 +167,7 @@ org.hsqldb hsqldb - 2.3.4 + 2.4.0 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 a835e1861e74..fa514658576b 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -1,7 +1,6 @@ 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; @@ -10,54 +9,32 @@ 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; @Repository -public abstract class JdbcMealRepositoryImpl implements MealRepository { +public class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); - @Autowired - private JdbcTemplate jdbcTemplate; - - @Autowired - private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final JdbcTemplate jdbcTemplate; - private SimpleJdbcInsert insertMeal; + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; - protected abstract T toDbDateTime(LocalDateTime ldt); + private final SimpleJdbcInsert insertMeal; @Autowired - private void setDataSource(DataSource dataSource) { + public JdbcMealRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { this.insertMeal = new SimpleJdbcInsert(dataSource) .withTableName("meals") .usingGeneratedKeyColumns("id"); - } - @Repository - @Profile(Profiles.POSTGRES_DB) - public static class Java8JdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { - @Override - protected LocalDateTime toDbDateTime(LocalDateTime ldt) { - return ldt; - } - } - - @Repository - @Profile(Profiles.HSQL_DB) - public static class TimestampJdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { - - @Override - protected Timestamp toDbDateTime(LocalDateTime ldt) { - return Timestamp.valueOf(ldt); - } + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; } @Override @@ -66,7 +43,7 @@ public Meal save(Meal meal, int userId) { .addValue("id", meal.getId()) .addValue("description", meal.getDescription()) .addValue("calories", meal.getCalories()) - .addValue("date_time", toDbDateTime(meal.getDateTime())) + .addValue("date_time", meal.getDateTime()) .addValue("user_id", userId); if (meal.isNew()) { @@ -106,6 +83,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, toDbDateTime(startDate), toDbDateTime(endDate)); + ROW_MAPPER, userId, startDate, endDate); } } From 5a13e81e2e1594f2028678d6683e19a1a9d5af02 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 15:23:37 +0300 Subject: [PATCH 68/91] 6 06 HW5 optional fetch join --- .../java/ru/javawebinar/topjava/model/User.java | 9 +++++++++ .../topjava/repository/MealRepository.java | 4 ++++ .../topjava/repository/UserRepository.java | 4 ++++ .../repository/datajpa/CrudMealRepository.java | 3 +++ .../repository/datajpa/CrudUserRepository.java | 2 ++ .../datajpa/DataJpaMealRepositoryImpl.java | 5 +++++ .../datajpa/DataJpaUserRepositoryImpl.java | 5 +++++ .../topjava/service/MealService.java | 2 ++ .../topjava/service/MealServiceImpl.java | 7 ++++++- .../topjava/service/UserService.java | 2 ++ .../topjava/service/UserServiceImpl.java | 5 +++++ .../service/AbstractMealServiceTest.java | 2 +- .../service/AbstractUserServiceTest.java | 2 +- .../service/datajpa/DataJpaMealServiceTest.java | 17 +++++++++++++++++ .../service/datajpa/DataJpaUserServiceTest.java | 16 ++++++++++++++++ 15 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 17fb789de0f9..94b08ecbf1c0 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -8,6 +8,7 @@ import javax.persistence.*; import java.util.Date; import java.util.EnumSet; +import java.util.List; import java.util.Set; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; @@ -51,6 +52,10 @@ public class User extends NamedEntity { @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") + @OrderBy("dateTime DESC") + protected List meals; + public User() { } @@ -115,6 +120,10 @@ public String getPassword() { return password; } + public List getMeals() { + return meals; + } + @Override public String toString() { return "User{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index 2e5864b39816..33f37e890b3b 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 List 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 c37b84d5fd77..02881548a6d8 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -17,4 +17,8 @@ public interface UserRepository { User getByEmail(String email); List getAll(); + + default User getWithMeals(int id){ + throw new UnsupportedOperationException(); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java index 853daceeae25..0260683327eb 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -27,4 +27,7 @@ public interface CrudMealRepository extends JpaRepository { @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); } \ No newline at end of file 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 9e4974884ad0..2217272b4acb 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -30,4 +30,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 24d6ef040edb..962a8502f370 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -48,4 +48,9 @@ public List getAll(int userId) { public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { return crudMealRepository.getBetween(startDate, endDate, userId); } + + @Override + public Meal getWithUser(int id, int userId) { + return crudMealRepository.getWithUser(id, userId); + } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java index 249d61937aad..3551a78522c0 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -39,4 +39,9 @@ public User getByEmail(String email) { public List getAll() { return crudRepository.findAll(SORT_NAME_EMAIL); } + + @Override + public User getWithMeals(int id) { + return crudRepository.getWithMeals(id); + } } diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index edd39036c245..f1483afb9574 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -24,4 +24,6 @@ default List getBetweenDates(LocalDate startDate, LocalDate endDate, int u Meal update(Meal meal, int userId) throws NotFoundException; Meal save(Meal meal, int userId); + + Meal getWithUser(int id, int userId); } \ 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 a8d324673f88..bb43d9f303c2 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -2,8 +2,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import ru.javawebinar.topjava.model.Meal; import org.springframework.util.Assert; +import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import java.time.LocalDateTime; @@ -54,4 +54,9 @@ public Meal save(Meal meal, int userId) { Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } + + @Override + public Meal getWithUser(int id, int userId) { + return checkNotFoundWithId(repository.getWithUser(id, userId), id); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index 1dae83438e7b..11cba2fde46e 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -21,4 +21,6 @@ public interface UserService { void update(User user); void evictCache(); + + User getWithMeals(int id); } \ 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 8c72442f90f2..26741041d468 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -66,4 +66,9 @@ public void update(User user) { public void evictCache() { // only for evict cache } + + @Override + public User getWithMeals(int id) { + return checkNotFoundWithId(repository.getWithMeals(id), id); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index c44325741149..3683bcfdbbde 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -16,7 +16,7 @@ public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired - private MealService service; + protected MealService service; @Test public void testDelete() throws Exception { diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 789fd99ff086..ece76f343e0e 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -17,7 +17,7 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired - private UserService service; + protected UserService service; @Before public void setUp() throws Exception { diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java index e28cc6d6bc53..0a76c1c9da1c 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -1,10 +1,27 @@ package ru.javawebinar.topjava.service.datajpa; +import org.junit.Test; import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.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); + } } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index a0757e739339..999479ec822e 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -1,10 +1,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 165c73d20b0f91ab8c385727a4a67c9137cab70a Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 27 Apr 2017 15:32:40 +0300 Subject: [PATCH 69/91] 6 07 HW5 graph batch size --- src/main/java/ru/javawebinar/topjava/model/User.java | 8 +++++++- .../topjava/repository/datajpa/CrudUserRepository.java | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 94b08ecbf1c0..e5a9b7030d72 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import org.hibernate.annotations.BatchSize; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; @@ -16,12 +17,15 @@ @NamedQueries({ @NamedQuery(name = User.DELETE, query = "DELETE FROM User u WHERE u.id=:id"), @NamedQuery(name = User.BY_EMAIL, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email=?1"), - @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"), + @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u ORDER BY u.name, u.email"), }) @Entity +//@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 BY_EMAIL = "User.getByEmail"; public static final String ALL_SORTED = "User.getAllSorted"; @@ -46,6 +50,8 @@ public class User extends NamedEntity { @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") @ElementCollection(fetch = FetchType.EAGER) +// @Fetch(FetchMode.SUBSELECT) + @BatchSize(size = 200) private Set roles; @Column(name = "calories_per_day", columnDefinition = "int default 2000") diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java index 2217272b4acb..16971e7374b9 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.repository.datajpa; import org.springframework.data.domain.Sort; +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; @@ -14,7 +15,6 @@ public interface CrudUserRepository extends JpaRepository { @Transactional @Modifying -// @Query(name = User.DELETE) @Query("DELETE FROM User u WHERE u.id=:id") int delete(@Param("id") int id); @@ -30,6 +30,10 @@ public interface CrudUserRepository extends JpaRepository { User getByEmail(String email); - @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") +// @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") +// @EntityGraph(value = User.GRAPH_WITH_MEALS) + + @EntityGraph(attributePaths={"meals", "roles"}) + @Query("SELECT u FROM User u WHERE u.id=?1") User getWithMeals(int id); } From 59a5bc8aa5863ad5d96efe2fbaa301d8f571a1db Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 28 Apr 2017 13:22:52 +0300 Subject: [PATCH 70/91] 6 08 profile resolver --- .../java/ru/javawebinar/topjava/Profiles.java | 23 +++++++++++++++---- .../javawebinar/topjava/web/MealServlet.java | 2 +- .../topjava/ActiveDbProfileResolver.java | 12 ++++++++++ .../ru/javawebinar/topjava/SpringMain.java | 2 +- .../topjava/service/AbstractServiceTest.java | 5 ++-- 5 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index d6418855b781..d0bb8849dc4d 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -1,9 +1,5 @@ package ru.javawebinar.topjava; -/** - * User: gkislin - * Date: 19.08.2014 - */ public class Profiles { public static final String POSTGRES_DB = "postgres", @@ -17,4 +13,23 @@ public class Profiles { DATAJPA = "datajpa"; public static final String REPOSITORY_IMPLEMENTATION = JPA; + + private static final String + POSTGRES_DB = "postgres", + HSQL_DB = "hsqldb"; + +// Get DB profile depending of DB driver in classpath + public static String getActiveDbProfile() { + try { + Class.forName("org.postgresql.Driver"); + return POSTGRES_DB; + } catch (ClassNotFoundException ex) { + try { + Class.forName("org.hsqldb.jdbcDriver"); + return Profiles.HSQL_DB; + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Could not resolve DB profile"); + } + } + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 7ec770f4aa1d..4b0886893ac2 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -28,7 +28,7 @@ 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.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); - springContext.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.REPOSITORY_IMPLEMENTATION); + springContext.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); springContext.refresh(); mealController = springContext.getBean(MealRestController.class); } diff --git a/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java new file mode 100644 index 000000000000..05d47b19f214 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava; + +import org.springframework.test.context.ActiveProfilesResolver; + +//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver +public class ActiveDbProfileResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class aClass) { + return new String[]{Profiles.getActiveDbProfile()}; + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 5b494fbbcb01..a3d619991c8a 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -17,7 +17,7 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) { - appCtx.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.REPOSITORY_IMPLEMENTATION); + appCtx.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); appCtx.load("spring/spring-app.xml", "spring/spring-db.xml"); appCtx.refresh(); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index cecf58981ead..9739c4dc8059 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -14,17 +14,16 @@ 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.ActiveDbProfileResolver; import java.util.concurrent.TimeUnit; -import static ru.javawebinar.topjava.Profiles.ACTIVE_DB; - @ContextConfiguration({ "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" }) @RunWith(SpringJUnit4ClassRunner.class) -@ActiveProfiles(ACTIVE_DB) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) abstract public class AbstractServiceTest { private static final Logger LOG = LoggerFactory.getLogger(AbstractServiceTest.class); From 100293426c8b211d303870cd8663de4988ee5760 Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 28 Apr 2017 13:29:24 +0300 Subject: [PATCH 71/91] 6 09 add test validation --- pom.xml | 8 +++++++ .../java/ru/javawebinar/topjava/Profiles.java | 6 +---- .../service/AbstractMealServiceTest.java | 10 ++++++++ .../topjava/service/AbstractServiceTest.java | 24 +++++++++++++++++++ .../service/AbstractUserServiceTest.java | 10 ++++++++ 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 2581fe5bb4ce..e0b52cfe8dfc 100644 --- a/pom.xml +++ b/pom.xml @@ -119,10 +119,18 @@ ${hibernate-validator.version} + javax.el javax.el-api 3.0.0 + provided + + + org.glassfish + javax.el + 3.0.0 + provided diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index d0bb8849dc4d..696e7d8c03ee 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -5,7 +5,7 @@ public class Profiles { POSTGRES_DB = "postgres", HSQL_DB = "hsqldb"; - public static final String ACTIVE_DB = HSQL_DB; + public static final String ACTIVE_DB = POSTGRES_DB; public static final String JDBC = "jdbc", @@ -14,10 +14,6 @@ public class Profiles { public static final String REPOSITORY_IMPLEMENTATION = JPA; - private static final String - POSTGRES_DB = "postgres", - HSQL_DB = "hsqldb"; - // Get DB profile depending of DB driver in classpath public static String getActiveDbProfile() { try { diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 3683bcfdbbde..d0c7b5f1f9ab 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -5,10 +5,12 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; +import javax.validation.ConstraintViolationException; import java.time.LocalDate; import java.time.Month; import java.util.Arrays; +import static java.time.LocalDateTime.of; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; @@ -75,4 +77,12 @@ public void testGetBetween() throws Exception { LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID)); } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.save(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), " ", 300), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.save(new Meal(null, null, "Description", 300), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.save(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 9), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.save(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 5001), USER_ID), ConstraintViolationException.class); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index 9739c4dc8059..795540de9f13 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.service; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Rule; import org.junit.rules.ExpectedException; import org.junit.rules.Stopwatch; @@ -18,6 +19,8 @@ import java.util.concurrent.TimeUnit; +import static org.hamcrest.CoreMatchers.instanceOf; + @ContextConfiguration({ "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" @@ -58,4 +61,25 @@ public static void printResult() { "---------------------------------\n"); results.setLength(0); } + + // Check root cause in JUnit: https://github.com/junit-team/junit4/pull/778 + public static void validateRootCause(Runnable runnable, Class exceptionClass) { + try { + runnable.run(); + Assert.fail("Expected " + exceptionClass.getName()); + } catch (Exception e) { + Assert.assertThat(getRootCause(e), instanceOf(exceptionClass)); + } + } + + // http://stackoverflow.com/a/28565320/548473 + public static Throwable getRootCause(Throwable t) { + Throwable result = t; + Throwable cause; + + while (null != (cause = result.getCause()) && (result != cause)) { + result = cause; + } + return result; + } } \ 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 ece76f343e0e..85e40c531e31 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -8,6 +8,7 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; +import javax.validation.ConstraintViolationException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -79,4 +80,13 @@ public void testUpdate() throws Exception { service.update(updated); MATCHER.assertEquals(updated, service.get(USER_ID)); } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.save(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "mail@yandex.ru", "password", 9, true, Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "mail@yandex.ru", "password", 10001, true, Collections.emptySet())), ConstraintViolationException.class); + } } \ No newline at end of file From 0662e6f87bc889b4ffc872f81f696fe08183f0ee Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 28 Apr 2017 15:26:20 +0300 Subject: [PATCH 72/91] 6 10 hibernate cache --- pom.xml | 5 +++++ .../ru/javawebinar/topjava/model/User.java | 4 ++++ .../topjava/repository/JpaUtil.java | 22 +++++++++++++++++++ src/main/resources/cache/ehcache.xml | 3 +++ src/main/resources/spring/spring-db.xml | 8 +++++++ .../service/AbstractUserServiceTest.java | 5 +++++ 6 files changed, 47 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java diff --git a/pom.xml b/pom.xml index e0b52cfe8dfc..f90077c0926c 100644 --- a/pom.xml +++ b/pom.xml @@ -132,6 +132,11 @@ 3.0.0 provided + + org.hibernate + hibernate-ehcache + ${hibernate.version} + net.sf.ehcache diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index e5a9b7030d72..ffe30481bcc3 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,6 +1,8 @@ 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; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; @@ -14,6 +16,7 @@ import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @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"), @@ -46,6 +49,7 @@ public class User extends NamedEntity { @Column(name = "registered", columnDefinition = "timestamp default now()") private Date registered = new Date(); + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Enumerated(EnumType.STRING) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") 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..d740b9f7598e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java @@ -0,0 +1,22 @@ +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().evictAllRegions(); + } +} diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml index 8d420010d88d..b0275e589a4a 100644 --- a/src/main/resources/cache/ehcache.xml +++ b/src/main/resources/cache/ehcache.xml @@ -25,4 +25,7 @@ diskExpiryThreadIntervalSeconds="1" memoryStoreEvictionPolicy="LRU"/> + + diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 1e78287feb20..e37d714383ff 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -64,6 +64,12 @@ + + + + + + @@ -79,6 +85,8 @@ + + diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 85e40c531e31..7f3453d035b7 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 javax.validation.ConstraintViolationException; @@ -20,9 +21,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 38dcc813915f18294f9ac1a9386682cdc5e84aef Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 28 Apr 2017 16:37:19 +0300 Subject: [PATCH 73/91] 6 11 cascade ddl --- src/main/java/ru/javawebinar/topjava/Profiles.java | 2 +- src/main/java/ru/javawebinar/topjava/model/BaseEntity.java | 1 + src/main/java/ru/javawebinar/topjava/model/Meal.java | 3 +++ src/main/java/ru/javawebinar/topjava/model/User.java | 2 +- src/main/resources/spring/spring-db.xml | 7 ++++++- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index 696e7d8c03ee..f71b305890b0 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -12,7 +12,7 @@ public class Profiles { JPA = "jpa", DATAJPA = "datajpa"; - public static final String REPOSITORY_IMPLEMENTATION = JPA; + public static final String REPOSITORY_IMPLEMENTATION = DATAJPA; // Get DB profile depending of DB driver in classpath public static String getActiveDbProfile() { diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java index 5ef82d46ec40..ecb6e0957f94 100644 --- a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java @@ -18,6 +18,7 @@ public class BaseEntity implements Persistable { @Id @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) + // @Column(name = "id", unique = true, nullable = false, columnDefinition = "integer default nextval('global_seq')") @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) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 69f02f7c9008..b2fc2aee8310 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 org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.Range; @@ -39,6 +41,7 @@ public class Meal extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) @NotNull private User user; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index ffe30481bcc3..f9c88759f211 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -62,7 +62,7 @@ public class User extends NamedEntity { @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; - @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") + @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")//, cascade = CascadeType.REMOVE, orphanRemoval = true) @OrderBy("dateTime DESC") protected List meals; diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index e37d714383ff..1f4794fdb200 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -69,7 +69,12 @@ - + From a83fda773645a5b7aa91c189135f6e4dc8be33fc Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 28 Apr 2017 18:02:51 +0300 Subject: [PATCH 74/91] 6 12 spring web --- pom.xml | 17 ++++++++++++++--- .../ru/javawebinar/topjava/web/MealServlet.java | 16 +++------------- .../ru/javawebinar/topjava/web/UserServlet.java | 16 +++++++++++++++- src/main/webapp/WEB-INF/web.xml | 16 ++++++++++++++++ 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index f90077c0926c..c6131a5aace0 100644 --- a/pom.xml +++ b/pom.xml @@ -108,6 +108,17 @@ ${spring-data-jpa.version} + + org.springframework + spring-web + + + commons-logging + commons-logging + + + + org.hibernate hibernate-core @@ -146,9 +157,9 @@ - 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/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 4b0886893ac2..78f559d28097 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.web; -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.DateTimeUtil; import ru.javawebinar.topjava.web.meal.MealRestController; @@ -20,25 +20,15 @@ public class MealServlet extends HttpServlet { - 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.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); - springContext.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); - springContext.refresh(); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); mealController = springContext.getBean(MealRestController.class); } - @Override - public void destroy() { - springContext.close(); - super.destroy(); - } - @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 69aa1d958db7..3d26eb94b2ac 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; @@ -14,6 +18,15 @@ public class UserServlet extends HttpServlet { private static final Logger LOG = getLogger(UserServlet.class); + private AdminRestController adminController; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + adminController = springContext.getBean(AdminRestController.class); + } + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int userId = Integer.valueOf(request.getParameter("userId")); AuthorizedUser.setId(userId); @@ -22,7 +35,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) @Override 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/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 686b79f85e6f..4306595b08bd 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -5,6 +5,22 @@ version="3.1"> Topjava +k + spring.profiles.default + postgres,datajpa + + + + contextConfigLocation + + classpath:spring/spring-app.xml + classpath:spring/spring-db.xml + + + + + org.springframework.web.context.ContextLoaderListener + userServlet ru.javawebinar.topjava.web.UserServlet From 96f37f6108de4cf6bdd1fc03624608e6a640b743 Mon Sep 17 00:00:00 2001 From: Drag Date: Fri, 28 Apr 2017 18:37:10 +0300 Subject: [PATCH 75/91] 6 12 spring web --- src/main/webapp/WEB-INF/web.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 4306595b08bd..9e0d0ade559d 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -5,7 +5,7 @@ version="3.1"> Topjava -k + spring.profiles.default postgres,datajpa From bc4bce98d684ead652a67a3c9b4b94cf6c336748 Mon Sep 17 00:00:00 2001 From: Drag Date: Wed, 3 May 2017 01:36:58 +0300 Subject: [PATCH 76/91] 6 13 jsp jstl i18n --- src/main/resources/messages/app.properties | 15 +++++++ src/main/resources/messages/app_ru.properties | 15 +++++++ src/main/webapp/css/style.css | 8 ++++ src/main/webapp/fragments/bodyHeader.jsp | 5 +++ src/main/webapp/fragments/footer.jsp | 5 +++ src/main/webapp/fragments/headTag.jsp | 10 +++++ src/main/webapp/index.html | 19 --------- src/main/webapp/index.jsp | 26 ++++++++++++ src/main/webapp/meals.jsp | 5 +-- src/main/webapp/users.jsp | 41 ++++++++++++++++--- 10 files changed, 121 insertions(+), 28 deletions(-) create mode 100644 src/main/resources/messages/app.properties create mode 100644 src/main/resources/messages/app_ru.properties create mode 100644 src/main/webapp/fragments/bodyHeader.jsp create mode 100644 src/main/webapp/fragments/footer.jsp create mode 100644 src/main/webapp/fragments/headTag.jsp delete mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/index.jsp diff --git a/src/main/resources/messages/app.properties b/src/main/resources/messages/app.properties new file mode 100644 index 000000000000..30b56a3549da --- /dev/null +++ b/src/main/resources/messages/app.properties @@ -0,0 +1,15 @@ +app.title=Calories management +app.home=Home +app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application +app.login=Login as + +users.title=Users +users.name=Name +users.email=Email +users.roles=Roles +users.active=Active +users.registered=Registered + +meals.title=Meals + +common.select=Select \ No newline at end of file diff --git a/src/main/resources/messages/app_ru.properties b/src/main/resources/messages/app_ru.properties new file mode 100644 index 000000000000..1c6df693d5bc --- /dev/null +++ b/src/main/resources/messages/app_ru.properties @@ -0,0 +1,15 @@ +app.title=\u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 +app.home=\u0413\u043B\u0430\u0432\u043D\u0430\u044F +app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0443 Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery +app.login=\u0417\u0430\u0439\u0442\u0438 \u043A\u0430\u043A + +users.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 +users.name=\u0418\u043C\u044F +users.email=\u041F\u043E\u0447\u0442\u0430 +users.roles=\u0420\u043E\u043B\u0438 +users.active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439 +users.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D + +meals.title=\u041C\u043E\u044F \u0435\u0434\u0430 + +common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \ No newline at end of file diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css index 0c9fc667371c..36305de387ee 100644 --- a/src/main/webapp/css/style.css +++ b/src/main/webapp/css/style.css @@ -22,3 +22,11 @@ dd { .exceeded { color: red; } + +header, footer { + background: none repeat scroll 0 0 #A6C9E2; + color: #2E6E9E; + font-size: 20px; + padding: 5px 20px; + margin: 6px 0; +} \ No newline at end of file diff --git a/src/main/webapp/fragments/bodyHeader.jsp b/src/main/webapp/fragments/bodyHeader.jsp new file mode 100644 index 000000000000..16432ecaa755 --- /dev/null +++ b/src/main/webapp/fragments/bodyHeader.jsp @@ -0,0 +1,5 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + +

     | 
    \ No newline at end of file diff --git a/src/main/webapp/fragments/footer.jsp b/src/main/webapp/fragments/footer.jsp new file mode 100644 index 000000000000..c59bff2e54bf --- /dev/null +++ b/src/main/webapp/fragments/footer.jsp @@ -0,0 +1,5 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + +
    +
    \ No newline at end of file diff --git a/src/main/webapp/fragments/headTag.jsp b/src/main/webapp/fragments/headTag.jsp new file mode 100644 index 000000000000..c1532783af5e --- /dev/null +++ b/src/main/webapp/fragments/headTag.jsp @@ -0,0 +1,10 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> + + + + + <fmt:message key="app.title"/> + + \ No newline at end of file diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html deleted file mode 100644 index a9a4e7837e24..000000000000 --- a/src/main/webapp/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Java Enterprise (Topjava) - - -

    Проект Java Enterprise (Topjava)

    -
    -
    - Meal list of  - - - - - diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 000000000000..a6cccf49e336 --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,26 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + +
    +
    + : + + +
      +
    • +
    • +
    +
    + + + \ No newline at end of file diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 2a74fc4503d4..0846ec620c91 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -5,13 +5,12 @@ - Meal list + Calories management
    -

    Home

    -

    Meal list

    +

    Meals

    From Date:
    diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp index 2efb8cef01ec..b3df4ac31b0a 100644 --- a/src/main/webapp/users.jsp +++ b/src/main/webapp/users.jsp @@ -1,11 +1,40 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + - - - User list - + -

    Home

    -

    User list

    + + +
    +

    + +

    + + + + + + + + + + + + + + + + + + + +
    ${user.email}${user.roles}<%=user.isEnabled()%> +
    +
    + From 73f66e4729549cc4ecf7c4721a065412ec856208 Mon Sep 17 00:00:00 2001 From: Drag Date: Wed, 3 May 2017 01:42:34 +0300 Subject: [PATCH 77/91] 6 14 tomcat pool jndi cargo --- pom.xml | 64 +++++++++++++++++++++++++ src/main/resources/db/tomcat.properties | 7 +++ src/main/resources/spring/spring-db.xml | 9 +++- src/main/resources/tomcat/context.xml | 57 ++++++++++++++++++++++ 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/tomcat.properties create mode 100644 src/main/resources/tomcat/context.xml diff --git a/pom.xml b/pom.xml index c6131a5aace0..299dd831d753 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,70 @@ -Dfile.encoding=UTF-8 + + + + org.codehaus.cargo + cargo-maven2-plugin + 1.6.3 + + + 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} + + + + + + + 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 1f4794fdb200..8ed64422471c 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}"/> + + + + + diff --git a/src/main/resources/tomcat/context.xml b/src/main/resources/tomcat/context.xml new file mode 100644 index 000000000000..9311d5904aea --- /dev/null +++ b/src/main/resources/tomcat/context.xml @@ -0,0 +1,57 @@ + + + + + + + + WEB-INF/web.xml + ${catalina.base}/conf/web.xml + + + + + + + + + From 1cd233b91c85b53b1ec823d75f23b9452be3c8d0 Mon Sep 17 00:00:00 2001 From: Drag Date: Wed, 3 May 2017 14:04:37 +0300 Subject: [PATCH 78/91] 6 15 spring webmvc --- pom.xml | 2 +- .../topjava/web/RootController.java | 35 +++++++++++++++++++ src/main/resources/logback.xml | 1 + src/main/resources/spring/spring-app.xml | 3 -- src/main/resources/spring/spring-mvc.xml | 18 ++++++++++ .../jsp}/fragments/bodyHeader.jsp | 2 +- .../{ => WEB-INF/jsp}/fragments/footer.jsp | 0 .../{ => WEB-INF/jsp}/fragments/headTag.jsp | 2 +- src/main/webapp/{ => WEB-INF/jsp}/index.jsp | 0 src/main/webapp/{ => WEB-INF/jsp}/meal.jsp | 0 src/main/webapp/{ => WEB-INF/jsp}/meals.jsp | 0 src/main/webapp/{ => WEB-INF/jsp}/users.jsp | 0 src/main/webapp/WEB-INF/web.xml | 17 +++++++++ src/main/webapp/{ => resources}/css/style.css | 0 14 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/RootController.java create mode 100644 src/main/resources/spring/spring-mvc.xml rename src/main/webapp/{ => WEB-INF/jsp}/fragments/bodyHeader.jsp (50%) rename src/main/webapp/{ => WEB-INF/jsp}/fragments/footer.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/fragments/headTag.jsp (85%) rename src/main/webapp/{ => WEB-INF/jsp}/index.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/meal.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/meals.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/users.jsp (100%) rename src/main/webapp/{ => resources}/css/style.css (100%) diff --git a/pom.xml b/pom.xml index 299dd831d753..15567c968652 100644 --- a/pom.xml +++ b/pom.xml @@ -174,7 +174,7 @@ org.springframework - spring-web + spring-webmvc commons-logging 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..b3a82393b7a4 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -0,0 +1,35 @@ +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; + +@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/logback.xml b/src/main/resources/logback.xml index e9b900b26669..8616ff8e73fc 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -21,6 +21,7 @@ + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index fbf54e789877..c3e7587cfdea 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -16,7 +16,4 @@ - - - \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml new file mode 100644 index 000000000000..7c71d5af5939 --- /dev/null +++ b/src/main/resources/spring/spring-mvc.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp similarity index 50% rename from src/main/webapp/fragments/bodyHeader.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 16432ecaa755..903b0ef09fdd 100644 --- a/src/main/webapp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -2,4 +2,4 @@ <%@ 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/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp similarity index 100% rename from src/main/webapp/fragments/footer.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/footer.jsp diff --git a/src/main/webapp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp similarity index 85% rename from src/main/webapp/fragments/headTag.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index c1532783af5e..da40d461d76a 100644 --- a/src/main/webapp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -6,5 +6,5 @@ <fmt:message key="app.title"/> - + \ No newline at end of file diff --git a/src/main/webapp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp similarity index 100% rename from src/main/webapp/index.jsp rename to src/main/webapp/WEB-INF/jsp/index.jsp diff --git a/src/main/webapp/meal.jsp b/src/main/webapp/WEB-INF/jsp/meal.jsp similarity index 100% rename from src/main/webapp/meal.jsp rename to src/main/webapp/WEB-INF/jsp/meal.jsp diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp similarity index 100% rename from src/main/webapp/meals.jsp rename to src/main/webapp/WEB-INF/jsp/meals.jsp diff --git a/src/main/webapp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp similarity index 100% rename from src/main/webapp/users.jsp rename to src/main/webapp/WEB-INF/jsp/users.jsp diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 9e0d0ade559d..46061022703a 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -18,9 +18,25 @@ + org.springframework.web.context.ContextLoaderListener + + mvc-dispatcher + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + classpath:spring/spring-mvc.xml + + 1 + + + mvc-dispatcher + / + + + diff --git a/src/main/webapp/css/style.css b/src/main/webapp/resources/css/style.css similarity index 100% rename from src/main/webapp/css/style.css rename to src/main/webapp/resources/css/style.css From 086934ca146e64fc664a52d1c3d530f031da18f4 Mon Sep 17 00:00:00 2001 From: Drag Date: Wed, 3 May 2017 15:05:10 +0300 Subject: [PATCH 79/91] 6 16 spring i18n --- .../messages/app.properties | 0 config/messages/app_ru.properties | 15 +++++++++++++++ src/main/resources/messages/app_ru.properties | 15 --------------- src/main/resources/spring/spring-mvc.xml | 19 +++++++++++++++++++ .../WEB-INF/jsp/fragments/bodyHeader.jsp | 5 ++--- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 5 ++--- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 5 ++--- src/main/webapp/WEB-INF/jsp/index.jsp | 12 +++++------- src/main/webapp/WEB-INF/jsp/users.jsp | 15 +++++++-------- 9 files changed, 52 insertions(+), 39 deletions(-) rename {src/main/resources => config}/messages/app.properties (100%) create mode 100644 config/messages/app_ru.properties delete mode 100644 src/main/resources/messages/app_ru.properties diff --git a/src/main/resources/messages/app.properties b/config/messages/app.properties similarity index 100% rename from src/main/resources/messages/app.properties rename to config/messages/app.properties diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties new file mode 100644 index 000000000000..dc59f93fe853 --- /dev/null +++ b/config/messages/app_ru.properties @@ -0,0 +1,15 @@ +app.title=Подсчет калорий +app.home=Главная +app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery +app.login=Зайти как + +users.title=Пользователи +users.name=Имя +users.email=Почта +users.roles=Роли +users.active=Активный +users.registered=Зарегистрирован + +meals.title=Моя еда + +common.select=Выбрать \ No newline at end of file diff --git a/src/main/resources/messages/app_ru.properties b/src/main/resources/messages/app_ru.properties deleted file mode 100644 index 1c6df693d5bc..000000000000 --- a/src/main/resources/messages/app_ru.properties +++ /dev/null @@ -1,15 +0,0 @@ -app.title=\u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 -app.home=\u0413\u043B\u0430\u0432\u043D\u0430\u044F -app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0443 Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery -app.login=\u0417\u0430\u0439\u0442\u0438 \u043A\u0430\u043A - -users.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 -users.name=\u0418\u043C\u044F -users.email=\u041F\u043E\u0447\u0442\u0430 -users.roles=\u0420\u043E\u043B\u0438 -users.active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439 -users.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D - -meals.title=\u041C\u043E\u044F \u0435\u0434\u0430 - -common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \ 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 7c71d5af5939..c2c604f4d815 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -15,4 +15,23 @@ + + + + + + \ 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 903b0ef09fdd..fcafd0e813f2 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,5 +1,4 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> - -
     | 
    \ 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 c59bff2e54bf..0935c441a36b 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,5 +1,4 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
    -
    \ 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 da40d461d76a..6d77694e3406 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -1,10 +1,9 @@ <%@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" %> - - <fmt:message key="app.title"/> + <spring:message code="app.title"/> \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp index a6cccf49e336..4a1d77633c16 100644 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -1,8 +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" %> @@ -10,15 +8,15 @@
    - : - +
      -
    • -
    • +
    • +
    diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index b3df4ac31b0a..8623615f1c05 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -1,8 +1,7 @@ <%@ 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" %> @@ -10,16 +9,16 @@
    -

    +

    - - - - - + + + + + From fcf83a4b3eb5d0f23ec2bbb2324edfd3ee11e603 Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 11 May 2017 13:16:14 +0300 Subject: [PATCH 80/91] 7 01 HW6 fix tests --- .../ru/javawebinar/topjava/SpringMain.java | 2 +- .../service/AbstractJpaUserServiceTest.java | 32 +++++++++++++++++++ .../service/AbstractMealServiceTest.java | 2 ++ .../topjava/service/AbstractServiceTest.java | 11 +++++++ .../service/AbstractUserServiceTest.java | 15 --------- .../datajpa/DataJpaUserServiceTest.java | 4 +-- .../service/jpa/JpaUserServiceTest.java | 4 +-- .../web/InMemoryAdminRestControllerTest.java | 4 ++- src/test/resources/spring/mock.xml | 1 + 9 files changed, 54 insertions(+), 21 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 a3d619991c8a..070680d64fce 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -18,7 +18,7 @@ public static void main(String[] args) { // java 7 Automatic resource management try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) { appCtx.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); - appCtx.load("spring/spring-app.xml", "spring/spring-db.xml"); + appCtx.load("spring/spring-app.xml", "spring/mock.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..9fcfb7f5ea7d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java @@ -0,0 +1,32 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.JpaUtil; + +import javax.validation.ConstraintViolationException; +import java.util.Collections; + +abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest { + + @Autowired + private JpaUtil jpaUtil; + + @Before + public void setUp() throws Exception { + super.setUp(); + jpaUtil.clear2ndLevelHibernateCache(); + } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.save(new User(null, " ", "invalid@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", "password", 9, true, Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", "password", 10001, true, Collections.emptySet())), ConstraintViolationException.class); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index d0c7b5f1f9ab..421fe2324a5b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.service; +import org.junit.Assume; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.Meal; @@ -80,6 +81,7 @@ public void testGetBetween() throws Exception { @Test public void testValidation() throws Exception { + Assume.assumeTrue(isJpaBased()); validateRootCause(() -> service.save(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), " ", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.save(new Meal(null, null, "Description", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.save(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 9), USER_ID), ConstraintViolationException.class); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index 795540de9f13..beb58d2c8817 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -10,12 +10,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; 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.ActiveDbProfileResolver; +import ru.javawebinar.topjava.Profiles; import java.util.concurrent.TimeUnit; @@ -36,6 +39,9 @@ abstract public class AbstractServiceTest { @Rule public ExpectedException thrown = ExpectedException.none(); + @Autowired + public Environment env; + @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() { @@ -52,6 +58,11 @@ protected void finished(long nanos, Description description) { SLF4JBridgeHandler.install(); } + public boolean isJpaBased() { +// return Arrays.stream(env.getActiveProfiles()).noneMatch(Profiles.JDBC::equals); + return env.acceptsProfiles(Profiles.JPA, Profiles.DATAJPA); + } + @AfterClass public static void printResult() { LOG.info("\n---------------------------------" + diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 7f3453d035b7..ece76f343e0e 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -6,10 +6,8 @@ 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 javax.validation.ConstraintViolationException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -21,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 @@ -85,13 +79,4 @@ public void testUpdate() throws Exception { service.update(updated); MATCHER.assertEquals(updated, service.get(USER_ID)); } - - @Test - public void testValidation() throws Exception { - validateRootCause(() -> service.save(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.save(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.save(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.save(new User(null, "User", "mail@yandex.ru", "password", 9, true, Collections.emptySet())), ConstraintViolationException.class); - validateRootCause(() -> service.save(new User(null, "User", "mail@yandex.ru", "password", 10001, true, Collections.emptySet())), ConstraintViolationException.class); - } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index 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/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java index f7006200f781..6e5960f61f63 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -28,7 +28,9 @@ public static void beforeClass() { @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 diff --git a/src/test/resources/spring/mock.xml b/src/test/resources/spring/mock.xml index c7c9f62cc5d8..66728caf0dfd 100644 --- a/src/test/resources/spring/mock.xml +++ b/src/test/resources/spring/mock.xml @@ -4,4 +4,5 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + \ No newline at end of file From 93e251ae26da8fa6e291a57b1a6b120d012b4dde Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 11 May 2017 13:56:21 +0300 Subject: [PATCH 81/91] 7 02 HW6 meals --- config/messages/app.properties | 16 +++- config/messages/app_ru.properties | 16 +++- .../javawebinar/topjava/web/MealServlet.java | 89 ------------------- .../topjava/web/RootController.java | 16 +++- .../javawebinar/topjava/web/UserServlet.java | 42 --------- .../web/meal/AbstractMealController.java | 71 +++++++++++++++ .../topjava/web/meal/JspMealController.java | 69 ++++++++++++++ .../topjava/web/meal/MealRestController.java | 72 +-------------- src/main/webapp/WEB-INF/jsp/meal.jsp | 25 +++--- src/main/webapp/WEB-INF/jsp/meals.jsp | 38 ++++---- src/main/webapp/WEB-INF/web.xml | 22 ----- 11 files changed, 216 insertions(+), 260 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 create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java diff --git a/config/messages/app.properties b/config/messages/app.properties index 30b56a3549da..b0c1bea96528 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -11,5 +11,19 @@ users.active=Active users.registered=Registered meals.title=Meals +meals.edit=Edit meal +meals.add=Add meal +meals.filter=Filter +meals.startDate=From date +meals.endDate=To date +meals.startTime=From time +meals.endTime=To time +meals.description=Description +meals.dateTime=DateTime +meals.calories=Calories -common.select=Select \ No newline at end of file +common.select=Select +common.delete=Delete +common.update=Update +common.save=Save +common.cancel=Cancel \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index dc59f93fe853..719499892981 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -11,5 +11,19 @@ users.active=Активный users.registered=Зарегистрирован meals.title=Моя еда +meals.edit=Редактировать еду +meals.add=Добавить еду +meals.filter=Отфильтровать +meals.startDate=От даты +meals.endDate=До даты +meals.startTime=От времени +meals.endTime=До времени +meals.description=Описание +meals.dateTime=Время +meals.calories=Калории -common.select=Выбрать \ No newline at end of file +common.select=Выбрать +common.delete=Удалить +common.update=Обновить +common.save=Сохранить +common.cancel=Отмена \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java deleted file mode 100644 index 78f559d28097..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ /dev/null @@ -1,89 +0,0 @@ -package ru.javawebinar.topjava.web; - -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.util.DateTimeUtil; -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; - -public class MealServlet extends HttpServlet { - - private MealRestController mealController; - - @Override - public void init(ServletConfig config) throws ServletException { - super.init(config); - WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - mealController = springContext.getBean(MealRestController.class); - } - - @Override - 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()) { - mealController.create(meal); - } else { - mealController.update(meal, getId(request)); - } - response.sendRedirect("meals"); - - } else if ("filter".equals(action)) { - LocalDate startDate = DateTimeUtil.parseLocalDate(request.getParameter("startDate")); - LocalDate endDate = DateTimeUtil.parseLocalDate(request.getParameter("endDate")); - LocalTime startTime = DateTimeUtil.parseLocalTime(request.getParameter("startTime")); - LocalTime endTime = DateTimeUtil.parseLocalTime(request.getParameter("endTime")); - request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); - request.getRequestDispatcher("/meals.jsp").forward(request, response); - } - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String action = request.getParameter("action"); - - switch (action == null ? "all" : action) { - case "delete": - int id = getId(request); - mealController.delete(id); - response.sendRedirect("meals"); - break; - case "create": - case "update": - final Meal meal = "create".equals(action) ? - new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : - mealController.get(getId(request)); - request.setAttribute("meal", meal); - request.getRequestDispatcher("/meal.jsp").forward(request, response); - break; - case "all": - default: - request.setAttribute("meals", mealController.getAll()); - request.getRequestDispatcher("/meals.jsp").forward(request, response); - break; - } - } - - private int getId(HttpServletRequest request) { - String paramId = Objects.requireNonNull(request.getParameter("id")); - return Integer.valueOf(paramId); - } -} \ 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 b3a82393b7a4..016aa3d60a3e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -6,14 +6,19 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import ru.javawebinar.topjava.AuthorizedUser; +import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.http.HttpServletRequest; @Controller public class RootController { @Autowired - private UserService service; + private UserService userService; + + @Autowired + private MealService mealService; @RequestMapping(value = "/", method = RequestMethod.GET) public String root() { @@ -22,7 +27,7 @@ public String root() { @RequestMapping(value = "/users", method = RequestMethod.GET) public String users(Model model) { - model.addAttribute("users", service.getAll()); + model.addAttribute("users", userService.getAll()); return "users"; } @@ -32,4 +37,11 @@ public String setUser(HttpServletRequest request) { AuthorizedUser.setId(userId); return "redirect:meals"; } + + @RequestMapping(value = "/meals", method = RequestMethod.GET) + public String meals(Model model) { + model.addAttribute("meals", + MealsUtil.getWithExceeded(mealService.getAll(AuthorizedUser.id()), AuthorizedUser.getCaloriesPerDay())); + return "meals"; + } } 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 3d26eb94b2ac..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.javawebinar.topjava.web; - -import org.slf4j.Logger; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import ru.javawebinar.topjava.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; - -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"); - } - - @Override - 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..ce8649307666 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java @@ -0,0 +1,71 @@ +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.DateTimeUtil; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +public abstract class AbstractMealController { + private final Logger LOG = LoggerFactory.getLogger(getClass()); + + @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 Meal create(Meal meal) { + int userId = AuthorizedUser.id(); + checkNew(meal); + LOG.info("create {} for User {}", meal, userId); + return service.save(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = AuthorizedUser.id(); + checkIdConsistent(meal, id); + LOG.info("update {} for User {}", meal, userId); + service.update(meal, userId); + } + + public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { + int userId = AuthorizedUser.id(); + LOG.info("getBetween dates({} - {}) time({} - {}) for User {}", startDate, endDate, startTime, endTime, userId); + + return MealsUtil.getFilteredWithExceeded( + service.getBetweenDates( + startDate != null ? startDate : DateTimeUtil.MIN_DATE, + endDate != null ? endDate : DateTimeUtil.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/JspMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java new file mode 100644 index 000000000000..1f752dc0a163 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -0,0 +1,69 @@ +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 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; + +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; + +@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) { + Meal meal = new Meal(LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); + + if (request.getParameter("id").isEmpty()) { + super.create(meal); + } else { + super.update(meal, getId(request)); + } + return "redirect:meals"; + } + + @RequestMapping(value = "/filter", method = RequestMethod.POST) + public String getBetween(HttpServletRequest request, Model model) { + LocalDate startDate = parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = parseLocalTime(request.getParameter("endTime")); + model.addAttribute("meals", super.getBetween(startDate, startTime, endDate, endTime)); + return "meals"; + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.valueOf(paramId); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index a8208a9c0917..c3daf68535d7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,77 +1,7 @@ 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.DateTimeUtil; -import ru.javawebinar.topjava.util.MealsUtil; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; - -import static ru.javawebinar.topjava.util.ValidationUtil.checkIdConsistent; -import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; @Controller -public class MealRestController { - private static final Logger LOG = LoggerFactory.getLogger(MealRestController.class); - - private final MealService service; - - @Autowired - public MealRestController(MealService service) { - this.service = 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 Meal create(Meal meal) { - int userId = AuthorizedUser.id(); - checkNew(meal); - LOG.info("create {} for User {}", meal, userId); - return service.save(meal, userId); - } - - public void update(Meal meal, int id) { - int userId = AuthorizedUser.id(); - checkIdConsistent(meal, id); - LOG.info("update {} for User {}", meal, userId); - service.update(meal, userId); - } - - public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { - int userId = AuthorizedUser.id(); - LOG.info("getBetween dates({} - {}) time({} - {}) for User {}", startDate, endDate, startTime, endTime, userId); - - return MealsUtil.getFilteredWithExceeded( - service.getBetweenDates( - startDate != null ? startDate : DateTimeUtil.MIN_DATE, - endDate != null ? endDate : DateTimeUtil.MAX_DATE, userId), - startTime != null ? startTime : LocalTime.MIN, - endTime != null ? endTime : LocalTime.MAX, - AuthorizedUser.getCaloriesPerDay() - ); - } +public class MealRestController extends AbstractMealController { } \ 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 259d80f3ee9d..73228e80925e 100644 --- a/src/main/webapp/WEB-INF/jsp/meal.jsp +++ b/src/main/webapp/WEB-INF/jsp/meal.jsp @@ -1,35 +1,34 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> - - - Meal - - + + +
    -

    Home

    -

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

    -
    +

    +
    -
    DateTime:
    +
    :
    -
    Description:
    +
    :
    -
    Calories:
    +
    :
    - - + +
    + diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 0846ec620c91..1847501ef1bb 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -1,44 +1,43 @@ <%@ 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="fn" uri="http://topjava.javawebinar.ru/functions" %> - - - Calories management - - + + +
    -

    Meals

    -
    +

    + +
    -
    From Date:
    +
    :
    -
    To Date:
    +
    :
    -
    From Time:
    +
    :
    -
    To Time:
    +
    :
    - +
    - Add Meal +
    - - - + + + @@ -53,11 +52,12 @@ - - + +
    DateDescriptionCalories
    ${meal.description} ${meal.calories}UpdateDelete
    + \ 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..a901fde09b6b 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -36,26 +36,4 @@ / - - From 7f6e331df3c5096fa001d8d23575aeaee85c184b Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 11 May 2017 14:18:10 +0300 Subject: [PATCH 82/91] 7 03 HW6 fix relative url utf8 --- .../topjava/web/meal/JspMealController.java | 4 ++-- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 1 + src/main/webapp/WEB-INF/web.xml | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 2 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 1f752dc0a163..7b12fc8c4459 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -23,7 +23,7 @@ public class JspMealController extends AbstractMealController { @RequestMapping(value = "/delete", method = RequestMethod.GET) public String delete(HttpServletRequest request) { super.delete(getId(request)); - return "redirect:meals"; + return "redirect:/meals"; } @RequestMapping(value = "/update", method = RequestMethod.GET) @@ -49,7 +49,7 @@ public String updateOrCreate(HttpServletRequest request) { } else { super.update(meal, getId(request)); } - return "redirect:meals"; + return "redirect:/meals"; } @RequestMapping(value = "/filter", method = RequestMethod.POST) diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 6d77694e3406..0c77f10859f8 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -5,5 +5,6 @@ <spring:message code="app.title"/> + \ 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 a901fde09b6b..6e1280c56d07 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -36,4 +36,21 @@ / + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + + From fd2af68c236e233ada0836c15cfbff81edc53f4d Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 11 May 2017 14:23:37 +0300 Subject: [PATCH 83/91] 7 04 HW6 optional add role --- .../ru/javawebinar/topjava/model/User.java | 21 +++++++++++-------- src/main/resources/db/populateDB.sql | 3 ++- .../ru/javawebinar/topjava/UserTestData.java | 4 ++-- .../service/AbstractJpaUserServiceTest.java | 5 +++-- .../service/AbstractUserServiceTest.java | 12 ++++++----- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index f9c88759f211..0472e37f87b3 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -7,19 +7,17 @@ import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.Range; +import org.springframework.util.CollectionUtils; import javax.persistence.*; -import java.util.Date; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; +import java.util.*; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @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.BY_EMAIL, query = "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email=?1"), @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u ORDER BY u.name, u.email"), }) @Entity @@ -70,20 +68,21 @@ public User() { } public User(User u) { - this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRoles()); + this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRegistered(), u.getRoles()); } public User(Integer id, String name, String email, String password, Role role, Role... roles) { - this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, new Date(), EnumSet.of(role, roles)); } - public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Date registered, Collection roles) { super(id, name); this.email = email; this.password = password; this.caloriesPerDay = caloriesPerDay; this.enabled = enabled; - this.roles = roles; + this.registered = registered; + setRoles(roles); } public String getEmail() { @@ -134,6 +133,10 @@ public List getMeals() { return meals; } + public void setRoles(Collection roles) { + this.roles = CollectionUtils.isEmpty(roles) ? Collections.emptySet() : EnumSet.copyOf(roles); + } + @Override public String toString() { return "User{" + 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 e1151110c95e..f2d6be7263f9 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -13,7 +13,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 || @@ -23,7 +23,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()) ) ); } diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java index 9fcfb7f5ea7d..22f5e92733a7 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java @@ -9,6 +9,7 @@ import javax.validation.ConstraintViolationException; import java.util.Collections; +import java.util.Date; abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest { @@ -26,7 +27,7 @@ public void testValidation() throws Exception { validateRootCause(() -> service.save(new User(null, " ", "invalid@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); validateRootCause(() -> service.save(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", "password", 9, true, Collections.emptySet())), ConstraintViolationException.class); - validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", "password", 10001, true, Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", "password", 9, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.save(new User(null, "User", "invalid@yandex.ru", "password", 10001, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); } } \ 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 ece76f343e0e..c4f7b3404ec5 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import static ru.javawebinar.topjava.UserTestData.*; @@ -26,7 +27,7 @@ public void setUp() throws Exception { @Test public void testSave() throws Exception { - User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); + User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.ROLE_USER)); User created = service.save(newUser); newUser.setId(created.getId()); MATCHER.assertCollectionEquals(Arrays.asList(ADMIN, newUser, USER), service.getAll()); @@ -50,8 +51,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) @@ -61,8 +62,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 @@ -76,6 +77,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 35454e3a484a35e83f5f3f43e16309fbdb4dc1ae Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 11 May 2017 14:52:54 +0300 Subject: [PATCH 84/91] 7 05 HW6 optional jdbc --- .../jdbc/JdbcMealRepositoryImpl.java | 4 ++ .../jdbc/JdbcUserRepositoryImpl.java | 53 +++++++++++++++++-- src/main/resources/spring/spring-db.xml | 9 +++- src/test/resources/logback-test.xml | 2 +- 4 files changed, 61 insertions(+), 7 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 fa514658576b..72730a0fe8dc 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -9,6 +9,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -17,6 +18,7 @@ import java.util.List; @Repository +@Transactional(readOnly = true) public class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); @@ -38,6 +40,7 @@ public JdbcMealRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, } @Override + @Transactional public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) @@ -62,6 +65,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 03061174f7b9..580f83253bff 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -7,14 +7,19 @@ import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import javax.sql.DataSource; -import java.util.List; +import java.util.*; @Repository +@Transactional(readOnly = true) public class JdbcUserRepositoryImpl implements UserRepository { private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); @@ -36,13 +41,20 @@ public JdbcUserRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, } @Override + @Transactional public User save(User user) { BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); if (user.isNew()) { Number newKey = insertUser.executeAndReturnKey(parameterSource); user.setId(newKey.intValue()); + insertRoles(user); } else { + // Simplest implementation. + // More complicated : get user roles from DB and compare them with user.roles (assume that roles are changed rarely). + // If roles are changed, calculate difference in java and delete/insert them. + 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", parameterSource); @@ -51,6 +63,7 @@ public User save(User user) { } @Override + @Transactional public boolean delete(int id) { return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0; } @@ -58,18 +71,50 @@ public boolean delete(int id) { @Override public User get(int id) { List users = jdbcTemplate.query("SELECT * FROM users WHERE id=?", ROW_MAPPER, id); - return DataAccessUtils.singleResult(users); + return setRoles(DataAccessUtils.singleResult(users)); } @Override public User getByEmail(String email) { // return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); 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); + SqlRowSet rowSet = jdbcTemplate.queryForRowSet("SELECT * FROM user_roles"); + Map> map = new HashMap<>(); + while (rowSet.next()) { + Set roles = map.computeIfAbsent(rowSet.getInt("user_id"), userId -> EnumSet.noneOf(Role.class)); + roles.add(Role.valueOf(rowSet.getString("role"))); + } + List users = jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + users.forEach(u -> u.setRoles(map.get(u.getId()))); + return users; + } + + private void insertRoles(User u) { + Set roles = u.getRoles(); + if (!CollectionUtils.isEmpty(roles)) { + jdbcTemplate.batchUpdate("INSERT INTO user_roles (user_id, role) VALUES (?, ?)", roles, roles.size(), + (ps, role) -> { + ps.setInt(1, u.getId()); + ps.setString(2, role.name()); + }); + } + } + + 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=?", + (rs, rowNum) -> Role.valueOf(rs.getString("role")), 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 8ed64422471c..8cd84b0a37fd 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 @@ + + + + @@ -92,8 +99,6 @@
    - - diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index afe896f21924..63c62b15dfe4 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -12,7 +12,7 @@ - + From 88ba9d6caae0697048ccc29414a7682c2322f66b Mon Sep 17 00:00:00 2001 From: Drag Date: Thu, 11 May 2017 16:29:12 +0300 Subject: [PATCH 85/91] 7 06 controller test --- pom.xml | 14 +++++ .../topjava/AllActiveProfileResolver.java | 12 ++++ .../topjava/web/AbstractControllerTest.java | 58 +++++++++++++++++++ .../topjava/web/RootControllerTest.java | 29 ++++++++++ 4 files changed, 113 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java diff --git a/pom.xml b/pom.xml index 15567c968652..c089e0571feb 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 42.0.0 4.12 + 1.3 5.2.9.Final @@ -239,7 +240,20 @@ junit ${junit.version} test + + + hamcrest-core + org.hamcrest + + + + org.hamcrest + hamcrest-all + ${hamcrest.version} + test + + org.springframework spring-test diff --git a/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java b/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java new file mode 100644 index 000000000000..4d1d1e9e3789 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava; + +import org.springframework.test.context.ActiveProfilesResolver; + +//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver +public class AllActiveProfileResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class aClass) { + return new String[]{Profiles.REPOSITORY_IMPLEMENTATION, Profiles.getActiveDbProfile()}; + } +} \ No newline at end of file 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..c209f7be497b --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -0,0 +1,58 @@ +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.AllActiveProfileResolver; +import ru.javawebinar.topjava.service.UserService; + +import javax.annotation.PostConstruct; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-mvc.xml", + "classpath:spring/spring-db.xml" +}) +@WebAppConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@Transactional +@ActiveProfiles(resolver = AllActiveProfileResolver.class) +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 + private 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/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java new file mode 100644 index 000000000000..dce3c4e3e3b2 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Test; + +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; + +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 ea5abf9ad6e6867eb10f9fb461313dc59a5ae935 Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 13 May 2017 17:29:39 +0300 Subject: [PATCH 86/91] 7 07 rest controller --- .../topjava/web/RootController.java | 12 +++--- .../topjava/web/meal/JspMealController.java | 13 ++++--- .../topjava/web/user/AdminRestController.java | 38 ++++++++++++++----- .../web/user/ProfileRestController.java | 12 ++++-- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 016aa3d60a3e..6766757755f9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -3,8 +3,8 @@ 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 org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.service.UserService; @@ -20,25 +20,25 @@ public class RootController { @Autowired private MealService mealService; - @RequestMapping(value = "/", method = RequestMethod.GET) + @GetMapping("/") public String root() { return "index"; } - @RequestMapping(value = "/users", method = RequestMethod.GET) + @GetMapping("/users") public String users(Model model) { model.addAttribute("users", userService.getAll()); return "users"; } - @RequestMapping(value = "/users", method = RequestMethod.POST) + @PostMapping("/users") public String setUser(HttpServletRequest request) { int userId = Integer.valueOf(request.getParameter("userId")); AuthorizedUser.setId(userId); return "redirect:meals"; } - @RequestMapping(value = "/meals", method = RequestMethod.GET) + @GetMapping("/meals") public String meals(Model model) { model.addAttribute("meals", MealsUtil.getWithExceeded(mealService.getAll(AuthorizedUser.id()), AuthorizedUser.getCaloriesPerDay())); 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 7b12fc8c4459..24debce3b0b7 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 javax.servlet.http.HttpServletRequest; @@ -20,25 +21,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) { Meal meal = new Meal(LocalDateTime.parse(request.getParameter("dateTime")), request.getParameter("description"), @@ -52,7 +53,7 @@ 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 = parseLocalDate(request.getParameter("startDate")); LocalDate endDate = parseLocalDate(request.getParameter("endDate")); 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 aba649e40542..a1f4be3a93ea 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,14 +1,20 @@ package ru.javawebinar.topjava.web.user; import org.springframework.beans.factory.annotation.Autowired; -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 ru.javawebinar.topjava.service.UserService; +import java.net.URI; import java.util.List; -@Controller +@RestController +@RequestMapping(AdminRestController.REST_URL) public class AdminRestController extends AbstractUserController { + static final String REST_URL = "/rest/admin/users"; @Autowired public AdminRestController(UserService service) { @@ -16,32 +22,46 @@ public AdminRestController(UserService service) { } @Override + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { return super.getAll(); } @Override - public User get(int id) { + @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + public User get(@PathVariable("id") int id) { return super.get(id); } - @Override - 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); + +// HttpHeaders httpHeaders = new HttpHeaders(); +// httpHeaders.setLocation(uriOfNewResource); + + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(REST_URL + "/{id}") + .buildAndExpand(created.getId()).toUri(); + + return ResponseEntity.created(uriOfNewResource).body(created); } @Override - public void delete(int id) { + @DeleteMapping(value = "/{id}") + public void delete(@PathVariable("id") int id) { super.delete(id); } @Override - 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); } @Override - public User getByMail(String email) { + @GetMapping(value = "/by", produces = MediaType.APPLICATION_JSON_VALUE) + public User getByMail(@RequestParam("email") String email) { return super.getByMail(email); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index 528327938972..945bdba74a51 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,28 +1,34 @@ package ru.javawebinar.topjava.web.user; import org.springframework.beans.factory.annotation.Autowired; -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; import ru.javawebinar.topjava.service.UserService; -@Controller +@RestController +@RequestMapping(ProfileRestController.REST_URL) public class ProfileRestController extends AbstractUserController { + static final String REST_URL = "/rest/profile"; @Autowired public ProfileRestController(UserService service) { super(service); } + @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 1fc899d6897b68ab40aab8ea600e2905349899c6 Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 13 May 2017 17:33:45 +0300 Subject: [PATCH 87/91] 7 08 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 c089e0571feb..87b9a79c7949 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 8.5.5 4.3.7.RELEASE 1.11.1.RELEASE + 2.8.8.1 1.2.2 @@ -234,6 +235,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 ecb6e0957f94..897f6e1af2ea 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; + /** * Do not manipulate new (transient) entries in HashSet/HashMap without overriding hashCode * http://stackoverflow.com/questions/5031614 @@ -13,6 +17,7 @@ // 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 0472e37f87b3..334cf0fddf98 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -8,6 +8,7 @@ import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; +import com.fasterxml.jackson.annotation.JsonIgnore; import javax.persistence.*; import java.util.*; @@ -62,6 +63,7 @@ public class User extends NamedEntity { @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")//, cascade = CascadeType.REMOVE, orphanRemoval = true) @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..d7c975872a7d --- /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 b34e91eedd4e07ce2798e3a2ea04a5a277e7dc81 Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 13 May 2017 18:17:25 +0300 Subject: [PATCH 88/91] 7 09 jackson hibernate --- pom.xml | 7 +++- .../javawebinar/topjava/model/BaseEntity.java | 6 +-- .../ru/javawebinar/topjava/model/User.java | 3 +- .../topjava/util/DateTimeUtil.java | 8 ++++ .../topjava/web/json/JacksonObjectMapper.java | 40 +++++++++++++++++++ .../web/json/JsonLocalDateTimeConverter.java | 38 ++++++++++++++++++ .../topjava/web/json/JsonUtil.java | 37 +++++++++++++++++ src/main/resources/spring/spring-mvc.xml | 10 ++++- .../topjava/web/json/JsonUtilTest.java | 26 ++++++++++++ 9 files changed, 166 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 87b9a79c7949..563aba7cfcd8 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 8.5.5 4.3.7.RELEASE 1.11.1.RELEASE - 2.8.8.1 + 2.8.8 1.2.2 @@ -239,6 +239,11 @@ com.fasterxml.jackson.core jackson-databind + ${jackson-json.version}.1 + + + 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 897f6e1af2ea..1ed9f223440b 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; - /** * Do not manipulate new (transient) entries in HashSet/HashMap without overriding hashCode * http://stackoverflow.com/questions/5031614 @@ -17,7 +13,7 @@ // 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 334cf0fddf98..12ccb9b40472 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -8,7 +8,6 @@ import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; -import com.fasterxml.jackson.annotation.JsonIgnore; import javax.persistence.*; import java.util.*; @@ -63,7 +62,7 @@ public class User extends NamedEntity { @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")//, cascade = CascadeType.REMOVE, orphanRemoval = true) @OrderBy("dateTime DESC") - @JsonIgnore +// @JsonIgnore protected List meals; public User() { diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 11269a5ede40..4bda0877b185 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -31,4 +31,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); + } } 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..0b1168fded86 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -0,0 +1,40 @@ +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; + +/** + *

    + * 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..f0505487b63a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java @@ -0,0 +1,38 @@ +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.DateTimeUtil; + +import java.io.IOException; +import java.time.LocalDateTime; + +public class JsonLocalDateTimeConverter { + public static class UserSettingSerializer extends JsonSerializer { + @Override + public void serialize(LocalDateTime ldt, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeString(DateTimeUtil.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 DateTimeUtil.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..f230bf1d9a87 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java @@ -0,0 +1,37 @@ +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; + +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 c2c604f4d815..0477b640f5ec 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..1e2751cf92a9 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java @@ -0,0 +1,26 @@ +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; + +public class JsonUtilTest { + + @Test + public void testReadWriteValue() throws Exception { + String json = JsonUtil.writeValue(MealTestData.ADMIN_MEAL1); + System.out.println(json); + 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 meals = JsonUtil.readValues(json, Meal.class); + MealTestData.MATCHER.assertCollectionEquals(MealTestData.MEALS, meals); + } +} \ No newline at end of file From 99458ff62472f93dbc260484868dbca9dfb4c3da Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 13 May 2017 18:19:47 +0300 Subject: [PATCH 89/91] 7 10 jackson jsr310 --- pom.xml | 5 +++ .../topjava/web/json/JacksonObjectMapper.java | 11 ++---- .../web/json/JsonLocalDateTimeConverter.java | 38 ------------------- 3 files changed, 9 insertions(+), 45 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java diff --git a/pom.xml b/pom.xml index 563aba7cfcd8..afef57056af4 100644 --- a/pom.xml +++ b/pom.xml @@ -246,6 +246,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 0b1168fded86..742794ba8479 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; /** *

    @@ -28,10 +27,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 f0505487b63a..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/json/JsonLocalDateTimeConverter.java +++ /dev/null @@ -1,38 +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.DateTimeUtil; - -import java.io.IOException; -import java.time.LocalDateTime; - -public class JsonLocalDateTimeConverter { - public static class UserSettingSerializer extends JsonSerializer { - @Override - public void serialize(LocalDateTime ldt, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeString(DateTimeUtil.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 DateTimeUtil.parseLocalDateTime(jp.getText()); - } - - @Override - public Class handledType() { - return LocalDateTime.class; - } - } -} From 747cc3dc653eca52155ed29ed029375db96b0f2e Mon Sep 17 00:00:00 2001 From: Drag Date: Sat, 13 May 2017 18:40:46 +0300 Subject: [PATCH 90/91] 7 11 test with matcher --- .../ru/javawebinar/topjava/MealTestData.java | 2 +- .../java/ru/javawebinar/topjava/TestUtil.java | 17 +++++ .../ru/javawebinar/topjava/UserTestData.java | 2 +- .../topjava/matcher/ModelMatcher.java | 70 +++++++++++++++++-- .../topjava/matcher/TestMatcher.java | 25 +++++++ .../topjava/web/AbstractControllerTest.java | 5 ++ .../web/user/AdminRestControllerTest.java | 68 ++++++++++++++++-- .../web/user/ProfileRestControllerTest.java | 47 +++++++++++++ 8 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/TestUtil.java create mode 100644 src/test/java/ru/javawebinar/topjava/matcher/TestMatcher.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index 9d362edc78bf..d845f2772163 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -12,7 +12,7 @@ public class MealTestData { - public static final ModelMatcher MATCHER = new ModelMatcher<>(); + public static final ModelMatcher MATCHER = ModelMatcher.of(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..8132aaf0d4a5 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -0,0 +1,17 @@ +package ru.javawebinar.topjava; + +import org.springframework.test.web.servlet.ResultActions; + +import java.io.UnsupportedEncodingException; + +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 f2d6be7263f9..e84882da888e 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -15,7 +15,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 = 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 f51ac5e86697..10fa74a6fed7 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; + /** * This test Matcher assert equality of beans and collections *

    @@ -15,22 +23,31 @@ *

    * Default equality is String.valueOf(entity) * + * Assert json response body with expected result, converting it via {@link JsonUtil} + * into object(collection) and wrap results by Wrapper. + * * @param : Entity */ public class ModelMatcher { private Equality equality; + private Class entityClass; public interface Equality { boolean areEqual(T expected, T actual); } - public ModelMatcher() { - this((T expected, T actual) -> expected == actual || String.valueOf(expected).equals(String.valueOf(actual))); + private ModelMatcher(Class entityClass, Equality equality) { + this.entityClass = entityClass; + this.equality = equality; } - public ModelMatcher(Equality equality) { - this.equality = equality; + public static ModelMatcher of(Class entityClass) { + return of(entityClass, (T expected, T actual) -> expected == actual || String.valueOf(expected).equals(String.valueOf(actual))); + } + + public static ModelMatcher of(Class entityClass, Equality equality) { + return new ModelMatcher<>(entityClass, equality); } private class Wrapper { @@ -54,6 +71,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)); } @@ -62,11 +91,40 @@ public void assertCollectionEquals(Collection expected, Collection actual) Assert.assertEquals(wrap(expected), wrap(actual)); } - public Wrapper wrap(T entity) { + private Wrapper wrap(T entity) { return new Wrapper(entity); } - public List wrap(Collection collection) { + private 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); + } + }); + } + + @SafeVarargs + 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..45ac0f7d8a06 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/matcher/TestMatcher.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.matcher; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import ru.javawebinar.topjava.web.json.JsonUtil; + +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); + } + + protected abstract 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 c209f7be497b..b6a164133bbd 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -13,6 +13,7 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; import ru.javawebinar.topjava.AllActiveProfileResolver; +import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.service.UserService; import javax.annotation.PostConstruct; @@ -35,6 +36,9 @@ abstract public class AbstractControllerTest { CHARACTER_ENCODING_FILTER.setForceEncoding(true); } + @Autowired + private JpaUtil jpaUtil; + protected MockMvc mockMvc; @Autowired @@ -54,5 +58,6 @@ private 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 d7c975872a7d..c95865ce5742 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 4cac3445fa5768932bc74d41c53abcc343e275f7 Mon Sep 17 00:00:00 2001 From: Drag Date: Mon, 15 May 2017 11:44:39 +0300 Subject: [PATCH 91/91] 7 12 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..4f3f6179155e --- /dev/null +++ b/config/Topjava-soapui-project.xml @@ -0,0 +1,17 @@ + +http://localhost:8080text/html;charset=utf-8500 401htmlapplication/json200ns:Responseapplication/json;charset=UTF-8200Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/usersuser@yandex.rupasswordNo AuthorizationBasicNo Authorizationapplication/jsontext/html;charset=utf-8500htmlapplication/json201user:Responseapplication/json;charset=UTF-8201user: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:Responseapplication/json;charset=UTF-8200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/users/100000No AuthorizationBasicNo Authorizationtext/html;charset=utf-8405 500htmlapplication/json200data200data200data200data<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:Responseapplication/json;charset=UTF-8200prof:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profileBasicBasicGlobal HTTP Settingsapplication/jsontext/html;charset=utf-8500 405htmlapplication/json201user:Response200data200data200data200data200data200data<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 945bdba74a51..8da19b413b6e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -31,4 +31,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 0477b640f5ec..79f7d6e3fff4 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 + + + + -->