From 28ae6bb93e04963ac4ea538d0b38bca0d908dc6d Mon Sep 17 00:00:00 2001 From: JavaOPs Date: Mon, 16 Oct 2017 01:54:48 +0300 Subject: [PATCH 001/135] Add info --- README.md | 178 ++++++++++++++++++++++++++++++++++++++++++++++++ ReleaseNotes.md | 137 +++++++++++++++++++++++++++++++++++++ cv.md | 104 ++++++++++++++++++++++++++++ description.md | 76 +++++++++++++++++++++ 4 files changed, 495 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..ba1c73f6b652 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +Java Enterprise Online Project +=============================== +Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API и хранением в базах данных Postgresql и HSQLDB. + +![topjava_structure](https://user-images.githubusercontent.com/13649199/27433714-8294e6fe-575e-11e7-9c41-7f6e16c5ebe5.jpg) + + Когда вы слышите что-то, вы забываете это. + Когда вы видите что-то, вы запоминаете это. + Но только когда вы начинаете делать это, + вы начинаете понимать это + + Старинная китайская поговорка + +## Описание и план проекта +### Демо разрабатываемого приложения +### [Изменения проекта (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 Report 2016 +- [Java in 2017 Survey](http://www.baeldung.com/java-in-2017) +- Из юниоров в разработчики: получаем первую работу + +#### 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 + - [Основы Git за 20 минут](https://www.youtube.com/watch?v=TMeZGvtQnT8) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Работа с проектом (выполнять инструкции) +> Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео: в `UserMeals/UserMealWithExceed` поля изменились на `private` +- **Prepare_ to_ HW0.patch (скачать и положить в каталог вашего проекта)** + +## Инструкция по шагам (из видео): +- Установить ПО (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 +``` +Реализовать метод UserMealsUtil.getFilteredWithExceeded: +- должны возвращаться только записи между startTime и endTime +- поле UserMealWithExceed.exceed должно показывать, + превышает ли сумма калорий за весь день параметра метода caloriesPerDay + +Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. + +- Проверте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) +- Оцените Time complexity вашего алгоритма, если он O(N*N)- попробуйте сделать O(N). +``` +- Java 8 Date and Time API +- Алгоритмы и структуры данных для начинающих: сложность алгоритмов +- Time complexity +- Временная сложность алгоритма +- Вычислительная сложность + +#### Optional (Java 8 Stream API) +``` +Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) +``` +- Видео: Доступно о Java 8 Lambda +- 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 +- Дополнительно + - [Сергей Куксенко — Stream API, часть 1](https://www.youtube.com/watch?v=O8oN4KSZEXE) + - [Сергей Куксенко — Stream API, часть 2](https://www.youtube.com/watch?v=i0Jr2l3jrDA) + +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Замечания к HW0 +- 1: Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно при правках в рабочем проекте обязятельно нужно проконсультироваться/проревьюится у авторов кода (находится по истории VCS) +- 2: Наследовать `UserMealWithExceed` от `UserMeal` я не буду, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. +- 3: Правильная реализация должна быть простой и красивой, можно сделать 2-мя способами: через стримы и через циклы. Сложность должна быть O(N), т.е. без вложенных стримов и циклов. +- 4: При реализации через циклы посмотрите в `Map` на методы `getOrDefault` или `merge` +- 5: При реализации через `Stream` заменяйте `forEach` оператором `stream.map(..)` +- 6: Объявляйте переменные непосредственно перед использованием (если возможно - сразу с инициализацией). При объявлении коллекций используйте тип переменной - интерфейс (Map, List, ..) +- 7: Если IDEA предлагает оптимизацию (желтым подчеркивает), нарпимер заменить лямбду на метод-референс, соглашайтесь (Alt+Enter) +- 8: Пользуйтесь форматированием кода в IDEA: `Alt+Ctrl+L` +- 9: Перед check-in проверяйте чендж-лист (курсор на файл и Ctrl+D): не оставляйте в коде ничего лишнего (закомментированный код, TODO и пр.). Если файл не меняется (например только пробелы или переводы строк), не надо его чекинить, делайте ему `revert` (Git -> Revert / `Ctrl+Alt+Z`). +- 10: `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование. +- 11: Результаты, возвращаемые `UserMealsUtil.getFilteredWithExceeded` мы будем использовать [в нашем приложении](http://topjava.herokuapp.com/) для фильтрации по времени и отображения еды правильным цветом. +- 12: Обращайте внимание на комментарии к вашим коммитам в git. Они должны быть короткие и информативные (лучше на english) +- 13: Просто так запустить и померять скорость JVM нельзя (как минимум дать прогреться и запустить очень большое число раз). +Лучше использовать какие-нибудь бенчмарки, например [JMH](http://tutorials.jenkov.com/java-performance/jmh.html), который мы юзаем на другом проекте (Mastejava) +----- + +### Полезные ресурсы +> ВНИМАНИЕ: +> - ДЗ первого урока будет связано с созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах. Введение будет, но предварительное знакомство не помешает. +> - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! + +Все остальное - опционально. + +#### HTML, JavaScript, CSS +- [Справочник по WEB](https://developer.mozilla.org/ru/) +- [Видео по WEB технологиям](https://www.youtube.com/user/WebMagistersRu/playlists) +- [Изучение JavaScript в одном видео уроке за час](https://www.youtube.com/watch?v=QBWWplFkdzw) +- HTML, CSS, JAVASCRIPT, SQL, JQUERY, BOOTSTRAP +- Введение в программирование на JavaScript +- Стандарты кодирования для HTML, CSS и JavaScript’a +- Основы работы с 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 + +### Туториалы, разное +[Что нужно знать о бэкенде новичку в веб-разработке](https://tproger.ru/translations/backend-web-development) +[Туториалы: Spring Framework, Hibernate, Java Core, JDBC](http://proselyte.net/tutorials/) + +#### Сервлеты +- Как создать 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..3fad758b11d6 --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,137 @@ +# TopJava Release Notes +### Topjava 11 +- добавил + - доп. решение HW1 через одним return и O(N) + - раскрасил лог ([Logback layouts coloring](https://logback.qos.ch/manual/layouts.html#coloring)) +- рефакторинг + - починил коммит формы по cancel (`history.back()`) в FireFox + - заменил неработающий DependencyCi на [VersionEye](https://www.versioneye.com/) c проверкой зависимостей на uptodate + - починил `CrudUserRepository.getWithMeals()` через `@EntityGraph`. С неколькими ролями (у админа) еда дублируется + - починил тесты контроллеров с профилем JDBC (`JpaUtil` отсутствует в контексте JDBC) + - переименовал `meal.jsp/user.jsp` в `mealForm.jsp/userForm.jsp` + - в `InMemoryMealRepositoryImpl.save()` сделал update атомарным + - переименовал методы сервисов `save` в `create` + - переименовал и cделал классы `BaseEntity` и `NamedEntity` абстрактными + - обновил Noty и API с ним до 3.1.0. Добавил glyphicon в сообщения Noty + - заменил `MATCHER_WITH_EXCEED` на валидацию через [JSONassert](https://github.com/skyscreamer/JSONassert). + - поменял Deprecated валидаторы `org.hibernate.validator.constraints` на `javax.validation.constraints` + - убрал пароль из результатов REST через [@JsonProperty READ_ONLY / WRITE_ONLY](https://stackoverflow.com/questions/12505141/only-using-jsonignore-during-serialization-but-not-deserialization/12505165#12505165). Тесты на REST пришлось починить добавлением добавлением в JSON пароля как дополнительного параметра (`JsonUtil.writeWithExtraProps`) + - **убрал JSON View и сделал преобразование времени на UI с помощью [jQuery converters](http://api.jquery.com/jQuery.ajax/#using-converters)** + - **поменял [группу валидации по умолчанию при сохранении через JPA](https://stackoverflow.com/questions/16930623/16930663#16930663).** Теперь + все валидаторы в модели работаю по умолчанию (`groups` не требуется). + - Добавил в `ErrorInfo` тип ошибки `ErrorType` + i18n. + +- правки + - переименовал `ModelMatcher` в `BeanMatcher` и починил: можно сравнивать только упорядоченные коллекции (List) + - поменял зависимость `org.hibernate:hibernate-validator` на `org.hibernate.validator:hibernate-validator` (warning при сборке) + +### Topjava 10 +- добавил + - доступ к AuthorizedUser через [`@AuthenticationPrincipal`](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#mvc-authentication-principal) и [authentication Tag](http://docs.spring.io/spring-security/site/docs/current/reference/html/taglibs.html#the-authentication-tag) + - [Обработку 404 NotFound](https://stackoverflow.com/questions/18322279/spring-mvc-spring-security-and-error-handling) + - локализацию ошибок валидации + - проверки json в тестах через [JSONassert](https://github.com/skyscreamer/JSONassert) и [через jsonPath](https://www.petrikainulainen.net/programming/spring-framework/integration-testing-of-spring-mvc-applications-write-clean-assertions-with-jsonpath/) + - [логирование от Postgres Driver](http://stackoverflow.com/a/43242620/548473) + - в `.travis.yml` [сборку только ветки master](https://docs.travis-ci.com/user/customizing-the-build#Building-Specific-Branches) + - [защиту от кэширование ajax запросов в IE](https://stackoverflow.com/a/4303862/548473) + - обработку запрета модификации системный юзеров через универсальный `ApplicationException` + - рефакторинг + - сделал `@EntityGraph` через `attributePaths` + - реализаовал обработку дублирования `user.email` и `meal.dateTime` через [Controller Based Exception Handling](https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc#controller-based-exception-handling) + - поменял отключение транзакционности в тестах через `@Transactional(propagation = Propagation.NEVER)` + - сделал выбор в сервлете через switch + - [все логгирование сделал через {} форматирование](http://stackoverflow.com/questions/10555409/logger-slf4j-advantages-of-formatting-with-instead-of-string-concatenation) и поправил его в контроллерах (поле проверки id) + - [перешел на конструктор DI](http://stackoverflow.com/questions/39890849/what-exactly-is-field-injection-and-how-to-avoid-it) + - в `ModelMatcher` переименовал `Comparator` -> `Equality` + - [заинлайнил все лямбды](http://stackoverflow.com/questions/19718353/is-repeatedly-instantiating-an-anonymous-class-wasteful) (компараторы, ModelMatcher.equality) + - поменялась реализация `JdbcUserRepositoryImpl.getAll()` + - на UI кнопки в таблице заменились на линки, поправил сообщения локализации + - [сделал кастомизацию JSON (@JsonView) и валидацию (groups)](https://drive.google.com/file/d/0B9Ye2auQ_NsFRTFsTjVHR2dXczA) для данных еды, отдаваемых на UI + - в `JdbcUserRepositoryImpl` поменял `MapSqlParameterSource` на `BeanPropertySqlParameterSource` +- удалил + - зависимость `javax.transaction.jta` (уже не нужна) + - `${spring.version}` в `pom.xml` зависимостях (уже есть в `spring-framework-bom`) + - distinct из запроса Hibernate на пользователей с ролями. [Оптимизация запроса distinct: 15.16.2](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct) + - лишние `
` тэги (`shadow` и `view-box`) + +### Topjava 9 +- добавил + - выбор профиля базы через `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 + - сделал универсальную обработку исключений дублирования email и dateTime + +### 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..4b51e6d7fd8b --- /dev/null +++ b/cv.md @@ -0,0 +1,104 @@ +## Составление резюме, подготовка к интервью, поиск работы + +![cv](https://cloud.githubusercontent.com/assets/13649199/10877471/93ea86b8-8157-11e5-9bfa-95e3fba75c58.jpg) + +- Научиться программировать сложнее, чем кажется +- [Собеседование. Разработка ПО. Вопросы.](https://drive.google.com/open?id=0B9Ye2auQ_NsFQVc2WUdCR0xvLWM) +- [Набор ссылок для тренировки и прохождения интервью](https://github.com/andreis/interview) + +### Составление резюме: +- [VisualCV: create resume in minutes](https://www.visualcv.com/) +- Выбрать шаблон для резюме +- Как продать свое резюме в 2 раза дороже +- Как правильно составить резюме +- Резюме программистов. Часть 1 (плохие) +- Резюме программистов. Часть 2 (хорошие) +- Как составить резюме на английском +- ОФОРМЛЕНИЕ IT-РЕЗЮМЕ для USA + +### Наши истории (делимся опытом и успехом) + +### Тесты/задачи онлайн: +- [Java Programming Test](https://tests4geeks.com/java) +- game: test Java skills +- Codility lesson tests +- Quizful- тесты онлайн +- LeetCode Online Judge +- Sphere online judge +- Codility programmers lessons +- Hackerrank practice coding + +## [Тестовое собеседование, самые спрашиваемые темы](http://javaops.ru/interview/test.html) + +### Интервью: +- Михаил Портнов. Собеседование на работу: как продать себя грамотно +- Михаил Портнов. Какие вопросы мы задаем на собеседовании? +- Михаил Портнов. Собеседование на работу: жизненный путь +- Канал: Резюме, поиск работы, интервью +- Яков Файн: Как стать профессиональным 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: +- Обязательно убери из резюме **любое упоминание Junior**. Количество обращений возрастет на порядок. Ссылку на стажировку можно поставить: http://javaops.ru (в linkedin: https://www.linkedin.com/company/java-online-projects). +- После завершения проекта ты освошь все заявленные в нем технологии - вставь их в квалификацию (включая 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. + - собственная доработка проекта + +- Делай упор не на обучение, а на **участие в проекте**. Выполнение домашних заданий это полноценное участие с написанием функционала по всем пройденным технологиям. На собеседовании смотрят не на то, что ты заканчивал, а на опыт и знания. + +### В процессе обучения +- Если рассмотриваешь предложения по работе, подними в своем профиле этот флаг и обязательно заполни ссылку на резюме. Обновления нашей базы выпускников смотрят уже более 125 партнеров по трудоустройству (компании и индивидуальные рекрутеры). Проверь содержание "Информация для HR": по нему принимают решение, открывать резюме или нет. + +- Вступайте в нашу группу участников Slack: каналы помощи с Java, отзывы о работодателях, обсуждение тестовых заданий, вакансии, цены на рынке труда, IT события, интересные видео и многое другое. + +- Подпишитесь на рассылку вакансий под себя + +### После прохождения испытательного срока жду твою [историю успеха](http://javaops.ru/view/story) + +### Основные сайты поиска работы: +- Яндекс агрегатор +- HH +- LinkedIn +- djinni.co (более актуально для Украины) + +## Как выжить на испытательном сроке +- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В + процессе формулирования очень часто приходит ее решение. +- Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки + разработчика. В логах надо читать верх самого нижнего эксепшена - там причина всей портянки. +- Грамотно уделяй время каждой проблеме. Две крайности - сразу бросаться за помощью и + бится нам ней часами. + Пробуй решить ее сам и в зависимости от проблемы выделяй на это разумное время. +- Если тебе что-то объясняют по проекту - обязательно записывай. +- Когда получаешь задачу - уточни все очень подробно. +- Получай в процессе решения обратную связь - в том ли направлении ты идешь. +- Не игнорируй совместные ланчи (курилки) +- Готовься к стендапам/летучкам. Задавай на них вменяемые вопросы. Выказывай заинтересованность +- Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи. +- [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) + +## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246) diff --git a/description.md b/description.md new file mode 100644 index 000000000000..d01afc2e22a7 --- /dev/null +++ b/description.md @@ -0,0 +1,76 @@ +#### Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API и сохранением в базах данных Postgresql и HSQLDB. + +- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения. +- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий. +- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit тестов. +- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой +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. jQuery datatables. +- AJAX. jQuery. Notifications. +- Spring Security +- Spring Binding/Validation +- Работа с datatables через Ajax. +- Spring Security Test +- [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/open?id=0B9Ye2auQ_NsFRTFsTjVHR2dXczA) +- Encoding password +- CSRF (добавление в проект защиты от межсайтовой подделки запроса) +- form-login. Spring Security Taglib +- Handler interceptor +- Spring Exception Handling +- Смена локали +- Фильтрация JSON через @JsonView +- Защита от XSS (Cross Site Scripting) +- Деплой в Heroku +- Локализация datatables, ошибок валидации +- Обработка ошибок 404 (NotFound) +- Доступ к AuthorizedUser +- Собеседование. Разработка ПО From d72f5658abd7dca0b4da195f3dfaa7e1034fa870 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 16 Oct 2017 16:05:07 +0300 Subject: [PATCH 002/135] Update README.md --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba1c73f6b652..4bea7b26a911 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,17 @@ Java Enterprise Online Project - [Сергей Куксенко — Stream API, часть 1](https://www.youtube.com/watch?v=O8oN4KSZEXE) - [Сергей Куксенко — Stream API, часть 2](https://www.youtube.com/watch?v=i0Jr2l3jrDA) +#### Замечания по использованию Stream API: +- Когда встречаешь что-то непривычное, приходится перестраивать мозги. Например переход с процедурного на ООП программирование дается непросто. Те, кто не знает шаблонов (и не хоят учить) также их встречают плохо. Хорошая новость в том, что если это принять и начать использовать, то начинаешь получать от этого удовольствие. И тут главное не впасть в другую крайность: + - [Используйте Stream API проще (или не используйте вообще)](https://habrahabr.ru/post/337350/) +- Если вас беспокоить производительность стримов, обязательно прочитайте про оптимизацию + - ["Что? Где? Когда?"](http://optimization.guide/intro.html) + - [Перформанс: что в имени тебе моём?](https://habrahabr.ru/company/jugru/blog/338732/) + - [Performance это праздник](https://habrahabr.ru/post/326242/) + +При использовании Stream API производительность улучшиться только на больших задачах, где возможно распараллеливание. +Еще - просто так запустить и померять скорость JVM нельзя (как минимум дать прогреться и запустить очень большое число раз). Лучше использовать какие-нибудь бенчмарки, например [JMH](http://tutorials.jenkov.com/java-performance/jmh.html), который мы юзаем на другом проекте (Mastejava). + ## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Замечания к HW0 - 1: Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно при правках в рабочем проекте обязятельно нужно проконсультироваться/проревьюится у авторов кода (находится по истории VCS) - 2: Наследовать `UserMealWithExceed` от `UserMeal` я не буду, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. @@ -112,8 +123,6 @@ Java Enterprise Online Project - 10: `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование. - 11: Результаты, возвращаемые `UserMealsUtil.getFilteredWithExceeded` мы будем использовать [в нашем приложении](http://topjava.herokuapp.com/) для фильтрации по времени и отображения еды правильным цветом. - 12: Обращайте внимание на комментарии к вашим коммитам в git. Они должны быть короткие и информативные (лучше на english) -- 13: Просто так запустить и померять скорость JVM нельзя (как минимум дать прогреться и запустить очень большое число раз). -Лучше использовать какие-нибудь бенчмарки, например [JMH](http://tutorials.jenkov.com/java-performance/jmh.html), который мы юзаем на другом проекте (Mastejava) ----- ### Полезные ресурсы From 376f1d8bd26a691b1989ddfde9ea9147a6b9b51b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 16 Oct 2017 16:15:46 +0300 Subject: [PATCH 003/135] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4bea7b26a911..f0371c311c2b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ Java Enterprise Online Project #### Spring Pet-Clinic - Spring PetClinic Sample Application -- Demo - Presentation ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) Системы управления версиями. Git. From 6a8b5980670ce1441904e4c9561008d475b1e236 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 16 Oct 2017 20:50:05 +0300 Subject: [PATCH 004/135] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f0371c311c2b..1112e626f086 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,8 @@ Java Enterprise Online Project Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..) ``` - Видео: Доступно о Java 8 Lambda -- Java 8: Lambda выражения -- Java 8: Потоки +- Java 8: Lambda выражения +- Java 8: Потоки - Pуководство по Java 8 Stream - Лямбда-выражения в Java 8 - A Guide to Java 8 @@ -162,7 +162,7 @@ Java Enterprise Online Project [Туториалы: Spring Framework, Hibernate, Java Core, JDBC](http://proselyte.net/tutorials/) #### Сервлеты -- Как создать Servlet? Полное руководство. +- Как создать Servlet? Полное руководство. - Простое CRUD приложение на JSP и сервлетах #### JDBC, SQL From 39719809cb2b266494dce6586452695807ba4bdf Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 18 Oct 2017 13:16:45 +0700 Subject: [PATCH 005/135] Prepare to HW0 --- .../javawebinar/topjava/model/UserMeal.java | 33 +++++++++++++++++ .../topjava/model/UserMealWithExceed.java | 24 +++++++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 13 +++++++ .../topjava/util/UserMealsUtil.java | 35 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMeal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java new file mode 100644 index 000000000000..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 3b1ef9235cf28cca074147a02f12811b0045af5c Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:24:33 +0700 Subject: [PATCH 006/135] 1 0 fix --- .gitignore | 4 +-- pom.xml | 2 +- .../java/ru/javawebinar/topjava/Main.java | 7 ++-- .../model/{UserMeal.java => Meal.java} | 8 ++--- ...ealWithExceed.java => MealWithExceed.java} | 8 ++--- .../javawebinar/topjava/util/MealsUtil.java | 31 ++++++++++++++++ .../topjava/util/UserMealsUtil.java | 35 ------------------- 7 files changed, 39 insertions(+), 56 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{UserMeal.java => Meal.java} (78%) rename src/main/java/ru/javawebinar/topjava/model/{UserMealWithExceed.java => MealWithExceed.java} (67%) 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/.gitignore b/.gitignore index 6ca05c4a5184..b4860155a096 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,4 @@ out target *.iml log - - - +*.patch \ No newline at end of file diff --git a/pom.xml b/pom.xml index c8a1c78f3b29..e3b8143e3e63 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.7.0 ${java.version} ${java.version} diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java index b23a2f0961fc..cb7e35af6afa 100644 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ b/src/main/java/ru/javawebinar/topjava/Main.java @@ -1,11 +1,8 @@ package ru.javawebinar.topjava; /** - * User: gkislin - * Date: 05.08.2015 - * - * @link http://caloriesmng.herokuapp.com/ - * @link https://github.com/JavaOPs/topjava + * @see Demo + * @see Initial project */ public class Main { public static void main(String[] args) { diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java similarity index 78% rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java rename to src/main/java/ru/javawebinar/topjava/model/Meal.java index aec9beb946cf..f546cef0f74a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -2,18 +2,14 @@ import java.time.LocalDateTime; -/** - * 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; diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java similarity index 67% rename from src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java rename to src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index f4f880e4e67a..c8e0d713c217 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -2,11 +2,7 @@ import java.time.LocalDateTime; -/** - * GKislin - * 11.01.2015. - */ -public class UserMealWithExceed { +public class MealWithExceed { private final LocalDateTime dateTime; private final String description; @@ -15,7 +11,7 @@ public class UserMealWithExceed { private final boolean exceed; - public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { this.dateTime = dateTime; this.description = description; this.calories = calories; diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java new file mode 100644 index 000000000000..a1bb183937ef --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,31 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealWithExceed; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +public class MealsUtil { + public static void main(String[] args) { + List mealList = 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) + ); + 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; + } +} 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 8fb628b33b3cecea4110a4260a18e4e5435d671f Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:24:58 +0700 Subject: [PATCH 007/135] 1 1 HW0 stream --- .../ru/javawebinar/topjava/model/Meal.java | 10 +++++ .../topjava/model/MealWithExceed.java | 12 +++++- .../javawebinar/topjava/util/MealsUtil.java | 40 ++++++++++++------- 3 files changed, 47 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 f546cef0f74a..943ff5cd59fa 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; public class Meal { private final LocalDateTime dateTime; @@ -26,4 +28,12 @@ public String getDescription() { public int getCalories() { return calories; } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index c8e0d713c217..4751c9e4fd69 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -17,4 +17,14 @@ public MealWithExceed(LocalDateTime dateTime, String description, int calories, this.calories = calories; this.exceed = exceed; } -} + + @Override + public String toString() { + return "UserMealWithExceed{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index a1bb183937ef..2143f7f49faa 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -3,29 +3,41 @@ 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; public class MealsUtil { public static void main(String[] args) { - List mealList = 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 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) ); - getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); -// .toLocalDate(); -// .toLocalTime(); + List mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExceeded.forEach(System.out::println); } - public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with correctly exceeded field - return null; + 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 From 02158270d067eb5e0c3dd04f2ee864c9d13af909 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:25:35 +0700 Subject: [PATCH 008/135] 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 2143f7f49faa..2ef2768e90bc 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; public class MealsUtil { @@ -24,6 +22,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) { @@ -35,9 +35,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 a5601f9e83a1e418da08123d1f38813e0bcc69bb Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:26:05 +0700 Subject: [PATCH 009/135] 1 3 HW0 optional2 --- .../javawebinar/topjava/util/MealsUtil.java | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 2ef2768e90bc..209284416ff6 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -8,7 +8,12 @@ import java.time.LocalTime; import java.time.Month; import java.util.*; +import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; public class MealsUtil { public static void main(String[] args) { @@ -24,6 +29,8 @@ public static void main(String[] args) { mealsWithExceeded.forEach(System.out::println); System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -36,7 +43,7 @@ public static List getFilteredWithExceeded(List meals, Loc return meals.stream() .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) - .collect(Collectors.toList()); + .collect(toList()); } public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -53,6 +60,51 @@ public static List getFilteredWithExceededByCycle(List mea return mealsWithExceeded; } + public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Collection> list = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate)).values(); + + return list.stream().flatMap(dayMeals -> { + boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; + return dayMeals.stream().filter(meal -> + TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExceed(meal, exceed)); + }).collect(toList()); + } + + public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final class Aggregate { + private final List dailyMeals = new ArrayList<>(); + private int dailySumOfCalories; + + private void accumulate(Meal meal) { + dailySumOfCalories += meal.getCalories(); + if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { + dailyMeals.add(meal); + } + } + + // never invoked if the upstream is sequential + private Aggregate combine(Aggregate that) { + this.dailySumOfCalories += that.dailySumOfCalories; + this.dailyMeals.addAll(that.dailyMeals); + return this; + } + + private Stream finisher() { + final boolean exceed = dailySumOfCalories > caloriesPerDay; + return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); + } + } + + Collection> values = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate, + Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) + ).values(); + + return values.stream().flatMap(identity()).collect(toList()); + } + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } From dc4a575cf183599d6298485390773b84def94948 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:28:04 +0700 Subject: [PATCH 010/135] 1 4 switch to war --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 19 +++++++++++++++++++ src/main/webapp/index.html | 13 +++++++++++++ src/main/webapp/users.jsp | 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 e3b8143e3e63..2e87eaf4e859 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..1c91ffe2cbaf --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + Topjava + + + userServlet + ru.javawebinar.topjava.web.UserServlet + 0 + + + userServlet + /users + + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..6253517f8b84 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,13 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

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

Home

+

Users

+ + \ No newline at end of file From 5740c90bdf178a6ee5494fd2fc17e46c812c854e Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:28:52 +0700 Subject: [PATCH 011/135] manual all UserServlet --- src/main/java/ru/javawebinar/topjava/web/UserServlet.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java 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..913ecf6a8173 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,4 @@ +package ru.javawebinar.topjava.web; + +public class UserServlet { +} From e7b485ac7cc1a223ba1863077b04ebbb90e0b47a Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:30:03 +0700 Subject: [PATCH 012/135] 1 5 add servlet api --- pom.xml | 8 ++++++++ .../ru/javawebinar/topjava/web/UserServlet.java | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2e87eaf4e859..abd5d2a6b91e 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 index 913ecf6a8173..76056e06c019 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,4 +1,15 @@ package ru.javawebinar.topjava.web; -public class UserServlet { +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class UserServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.getRequestDispatcher("/users.jsp").forward(request, response); + } } From df0a160c7524e93818125088ad6e5a582aa8e2d3 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:31:15 +0700 Subject: [PATCH 013/135] 1 6 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 abd5d2a6b91e..010a0c933e95 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 76056e06c019..11f282bac482 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -10,6 +10,7 @@ public class UserServlet extends HttpServlet { @Override 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 53217da65aa9fdc2b5c5a81ef187f57b9bab3c84 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 2 Nov 2017 16:32:35 +0700 Subject: [PATCH 014/135] 1 7 logging --- pom.xml | 25 ++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 7 +++++ src/main/resources/logback.xml | 29 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 010a0c933e95..d2ac57ffd6f5 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ 1.8 UTF-8 UTF-8 + + + 1.2.3 + 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 11f282bac482..ef52d67576c0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,15 +1,22 @@ package ru.javawebinar.topjava.web; +import org.slf4j.Logger; + import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import static org.slf4j.LoggerFactory.getLogger; + public class UserServlet extends HttpServlet { + private static final Logger log = getLogger(UserServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("redirect to users"); + // request.getRequestDispatcher("/users.jsp").forward(request, response); response.sendRedirect("users.jsp"); } 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 95883acf40f26be0a9fa23191a973c3d3ab28551 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 9 Nov 2017 16:14:28 +0700 Subject: [PATCH 015/135] 2_0 add fix --- src/main/java/ru/javawebinar/topjava/Main.java | 11 ----------- .../ru/javawebinar/topjava/model/MealWithExceed.java | 2 +- .../java/ru/javawebinar/topjava/util/MealsUtil.java | 2 +- .../java/ru/javawebinar/topjava/util/TimeUtil.java | 4 ---- 4 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java deleted file mode 100644 index cb7e35af6afa..000000000000 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.javawebinar.topjava; - -/** - * @see Demo - * @see Initial project - */ -public class Main { - public static void main(String[] args) { - System.out.format("Hello Topjava Enterprise!"); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index 4751c9e4fd69..438ad5e2b800 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -20,7 +20,7 @@ public MealWithExceed(LocalDateTime dateTime, String description, int calories, @Override public String toString() { - return "UserMealWithExceed{" + + return "MealWithExceed{" + "dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 209284416ff6..7fc4ed1eb4c5 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -105,7 +105,7 @@ private Stream finisher() { return values.stream().flatMap(identity()).collect(toList()); } - public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + private static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 02399b7288cf..b7eb2af6f93e 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -2,10 +2,6 @@ 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; From 8517df345f66564870888fc55ee60be567769f97 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 9 Nov 2017 16:48:54 +0700 Subject: [PATCH 016/135] 2_1 HW1 --- pom.xml | 6 +++ .../topjava/model/MealWithExceed.java | 16 ++++++ .../topjava/util/DateTimeUtil.java | 17 ++++++ .../javawebinar/topjava/util/MealsUtil.java | 53 +++++++++---------- .../ru/javawebinar/topjava/util/TimeUtil.java | 9 ---- .../javawebinar/topjava/web/MealServlet.java | 22 ++++++++ .../javawebinar/topjava/web/UserServlet.java | 6 +-- src/main/webapp/WEB-INF/tld/functions.tld | 16 ++++++ src/main/webapp/WEB-INF/web.xml | 10 ++++ src/main/webapp/index.html | 1 + src/main/webapp/meals.jsp | 47 ++++++++++++++++ 11 files changed, 163 insertions(+), 40 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 d2ac57ffd6f5..6f484d0beec9 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 438ad5e2b800..b2ab232a03e8 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -18,6 +18,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 "MealWithExceed{" + 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..5de28849657a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,17 @@ +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 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 7fc4ed1eb4c5..30f0dcee1df6 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -7,30 +7,27 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.*; -import java.util.stream.Collector; +import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; -import static java.util.function.Function.identity; import static java.util.stream.Collectors.toList; 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); - - System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + 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 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) { @@ -41,11 +38,16 @@ 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(toList()); } + private static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } + +/* public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { final Map caloriesSumByDate = new HashMap<>(); @@ -53,7 +55,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)); } }); @@ -67,7 +69,7 @@ public static List getFilteredWithExceededInOnePass(List m return list.stream().flatMap(dayMeals -> { boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; return dayMeals.stream().filter(meal -> - TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)) .map(meal -> createWithExceed(meal, exceed)); }).collect(toList()); } @@ -79,7 +81,7 @@ final class Aggregate { private void accumulate(Meal meal) { dailySumOfCalories += meal.getCalories(); - if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { + if (DateTimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { dailyMeals.add(meal); } } @@ -104,8 +106,5 @@ private Stream finisher() { return values.stream().flatMap(identity()).collect(toList()); } - - private static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { - return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); - } +*/ } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java deleted file mode 100644 index b7eb2af6f93e..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.javawebinar.topjava.util; - -import java.time.LocalTime; - -public class TimeUtil { - public static boolean 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..672ea881707e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.util.MealsUtil; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class MealServlet extends HttpServlet { + private static final Logger log = LoggerFactory.getLogger(MealServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.info("getAll"); + request.setAttribute("meals", MealsUtil.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 ef52d67576c0..f6cf12e69976 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -15,9 +15,7 @@ public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.debug("redirect to users"); - -// request.getRequestDispatcher("/users.jsp").forward(request, response); - response.sendRedirect("users.jsp"); + log.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); } } diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld new file mode 100644 index 000000000000..d138fecdbfb5 --- /dev/null +++ b/src/main/webapp/WEB-INF/tld/functions.tld @@ -0,0 +1,16 @@ + + + + 1.0 + functions + http://topjava.javawebinar.ru/functions + + + formatDateTime + ru.javawebinar.topjava.util.DateTimeUtil + java.lang.String toString(java.time.LocalDateTime) + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 1c91ffe2cbaf..d2e475517f7e 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -16,4 +16,14 @@ /users + + mealServlet + ru.javawebinar.topjava.web.MealServlet + 0 + + + mealServlet + /meals + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 6253517f8b84..cd88b335a454 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -8,6 +8,7 @@

Проект Users +
  • Meals
  • diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 000000000000..89e626a8f492 --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,47 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> +<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> + + + Meal list + + + +
    +

    Home

    +

    Meals

    +
    + + + + + + + + + + + + + + + + +
    DateDescriptionCalories
    + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}
    +
    + + \ No newline at end of file From 5fc6e7aa0745e4795934d84ca237a5cf2193ee0f Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 10 Nov 2017 10:18:54 +0700 Subject: [PATCH 017/135] 2_1 HW1 --- .../ru/javawebinar/topjava/model/Meal.java | 29 +++++++++ .../topjava/model/MealWithExceed.java | 12 +++- .../InMemoryMealRepositoryImpl.java | 43 +++++++++++++ .../topjava/repository/MealRepository.java | 15 +++++ .../javawebinar/topjava/util/MealsUtil.java | 7 ++- .../javawebinar/topjava/web/MealServlet.java | 63 ++++++++++++++++++- src/main/webapp/mealForm.jsp | 51 +++++++++++++++ src/main/webapp/meals.jsp | 5 ++ 8 files changed, 217 insertions(+), 8 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/mealForm.jsp diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 943ff5cd59fa..3abbee42511e 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,6 +5,8 @@ import java.time.LocalTime; public class Meal { + private Integer id; + private final LocalDateTime dateTime; private final String description; @@ -12,11 +14,24 @@ public class Meal { private final int calories; public Meal(LocalDateTime dateTime, String description, int calories) { + this(null, dateTime, description, calories); + } + + public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -36,4 +51,18 @@ public LocalDate getDate() { public LocalTime getTime() { return dateTime.toLocalTime(); } + + public boolean isNew() { + return id == null; + } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index b2ab232a03e8..2b375e45eecc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -3,6 +3,8 @@ import java.time.LocalDateTime; public class MealWithExceed { + private final Integer id; + private final LocalDateTime dateTime; private final String description; @@ -11,13 +13,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; } @@ -37,7 +44,8 @@ public boolean isExceed() { @Override public String toString() { return "MealWithExceed{" + - "dateTime=" + dateTime + + "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..bcb9cd8894a0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class 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..e249a885c8bf --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -0,0 +1,15 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; + +import java.util.Collection; + +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 30f0dcee1df6..81e76b78b188 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -8,6 +8,7 @@ import java.time.LocalTime; import java.time.Month; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -26,11 +27,11 @@ public class MealsUtil { public static final int DEFAULT_CALORIES_PER_DAY = 2000; - 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)) @@ -44,7 +45,7 @@ public static List getFilteredWithExceeded(List meals, Loc } private 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); } /* diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 672ea881707e..065bb4ff8d81 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,21 +2,78 @@ 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; 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 = "create".equals(action) ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : + repository.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); + break; + case "all": + default: + log.info("getAll"); + request.setAttribute("meals", + MealsUtil.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); } } diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp new file mode 100644 index 000000000000..3fa1f71e66c1 --- /dev/null +++ b/src/main/webapp/mealForm.jsp @@ -0,0 +1,51 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
    +

    Home

    +

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

    +
    + +
    + +
    +
    DateTime:
    +
    +
    +
    +
    Description:
    +
    +
    +
    +
    Calories:
    +
    +
    + + +
    +
    + + diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 89e626a8f492..be21a45af406 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -19,6 +19,7 @@

    Home

    Meals

    + Add Meal
    @@ -26,6 +27,8 @@ + + @@ -39,6 +42,8 @@ + +
    Date Description Calories
    ${meal.description} ${meal.calories}UpdateDelete
    From 36f03f10480d86c7fe636f083b503561d7aea991 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 10 Nov 2017 13:40:44 +0700 Subject: [PATCH 018/135] 2_1 HW1 --- .../javawebinar/topjava/AuthorizedUser.java | 14 +++ .../topjava/model/AbstractBaseEntity.java | 26 ++++++ .../topjava/model/AbstractNamedEntity.java | 24 +++++ .../ru/javawebinar/topjava/model/Role.java | 6 ++ .../ru/javawebinar/topjava/model/User.java | 91 +++++++++++++++++++ .../topjava/repository/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, 424 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/AuthorizedUser.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/Role.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/User.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/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/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java new file mode 100644 index 000000000000..a3d71fcb46ed --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractBaseEntity { + protected Integer id; + + protected AbstractBaseEntity(Integer id) { + this.id = id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public boolean isNew() { + return this.id == null; + } + + @Override + public String toString() { + return String.format("Entity %s (%s)", getClass().getName(), id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java new file mode 100644 index 000000000000..259511dd0b65 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractNamedEntity extends AbstractBaseEntity { + + protected String name; + + protected AbstractNamedEntity(Integer id, String name) { + super(id); + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return 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/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..d88e381945d8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,91 @@ +package ru.javawebinar.topjava.model; + +import java.util.Date; +import java.util.EnumSet; +import java.util.Set; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class User extends AbstractNamedEntity { + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + } + + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + super(id, name); + this.email = email; + this.password = password; + this.caloriesPerDay = caloriesPerDay; + this.enabled = enabled; + this.roles = roles; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getRegistered() { + return registered; + } + + public void setRegistered(Date registered) { + this.registered = registered; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getCaloriesPerDay() { + return caloriesPerDay; + } + + public void setCaloriesPerDay(int caloriesPerDay) { + this.caloriesPerDay = caloriesPerDay; + } + + public boolean isEnabled() { + return enabled; + } + + public Set getRoles() { + return roles; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "User (" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + + ')'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/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..d0cf33e30815 --- /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 create(User user); + + void delete(int id) throws NotFoundException; + + User get(int id) throws NotFoundException; + + User getByEmail(String email) throws NotFoundException; + + void update(User user); + + List getAll(); +} \ 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..d2f2a9103a0f --- /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 create(User user) { + return repository.save(user); + } + + @Override + public void delete(int id) throws NotFoundException { + 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..2914ed684410 --- /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.AbstractBaseEntity; +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(AbstractBaseEntity entity) { + if (!entity.isNew()) { + throw new IllegalArgumentException(entity + " must be new (id=null)"); + } + } + + public static void assureIdConsistent(AbstractBaseEntity 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..16a68259ec20 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,49 @@ +package ru.javawebinar.topjava.web.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.UserService; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +public abstract class AbstractUserController { + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private UserService service; + + public List getAll() { + log.info("getAll"); + return service.getAll(); + } + + public User get(int id) { + log.info("get {}", id); + return service.get(id); + } + + public User create(User user) { + log.info("create {}", user); + checkNew(user); + return service.create(user); + } + + public void delete(int id) { + log.info("delete {}", id); + service.delete(id); + } + + public void update(User user, int id) { + log.info("update {} with id={}", user, id); + assureIdConsistent(user, id); + service.update(user); + } + + public User getByMail(String email) { + log.info("getByEmail {}", email); + return service.getByEmail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java new file mode 100644 index 000000000000..ae3374468e41 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +public class AdminRestController extends AbstractUserController { + + @Override + public List getAll() { + return super.getAll(); + } + + @Override + public User get(int id) { + return super.get(id); + } + + @Override + public User create(User user) { + return super.create(user); + } + + @Override + public void delete(int id) { + super.delete(id); + } + + @Override + public void update(User user, int id) { + super.update(user, id); + } + + @Override + public User getByMail(String email) { + return super.getByMail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java new file mode 100644 index 000000000000..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 371f2e95d33c38c6d0ce177b62b954ad247dadac Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 12 Nov 2017 17:37:56 +0600 Subject: [PATCH 019/135] 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 6f484d0beec9..537022542c3b 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 4.3.12.RELEASE + 1.2.3 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 bcb9cd8894a0..9eecc3b4c7cd 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..0825c7b69afa --- /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 065bb4ff8d81..0dca99ceb244 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.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.ServletConfig; 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 9208198ab1e012d71b67fb49bd9dd81937b3cb43 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 12 Nov 2017 17:40:30 +0600 Subject: [PATCH 020/135] 2_5 add 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..2da5cacd3228 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.create(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 d2f2a9103a0f..753c3cce04e5 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 create(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 42a4c4928c56b3a60ba1039c3a302f33f4940b94 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 12 Nov 2017 17:41:59 +0600 Subject: [PATCH 021/135] 2_6 annotation processing --- .../ru/javawebinar/topjava/SpringMain.java | 20 ++++++---------- .../mock/MockUserRepositoryImpl.java | 2 ++ .../topjava/service/UserServiceImpl.java | 6 +++++ .../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, 39 insertions(+), 18 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 2da5cacd3228..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.create(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 0825c7b69afa..3825d9a48654 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 753c3cce04e5..109a60fb7f36 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -9,13 +11,17 @@ 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 create(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 16a68259ec20..0000f1c1e02f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; @@ -13,6 +14,7 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); + @Autowired private UserService service; public List getAll() { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index ae3374468e41..b37a8ed6c8a5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,9 +1,11 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.model.User; import java.util.List; +@Controller public class AdminRestController extends AbstractUserController { @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index 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 3e30610a098b301da8a28b828751766e6ce1024f Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 12 Nov 2017 17:42:44 +0600 Subject: [PATCH 022/135] 2_7 constructor injection --- .../ru/javawebinar/topjava/service/UserServiceImpl.java | 8 +++----- src/main/resources/spring/spring-app.xml | 8 ++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 109a60fb7f36..233d71e07f50 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -14,14 +14,12 @@ @Service public class UserServiceImpl implements UserService { - @Autowired - private UserRepository repository; + private final UserRepository repository; -/* - public void setRepository(UserRepository repository) { + @Autowired + public UserServiceImpl(UserRepository repository) { this.repository = repository; } -*/ @Override public User create(User user) { diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 96e40e902b66..538554fb7cbd 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -5,11 +5,11 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> From f93e84e6a260aef5d1a39b929434df1c7836bf03 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 11:03:42 +0700 Subject: [PATCH 023/135] 3_0 fix --- src/main/resources/spring/spring-app.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 538554fb7cbd..306726024f3c 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -12,7 +12,8 @@ --> - + + From 3c3e921ea82026513e1b647ce4b34f1b98e3ab52 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 11:04:10 +0700 Subject: [PATCH 024/135] 3_0 fix --- .../topjava/repository/mock/InMemoryMealRepositoryImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 9eecc3b4c7cd..21caea61c151 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -21,9 +21,11 @@ public class InMemoryMealRepositoryImpl implements MealRepository { public Meal save(Meal meal) { if (meal.isNew()) { meal.setId(counter.incrementAndGet()); + repository.put(meal.getId(), meal); + return meal; } - repository.put(meal.getId(), meal); - return meal; + // treat case: update, but absent in storage + return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); } @Override From fed2e5a9dbb3ee190def6d48a8586f222e52b700 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 11:04:21 +0700 Subject: [PATCH 025/135] 3_0 fix --- src/main/java/ru/javawebinar/topjava/web/MealServlet.java | 4 ++-- src/main/webapp/mealForm.jsp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 0dca99ceb244..dc509a1061d6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -36,7 +36,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), LocalDateTime.parse(request.getParameter("dateTime")), request.getParameter("description"), - Integer.valueOf(request.getParameter("calories"))); + Integer.parseInt(request.getParameter("calories"))); log.info(meal.isNew() ? "Create {}" : "Update {}", meal); repository.save(meal); @@ -74,6 +74,6 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t private int getId(HttpServletRequest request) { String paramId = Objects.requireNonNull(request.getParameter("id")); - return Integer.valueOf(paramId); + return Integer.parseInt(paramId); } } diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp index 3fa1f71e66c1..ddc71d3c0a7a 100644 --- a/src/main/webapp/mealForm.jsp +++ b/src/main/webapp/mealForm.jsp @@ -33,15 +33,15 @@
    DateTime:
    -
    +
    Description:
    -
    +
    Calories:
    -
    +
    From 8be1c1740f31f564d12a80dca8a62ec657ae3d4e Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 11:20:00 +0700 Subject: [PATCH 026/135] 3_01_HW2_repository --- .../topjava/repository/MealRepository.java | 18 ++++-- .../mock/InMemoryMealRepositoryImpl.java | 59 ++++++++++++++----- .../mock/InMemoryUserRepositoryImpl.java | 57 ++++++++++++++++++ .../mock/MockUserRepositoryImpl.java | 45 -------------- .../topjava/util/DateTimeUtil.java | 5 +- .../javawebinar/topjava/web/MealServlet.java | 9 +-- 6 files changed, 122 insertions(+), 71 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 e249a885c8bf..f0578ff40457 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -2,14 +2,22 @@ import ru.javawebinar.topjava.model.Meal; -import java.util.Collection; +import java.time.LocalDateTime; +import java.util.List; 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 desc + List getAll(int userId); + + // ORDERED dateTime desc + 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 21caea61c151..228fb9220525 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -2,45 +2,76 @@ 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.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; public class InMemoryMealRepositoryImpl implements MealRepository { - private Map repository = new ConcurrentHashMap<>(); + + // Map userId -> (mealId-> meal) + private Map> repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); { - MealsUtil.MEALS.forEach(this::save); + MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); + + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); } + @Override - public Meal save(Meal meal) { + public Meal save(Meal meal, int userId) { + Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); if (meal.isNew()) { meal.setId(counter.incrementAndGet()); - repository.put(meal.getId(), meal); + meals.put(meal.getId(), meal); return meal; } - // treat case: update, but absent in storage - return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> 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) { + return getAllAsStream(userId) + .filter(meal -> DateTimeUtil.isBetween(meal.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(Comparator.comparing(Meal::getDateTime).reversed()); + } +} \ No newline at end of file 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..d413d56e2d59 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -0,0 +1,57 @@ +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.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Repository +public class InMemoryUserRepositoryImpl implements UserRepository { + + 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) { + if (user.isNew()) { + user.setId(counter.incrementAndGet()); + repository.put(user.getId(), user); + return user; + } + return repository.computeIfPresent(user.getId(), (id, oldUser) -> 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(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String 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 3825d9a48654..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 dc509a1061d6..2a004e35586e 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.parseInt(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("/mealForm.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 8583b524c0c6bf0826422f6765db9c69b1d5b62f Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 11:41:45 +0700 Subject: [PATCH 027/135] 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 | 2 +- .../topjava/web/meal/MealRestController.java | 78 ++++++++++++++++++- src/main/webapp/meals.jsp | 2 +- 10 files changed, 169 insertions(+), 21 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 3abbee42511e..9eed15f706be 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -4,9 +4,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; -public class Meal { - private Integer id; - +public class Meal extends AbstractBaseEntity { private final LocalDateTime dateTime; private final String description; @@ -18,20 +16,12 @@ public Meal(LocalDateTime dateTime, String description, int calories) { } public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { - this.id = id; + super(id); this.dateTime = dateTime; this.description = description; this.calories = calories; } - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - public LocalDateTime getDateTime() { return dateTime; } @@ -52,10 +42,6 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } - public boolean isNew() { - return id == null; - } - @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 228fb9220525..aa00e554fb7e 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; @@ -18,6 +19,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 { // Map userId -> (mealId-> meal) diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index b63fc9b1df00..3f956057ea19 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 create(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..793bfe63dfd7 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 create(Meal meal, int userId) { + return repository.save(meal, userId); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/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 2b375e45eecc..c8ab125a80a4 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 81e76b78b188..04ca79d1b260 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.util; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.model.MealWithExceed; +import ru.javawebinar.topjava.to.MealWithExceed; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index ab4e8ea8bb8e..79f8ebdc4b46 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,84 @@ 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.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +@Controller public class MealRestController { - private MealService service; + private static final Logger log = LoggerFactory.getLogger(MealRestController.class); + + private final MealService service; + + @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.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = AuthorizedUser.id(); + assureIdConsistent(meal, id); + log.info("update {} for user {}", meal, userId); + service.update(meal, userId); + } + + /** + *
      Filter separately + *
    1. by date
    2. + *
    3. by time for every date
    4. + *
    + */ + 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); + + List mealsDateFiltered = service.getBetweenDates( + startDate != null ? startDate : DateTimeUtil.MIN_DATE, + endDate != null ? endDate : DateTimeUtil.MAX_DATE, userId); + return MealsUtil.getFilteredWithExceeded(mealsDateFiltered, + 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 be21a45af406..82978137b3bb 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -32,7 +32,7 @@ - + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> From b976f860ae8a464da0c98ac7f9ca49ae7754077d Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 11:44:55 +0700 Subject: [PATCH 028/135] 3_03_HW2_optional_MealServlet --- .../javawebinar/topjava/web/MealServlet.java | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 2a004e35586e..b8d0865eb19f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,12 +1,9 @@ package ru.javawebinar.topjava.web; -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; @@ -19,28 +16,36 @@ import java.util.Objects; 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 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), + Meal meal = new Meal( LocalDateTime.parse(request.getParameter("dateTime")), request.getParameter("description"), Integer.parseInt(request.getParameter("calories"))); - log.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal, AuthorizedUser.id()); + if (request.getParameter("id").isEmpty()) { + mealController.create(meal); + } else { + mealController.update(meal, getId(request)); + } response.sendRedirect("meals"); } @@ -51,23 +56,20 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t switch (action == null ? "all" : action) { 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("/mealForm.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 82d8894b98cd22522d705a1d963e00709af02319 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 11:57:12 +0700 Subject: [PATCH 029/135] 3_04_HW2_optional_filter --- .../topjava/util/DateTimeUtil.java | 11 ++++++ .../javawebinar/topjava/web/MealServlet.java | 34 ++++++++++++++----- src/main/webapp/css/style.css | 24 +++++++++++++ src/main/webapp/mealForm.jsp | 19 +---------- src/main/webapp/meals.jsp | 30 +++++++++++----- 5 files changed, 82 insertions(+), 36 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 b8d0865eb19f..1392da7ab89d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -11,10 +11,15 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.Objects; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; + public class MealServlet extends HttpServlet { private ConfigurableApplicationContext springContext; @@ -36,17 +41,28 @@ public void destroy() { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); - Meal meal = new Meal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.parseInt(request.getParameter("calories"))); + String action = request.getParameter("action"); + if (action == null) { + Meal meal = new Meal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); + + if (request.getParameter("id").isEmpty()) { + mealController.create(meal); + } else { + mealController.update(meal, getId(request)); + } + response.sendRedirect("meals"); - if (request.getParameter("id").isEmpty()) { - mealController.create(meal); - } else { - mealController.update(meal, getId(request)); + } else if ("filter".equals(action)) { + LocalDate startDate = parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = parseLocalTime(request.getParameter("endTime")); + request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); } - 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/mealForm.jsp b/src/main/webapp/mealForm.jsp index ddc71d3c0a7a..d4509bb3417a 100644 --- a/src/main/webapp/mealForm.jsp +++ b/src/main/webapp/mealForm.jsp @@ -4,24 +4,7 @@ Meal - +
    diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 82978137b3bb..269cc4e964a9 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -2,23 +2,35 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> -<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> Meal list - +

    Home

    Meals

    +
    +
    +
    From Date:
    +
    +
    +
    +
    To Date:
    +
    +
    +
    +
    From Time:
    +
    +
    +
    +
    To Time:
    +
    +
    + +
    +
    Add Meal
    From 66ebfcdb9a201ccd918eb8e18e43b288e51b5c92 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 12:00:34 +0700 Subject: [PATCH 030/135] 3_05_HW2_optional_select_user --- .../java/ru/javawebinar/topjava/AuthorizedUser.java | 7 ++++++- .../java/ru/javawebinar/topjava/web/UserServlet.java | 8 ++++++++ src/main/webapp/index.html | 12 ++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index 177a05931540..b03cb55e77e7 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 { + private 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 f6cf12e69976..79e55bbc6aa5 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; @@ -13,6 +14,13 @@ public class UserServlet extends HttpServlet { private static final Logger log = getLogger(UserServlet.class); + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + int userId = Integer.parseInt(request.getParameter("userId")); + 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 cd88b335a454..e68aaa192cd3 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -6,9 +6,13 @@

    Проект Java Enterprise (Topjava)


    - +
    + Meals of  + + + From cd38dc55041cabf7a63264f80de052256e0d51b0 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 16:19:46 +0700 Subject: [PATCH 031/135] 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 aa00e554fb7e..150a7b430cbb 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; @@ -21,6 +25,7 @@ @Repository public class InMemoryMealRepositoryImpl implements MealRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); // Map userId -> (mealId-> meal) private Map> repository = new ConcurrentHashMap<>(); @@ -45,6 +50,16 @@ public Meal save(Meal meal, int userId) { return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> 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 816437c60f2f1cbb028c62daa91ca0e622aea60f Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 16:44:55 +0700 Subject: [PATCH 032/135] 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 | 15 ++++-- .../web/InMemoryAdminRestControllerTest.java | 52 +++++++++++++++++++ 6 files changed, 96 insertions(+), 6 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 (79%) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index 537022542c3b..9e1ca18233e9 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ 1.2.3 1.7.25 + + + 4.12 @@ -36,6 +39,14 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.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 150a7b430cbb..90b92c27ad1b 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -20,8 +20,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 79% 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 d413d56e2d59..c5aa4b098217 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.repository.mock; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; @@ -11,14 +12,20 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import static ru.javawebinar.topjava.UserTestData.ADMIN; +import static ru.javawebinar.topjava.UserTestData.USER; + @Repository public class InMemoryUserRepositoryImpl implements UserRepository { - 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); + private AtomicInteger counter = new AtomicInteger(100); + + public void init() { + repository.clear(); + repository.put(UserTestData.USER_ID, USER); + repository.put(UserTestData.ADMIN_ID, ADMIN); + } @Override public User save(User user) { diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java new file mode 100644 index 000000000000..aaccf3c64f05 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -0,0 +1,52 @@ +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.mock.InMemoryUserRepositoryImpl; +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; + +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 + InMemoryUserRepositoryImpl repository = appCtx.getBean(InMemoryUserRepositoryImpl.class); + repository.init(); + } + + @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 11a3d4f848e8b2eb76b326b2149998cb262fd83f Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 16 Nov 2017 16:50:18 +0700 Subject: [PATCH 033/135] 3_08_add_spring_test --- pom.xml | 7 +++ ...InMemoryAdminRestControllerSpringTest.java | 47 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java diff --git a/pom.xml b/pom.xml index 9e1ca18233e9..068402062c11 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..9e981439f044 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -0,0 +1,47 @@ +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.mock.InMemoryUserRepositoryImpl; +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; + +@ContextConfiguration("classpath:spring/spring-app.xml") +@RunWith(SpringRunner.class) +public class InMemoryAdminRestControllerSpringTest { + + @Autowired + private AdminRestController controller; + + @Autowired + private InMemoryUserRepositoryImpl repository; + + @Before + public void setUp() throws Exception { + repository.init(); + } + + @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 78265fd5fced327d8c48107b51598cc0996098be Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 18 Nov 2017 11:23:34 +0600 Subject: [PATCH 034/135] 3_09 add postgresql --- pom.xml | 11 +++++++++++ src/main/resources/db/postgres.properties | 7 +++++++ 2 files changed, 18 insertions(+) create mode 100644 src/main/resources/db/postgres.properties diff --git a/pom.xml b/pom.xml index 068402062c11..fd3008a8d139 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,8 @@ 1.2.3 1.7.25 + + 4.12 @@ -86,6 +88,15 @@ + + + + org.postgresql + postgresql + 42.1.4 + + + 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 3679ee4df1fa86f9e353c63b4601bdf4f72d77d5 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 18 Nov 2017 11:34:12 +0600 Subject: [PATCH 035/135] 3_09 add postgresql --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fd3008a8d139..b411afa69382 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.7.25 - + 42.1.4 4.12 @@ -93,7 +93,7 @@ org.postgresql postgresql - 42.1.4 + ${postgresql.version} From 9cb51c6ee5557930c812a6869b0aa1a06786be6b Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 18 Nov 2017 13:00:46 +0600 Subject: [PATCH 036/135] 3_10 db implementation --- pom.xml | 7 +- .../jdbc/JdbcUserRepositoryImpl.java | 82 +++++++++++++++++++ src/main/resources/db/initDB.sql | 25 ++++++ src/main/resources/db/populateDB.sql | 11 +++ src/main/resources/spring/spring-db.xml | 25 ++++++ 5 files changed, 149 insertions(+), 1 deletion(-) 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 b411afa69382..0d406408521f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.7.25 - 42.1.4 + 42.1.3 4.12 @@ -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..fd40c64f5121 --- /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() NOT NULL, + enabled BOOL DEFAULT TRUE NOT NULL, + calories_per_day INTEGER DEFAULT 2000 NOT NULL +); +CREATE UNIQUE INDEX users_unique_email_idx ON users (email); + +CREATE TABLE user_roles +( + user_id INTEGER NOT NULL, + role VARCHAR, + CONSTRAINT user_roles_idx UNIQUE (user_id, role), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql new file mode 100644 index 000000000000..ccf86e060a68 --- /dev/null +++ b/src/main/resources/db/populateDB.sql @@ -0,0 +1,11 @@ +DELETE FROM user_roles; +DELETE FROM users; +ALTER SEQUENCE global_seq RESTART WITH 100000; + +INSERT INTO users (name, email, password) VALUES + ('User', 'user@yandex.ru', 'password'), + ('Admin', 'admin@gmail.com', 'admin'); + +INSERT INTO user_roles (role, user_id) VALUES + ('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..30a3733ba72d --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 1b7f195358770eab463d33c99bf2077c5b71f689 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 18 Nov 2017 15:48:48 +0600 Subject: [PATCH 037/135] 3_11 test UserService --- pom.xml | 9 +- .../topjava/model/AbstractBaseEntity.java | 23 +++++ .../topjava/model/AbstractNamedEntity.java | 3 + .../ru/javawebinar/topjava/model/User.java | 7 ++ .../jdbc/JdbcMealRepositoryImpl.java | 37 ++++++++ src/main/resources/spring/spring-app.xml | 2 +- .../ru/javawebinar/topjava/UserTestData.java | 21 ++++- .../topjava/service/UserServiceTest.java | 86 +++++++++++++++++++ 8 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java diff --git a/pom.xml b/pom.xml index 0d406408521f..e107847434b6 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.7.25 - 42.1.3 + 42.1.4 4.12 @@ -94,7 +94,6 @@ - org.postgresql postgresql @@ -129,6 +128,12 @@ ${spring.version} test + + org.assertj + assertj-core + 3.8.0 + test + diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index a3d71fcb46ed..5cd722231795 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,8 +1,13 @@ package ru.javawebinar.topjava.model; public abstract class AbstractBaseEntity { + public static final int START_SEQ = 100000; + protected Integer id; + public AbstractBaseEntity() { + } + protected AbstractBaseEntity(Integer id) { this.id = id; } @@ -23,4 +28,22 @@ public boolean isNew() { public String toString() { return String.format("Entity %s (%s)", getClass().getName(), id); } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractBaseEntity that = (AbstractBaseEntity) o; + return id != null && id.equals(that.id); + } + + @Override + public int hashCode() { + return id == null ? 0 : id; + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index 259511dd0b65..0e07e37b6ef1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -4,6 +4,9 @@ public abstract class AbstractNamedEntity extends AbstractBaseEntity { protected String name; + public AbstractNamedEntity() { + } + protected AbstractNamedEntity(Integer id, String name) { super(id); this.name = name; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index d88e381945d8..34c02d46160c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -20,6 +20,13 @@ public class User extends AbstractNamedEntity { private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + public User() { + } + + public User(User u) { + this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.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/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 306726024f3c..5ae45a114f8d 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -15,7 +15,7 @@ - + diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 196b09362719..3cc466c53754 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -3,10 +3,27 @@ import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + public class UserTestData { - public static final int USER_ID = 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 void assertMatch(User actual, User expected) { + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles"); + } + + public static void assertMatch(Iterable actual, User... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); + } } 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..fd2308e0fb3b --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -0,0 +1,86 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.Collections; +import java.util.List; + +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")) +public class UserServiceTest { + + @Autowired + private UserService service; + + @Test + public void create() throws Exception { + User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); + User created = service.create(newUser); + newUser.setId(created.getId()); + assertMatch(service.getAll(), ADMIN, newUser, USER); + } + + @Test(expected = DataAccessException.class) + public void duplicateMailCreate() throws Exception { + service.create(new User(null, "Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER)); + } + + @Test + public void delete() throws Exception { + service.delete(USER_ID); + assertMatch(service.getAll(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void notFoundDelete() throws Exception { + service.delete(1); + } + + @Test + public void get() throws Exception { + User user = service.get(USER_ID); + assertMatch(user, USER); + } + + @Test(expected = NotFoundException.class) + public void getNotFound() throws Exception { + service.get(1); + } + + @Test + public void getByEmail() throws Exception { + User user = service.getByEmail("user@yandex.ru"); + assertMatch(user, USER); + } + + @Test + public void update() throws Exception { + User updated = new User(USER); + updated.setName("UpdatedName"); + updated.setCaloriesPerDay(330); + service.update(updated); + assertMatch(service.get(USER_ID), updated); + } + + @Test + public void getAll() throws Exception { + List all = service.getAll(); + assertMatch(all, ADMIN, USER); + } +} \ No newline at end of file From dab6653823bc6f11bd0a554078ebef6799cfadaf Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 18 Nov 2017 15:49:49 +0600 Subject: [PATCH 038/135] 3_12 test logging --- pom.xml | 7 ++++++ src/main/resources/logback.xml | 4 ++-- .../topjava/service/UserServiceTest.java | 7 ++++++ src/test/resources/logback-test.xml | 22 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/logback-test.xml diff --git a/pom.xml b/pom.xml index e107847434b6..964adc187146 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/main/resources/logback.xml b/src/main/resources/logback.xml index e9b900b26669..c7bffc3a958c 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -9,14 +9,14 @@ UTF-8 - %date %-5level %logger{0} [%file:%line] %msg%n + %date %-5level %logger{50}.%M:%L - %msg%n UTF-8 - %-5level %logger{0} [%file:%line] %msg%n + %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index fd2308e0fb3b..d193e95c6d24 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -2,6 +2,7 @@ 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,12 @@ @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class UserServiceTest { + static { + // Only for postgres driver logging + // It uses java.util.logging and logged via jul-to-slf4j bridge + SLF4JBridgeHandler.install(); + } + @Autowired private UserService service; diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 000000000000..63a3f3019bbe --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + true + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + + + + + + + + + + + + \ No newline at end of file From 564ce32434878f3e4918b5dee7cfeb86f6478ebb Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 18 Nov 2017 15:50:28 +0600 Subject: [PATCH 039/135] 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 b03cb55e77e7..6625a2a0dfaa 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.AbstractBaseEntity; + import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; public class AuthorizedUser { - private static int id = 1; + private static int id = AbstractBaseEntity.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 1392da7ab89d..6e936f48a34e 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 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 e68aaa192cd3..886449733a86 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -9,8 +9,8 @@

    Проект Meals of  From bbbcfd9d2ba966b859b4d33b719b585e4bf68cc4 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 23 Nov 2017 14:49:48 +0700 Subject: [PATCH 040/135] 4_0 fix --- .../jdbc/JdbcUserRepositoryImpl.java | 8 ++++---- .../topjava/service/UserServiceImpl.java | 2 +- .../mock/InMemoryMealRepositoryImpl.java | 19 ++++++++++--------- 3 files changed, 15 insertions(+), 14 deletions(-) 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..8e7d32a087e9 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -49,10 +49,10 @@ public User save(User user) { 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); + } else if (namedParameterJdbcTemplate.update( + "UPDATE users SET name=:name, email=:email, password=:password, " + + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map) == 0) { + return null; } return user; } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 233d71e07f50..c651c41d3fb9 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -48,6 +48,6 @@ public List getAll() { @Override public void update(User user) { - repository.save(user); + checkNotFoundWithId(repository.save(user), user.getId()); } } \ No newline at end of file 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 90b92c27ad1b..bedb7e5d4756 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -3,6 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; +import org.springframework.util.CollectionUtils; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.DateTimeUtil; @@ -12,13 +13,14 @@ import javax.annotation.PreDestroy; import java.time.LocalDateTime; import java.time.Month; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; 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; @@ -74,21 +76,20 @@ public Meal get(int id, int userId) { @Override public List getAll(int userId) { - return getAllAsStream(userId).collect(Collectors.toList()); + return getAllFiltered(userId, meal -> true); } @Override public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { - return getAllAsStream(userId) - .filter(meal -> DateTimeUtil.isBetween(meal.getDateTime(), startDateTime, endDateTime)) - .collect(Collectors.toList()); + return getAllFiltered(userId, meal -> DateTimeUtil.isBetween(meal.getDateTime(), startDateTime, endDateTime)); } - private Stream getAllAsStream(int userId) { + private List getAllFiltered(int userId, Predicate filter) { Map meals = repository.get(userId); - return meals == null ? - Stream.empty() : + return CollectionUtils.isEmpty(meals) ? Collections.emptyList() : meals.values().stream() - .sorted(Comparator.comparing(Meal::getDateTime).reversed()); + .filter(filter) + .sorted(Comparator.comparing(Meal::getDateTime).reversed()) + .collect(Collectors.toList()); } } \ No newline at end of file From 6d143a21dfbeb4272b75956c015fa1b1a56b544a Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 23 Nov 2017 15:09:37 +0700 Subject: [PATCH 041/135] 4_1 HW3 --- .../ru/javawebinar/topjava/model/Meal.java | 21 ++++++- .../jdbc/JdbcMealRepositoryImpl.java | 61 +++++++++++++++++-- .../jdbc/JdbcUserRepositoryImpl.java | 15 ++--- src/main/resources/db/initDB.sql | 13 +++- src/main/resources/db/populateDB.sql | 11 ++++ 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 9eed15f706be..1c1e67b15a3b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,11 +5,14 @@ import java.time.LocalTime; public class Meal extends AbstractBaseEntity { - private final LocalDateTime dateTime; + private LocalDateTime dateTime; - private final String description; + private String description; - private final int calories; + private int calories; + + public Meal() { + } public Meal(LocalDateTime dateTime, String description, int calories) { this(null, dateTime, description, calories); @@ -42,6 +45,18 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } + public void setDateTime(LocalDateTime dateTime) { + this.dateTime = dateTime; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setCalories(int calories) { + this.calories = calories; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/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 8e7d32a087e9..0a802046b891 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,21 +37,14 @@ 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 if (namedParameterJdbcTemplate.update( "UPDATE users SET name=:name, email=:email, password=:password, " + - "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map) == 0) { + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { return null; } return user; diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index fd40c64f5121..3b7d07d4b790 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; @@ -22,4 +23,14 @@ CREATE TABLE user_roles role VARCHAR, CONSTRAINT user_roles_idx UNIQUE (user_id, role), FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE -); \ No newline at end of file +); + +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 ccf86e060a68..8ff6e60e0e70 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; @@ -9,3 +10,13 @@ INSERT INTO users (name, email, password) VALUES 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 9167bbfacc2c39926e690e6954199d0c7c5460a8 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 23 Nov 2017 15:14:29 +0700 Subject: [PATCH 042/135] 4_2 HW3_optional --- .../javawebinar/topjava/util/MealsUtil.java | 11 --- .../ru/javawebinar/topjava/MealTestData.java | 47 ++++++++++ .../mock/InMemoryMealRepositoryImpl.java | 13 --- .../topjava/service/MealServiceTest.java | 88 +++++++++++++++++++ 4 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/MealTestData.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 04ca79d1b260..2ae063ec26b5 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -4,10 +4,7 @@ import ru.javawebinar.topjava.to.MealWithExceed; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.Month; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -16,14 +13,6 @@ import static java.util.stream.Collectors.toList; 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; 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..d94faebff1c4 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava; + +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 org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class MealTestData { + public static final int MEAL1_ID = START_SEQ + 2; + public static final int ADMIN_MEAL_ID = START_SEQ + 8; + + public static final 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); + } + + public static void assertMatch(Meal actual, Meal expected) { + assertThat(actual).isEqualToComparingFieldByField(expected); + } + + public static void assertMatch(Iterable actual, Meal... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingFieldByFieldElementComparator().isEqualTo(expected); + } +} 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 bedb7e5d4756..c0093d943a59 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -7,12 +7,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.Collections; import java.util.Comparator; import java.util.List; @@ -22,9 +20,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -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); @@ -33,14 +28,6 @@ public class InMemoryMealRepositoryImpl implements MealRepository { private Map> repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); - { - MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); - - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); - } - - @Override public Meal save(Meal meal, int userId) { Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); 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..ffd33b347e3e --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -0,0 +1,88 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.Month; + +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.USER_ID; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +public class MealServiceTest { + + static { + SLF4JBridgeHandler.install(); + } + + @Autowired + private MealService service; + + @Test + public void testDelete() throws Exception { + service.delete(MEAL1_ID, USER_ID); + assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); + } + + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { + service.delete(MEAL1_ID, 1); + } + + @Test + public void testSave() throws Exception { + Meal created = getCreated(); + service.create(created, USER_ID); + assertMatch(service.getAll(USER_ID), created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + } + + @Test + public void testGet() throws Exception { + Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); + assertMatch(actual, ADMIN_MEAL1); + } + + @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); + assertMatch(service.get(MEAL1_ID, USER_ID), updated); + } + + @Test(expected = NotFoundException.class) + public void testUpdateNotFound() throws Exception { + service.update(MEAL1, ADMIN_ID); + } + + @Test + public void testGetAll() throws Exception { + assertMatch(service.getAll(USER_ID), MEALS); + } + + @Test + public void testGetBetween() throws Exception { + assertMatch(service.getBetweenDates( + LocalDate.of(2015, Month.MAY, 30), + LocalDate.of(2015, Month.MAY, 30), USER_ID), MEAL3, MEAL2, MEAL1); + } +} \ No newline at end of file From 5cdd2f17f96057f81e78c7ae3a6f8d7096709b06 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 23 Nov 2017 15:57:19 +0700 Subject: [PATCH 043/135] 4_3 improve_code --- .travis.yml | 22 +++++++++++++++++++ pom.xml | 4 ++-- .../topjava/service/MealServiceImpl.java | 4 ++++ .../topjava/service/UserServiceImpl.java | 4 ++++ .../topjava/util/DateTimeUtil.java | 3 +++ .../javawebinar/topjava/util/MealsUtil.java | 3 +++ .../topjava/util/ValidationUtil.java | 3 +++ .../mock/InMemoryMealRepositoryImpl.java | 8 +++---- 8 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..65c75d2dd58f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +# 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/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 964adc187146..d73e16851a76 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ UTF-8 UTF-8 - 4.3.12.RELEASE + 5.0.1.RELEASE 1.2.3 @@ -112,7 +112,7 @@ javax.servlet javax.servlet-api - 3.1.0 + 4.0.0 provided diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 793bfe63dfd7..406d50175e0f 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -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); } @@ -47,6 +50,7 @@ public Meal update(Meal meal, int userId) { @Override public Meal create(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index c651c41d3fb9..a3f090177a3a 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 create(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"); checkNotFoundWithId(repository.save(user), user.getId()); } } \ 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/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 2ae063ec26b5..b7c9a769f776 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -16,6 +16,9 @@ public class MealsUtil { public static final int DEFAULT_CALORIES_PER_DAY = 2000; + private MealsUtil() { + } + public static List getWithExceeded(Collection meals, int caloriesPerDay) { return getFilteredWithExceeded(meals, LocalTime.MIN, LocalTime.MAX, caloriesPerDay); } diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 2914ed684410..78da8c6d7abe 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -6,6 +6,9 @@ public class ValidationUtil { + private ValidationUtil() { + } + public static void checkNotFoundWithId(boolean found, int id) { checkNotFound(found, "id=" + id); } 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 c0093d943a59..cc601e22ccc4 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -11,10 +11,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; @@ -30,6 +27,7 @@ public class InMemoryMealRepositoryImpl implements MealRepository { @Override public Meal save(Meal meal, int userId) { + Objects.requireNonNull(meal); Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); if (meal.isNew()) { meal.setId(counter.incrementAndGet()); @@ -68,6 +66,8 @@ public List getAll(int userId) { @Override public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Objects.requireNonNull(startDateTime); + Objects.requireNonNull(endDateTime); return getAllFiltered(userId, meal -> DateTimeUtil.isBetween(meal.getDateTime(), startDateTime, endDateTime)); } From 179963e92dfb9bc6cce5bc9e4c5faf4681e334d1 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 23 Nov 2017 16:35:54 +0700 Subject: [PATCH 044/135] 4_4 init_and populate_db --- src/main/resources/db/postgres.properties | 2 ++ src/main/resources/spring/spring-db.xml | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) 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 30a3733ba72d..22ad27239647 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,11 +1,18 @@ + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> + + + + + From 87fe3175128f3ff70779281ddef07e4e0d103244 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 26 Nov 2017 15:05:12 +0600 Subject: [PATCH 045/135] 4_5 create_mock_test_ctx --- src/main/resources/spring/spring-app.xml | 2 -- src/main/resources/spring/spring-db.xml | 2 ++ src/test/java/ru/javawebinar/topjava/SpringMain.java | 2 +- .../topjava/web/InMemoryAdminRestControllerSpringTest.java | 2 +- .../topjava/web/InMemoryAdminRestControllerTest.java | 2 +- src/test/resources/spring/mock.xml | 7 +++++++ 6 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/spring/mock.xml diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 5ae45a114f8d..4b239a370a1b 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -15,8 +15,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/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)); diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java index 9e981439f044..98e5d8a9278c 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -17,7 +17,7 @@ import static ru.javawebinar.topjava.UserTestData.ADMIN; -@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 aaccf3c64f05..3bf1c351c8e7 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -20,7 +20,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 58140e67e4474fd29f627607c710ee1da9948d25 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 26 Nov 2017 16:47:00 +0600 Subject: [PATCH 046/135] 4_6_add_jpa --- pom.xml | 27 +++++++- .../topjava/model/AbstractBaseEntity.java | 11 +++- .../topjava/model/AbstractNamedEntity.java | 10 +++ .../ru/javawebinar/topjava/model/Meal.java | 13 ++++ .../ru/javawebinar/topjava/model/User.java | 30 ++++++++- .../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, 215 insertions(+), 6 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 d73e16851a76..b10728c86b1f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,11 @@ 42.1.4 4.12 + + + 5.2.12.Final + 6.0.5.Final + 3.0.1-b08 @@ -96,7 +101,7 @@ org.springframework - spring-jdbc + spring-orm ${spring.version} @@ -107,6 +112,26 @@ ${postgresql.version} + + + org.hibernate + hibernate-core + ${hibernate.version} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + + + org.glassfish + javax.el + ${javax-el.version} + provided + + diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 5cd722231795..4d5525b0e04e 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,11 +1,20 @@ package ru.javawebinar.topjava.model; +import javax.persistence.*; + +@MappedSuperclass + +// http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access +@Access(AccessType.FIELD) public abstract class AbstractBaseEntity { public static final int START_SEQ = 100000; + @Id + @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") protected Integer id; - public AbstractBaseEntity() { + protected AbstractBaseEntity() { } protected AbstractBaseEntity(Integer id) { diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index 0e07e37b6ef1..be41aa890966 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -1,7 +1,17 @@ package ru.javawebinar.topjava.model; +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + + +@MappedSuperclass public abstract class AbstractNamedEntity extends AbstractBaseEntity { + @NotBlank + @Size(min = 2, max = 100) + @Column(name = "name", nullable = false) protected String name; public AbstractNamedEntity() { diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 1c1e67b15a3b..788ae8f6fe5b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -11,6 +13,9 @@ public class Meal extends AbstractBaseEntity { private int calories; + @ManyToOne(fetch = FetchType.LAZY) + private User user; + public Meal() { } @@ -57,6 +62,14 @@ public void setCalories(int calories) { this.calories = calories; } + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 34c02d46160c..9a9862d181ad 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,23 +1,49 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.util.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 AbstractNamedEntity { + @Column(name = "email", nullable = false, unique = true) + @Email + @NotBlank + @Size(max = 100) private String email; + @Column(name = "password", nullable = false) + @NotBlank + @Size(min = 5, max = 64) private String password; + @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") + @NotNull private boolean enabled = true; + @Column(name = "registered", columnDefinition = "timestamp default now()") + @NotNull 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 +112,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 ec4356b4d5abf091ab559d4bc9d255857737e07b Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 26 Nov 2017 16:49:29 +0600 Subject: [PATCH 047/135] 4_7_add_named_query_and_transaction --- .../ru/javawebinar/topjava/model/User.java | 9 +++++++++ .../repository/jpa/JpaUserRepositoryImpl.java | 19 +++++++++++++++---- src/main/resources/spring/spring-db.xml | 10 +++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 9a9862d181ad..93d5e6f3946f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -13,10 +13,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 AbstractNamedEntity { + public static final String DELETE = "User.delete"; + public static final String BY_EMAIL = "User.getByEmail"; + public static final String ALL_SORTED = "User.getAllSorted"; + @Column(name = "email", nullable = false, unique = true) @Email @NotBlank 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..b82dbd73342f 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,30 @@ 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"); 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 @@ + + + + + diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties new file mode 100644 index 000000000000..4b09e70bb431 --- /dev/null +++ b/src/main/resources/db/hsqldb.properties @@ -0,0 +1,13 @@ +database.url=jdbc:hsqldb:file:D:/temp/topjava +#database.url=jdbc:hsqldb:mem:topjava + +database.username=sa +database.password= +database.driverClassName=org.hsqldb.jdbcDriver + +database.init=true + +jdbc.initLocation=initDB_hsql.sql +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/db/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql new file mode 100644 index 000000000000..22599e5d31a5 --- /dev/null +++ b/src/main/resources/db/initDB_hsql.sql @@ -0,0 +1,37 @@ +DROP TABLE user_roles IF EXISTS; +DROP TABLE meals IF EXISTS; +DROP TABLE users IF EXISTS; +DROP SEQUENCE global_seq IF EXISTS; + +CREATE SEQUENCE GLOBAL_SEQ AS INTEGER START WITH 100000; + +CREATE TABLE users +( + id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, + registered TIMESTAMP DEFAULT now() NOT NULL, + enabled BOOLEAN DEFAULT TRUE NOT NULL, + calories_per_day INTEGER DEFAULT 2000 NOT NULL +); +CREATE UNIQUE INDEX users_unique_email_idx ON USERS (email); + +CREATE TABLE user_roles +( + user_id INTEGER NOT NULL, + role VARCHAR(255), + CONSTRAINT user_roles_idx UNIQUE (user_id, role), + FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE +); + +CREATE TABLE meals +( + id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY, + date_time TIMESTAMP NOT NULL, + description VARCHAR(255) NOT NULL, + calories INT NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals (user_id, date_time) \ No newline at end of file diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 0293ef30a3c8..51be31f5f7dd 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,9 +5,11 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password +database.driverClassName=org.postgresql.Driver database.init=true +jdbc.initLocation=initDB.sql jpa.showSql=true hibernate.format_sql=true hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 7e4ca66b02b1..8569a84b55f3 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -9,19 +9,22 @@ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + + + - + - + From 28d7e3e2950c1d439dc2fce946fe46edf020dd25 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 30 Nov 2017 15:58:52 +0700 Subject: [PATCH 049/135] 5_1 HW4 --- .../ru/javawebinar/topjava/model/Meal.java | 32 +++++++++++++++- .../repository/jpa/JpaMealRepositoryImpl.java | 38 ++++++++++++++++--- .../ru/javawebinar/topjava/MealTestData.java | 4 +- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 788ae8f6fe5b..48989e05fb82 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,19 +1,47 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +@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 AbstractBaseEntity { + public static final String ALL_SORTED = "Meal.getAll"; + public static final String DELETE = "Meal.delete"; + public static final String GET_BETWEEN = "Meal.getBetween"; + + @Column(name = "date_time", nullable = false) + @NotNull private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotBlank + @Size(min = 2, max = 120) private String description; + @Column(name = "calories", nullable = false) + @Range(min = 10, max = 5000) private int calories; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + @NotNull private User user; public Meal() { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java index 052d875e8b71..2aeeb6356951 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,65 @@ 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() && 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 diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index d94faebff1c4..ae8242fcd942 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -34,7 +34,7 @@ public static Meal getUpdated() { } public static void assertMatch(Meal actual, Meal expected) { - assertThat(actual).isEqualToComparingFieldByField(expected); + assertThat(actual).isEqualToIgnoringGivenFields(expected, "user"); } public static void assertMatch(Iterable actual, Meal... expected) { @@ -42,6 +42,6 @@ public static void assertMatch(Iterable actual, Meal... expected) { } public static void assertMatch(Iterable actual, Iterable expected) { - assertThat(actual).usingFieldByFieldElementComparator().isEqualTo(expected); + assertThat(actual).usingElementComparatorIgnoringFields("user").isEqualTo(expected); } } From 4cd3334ff4d59c01ab621ef113f2d03c811bfb73 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 30 Nov 2017 16:00:51 +0700 Subject: [PATCH 050/135] 5_2 fix_hibernate_issue --- .../ru/javawebinar/topjava/model/AbstractBaseEntity.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 4d5525b0e04e..384b5725d2d6 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import org.hibernate.Hibernate; + import javax.persistence.*; @MappedSuperclass @@ -12,6 +14,10 @@ public abstract class AbstractBaseEntity { @Id @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") + +// PROPERTY access for id due to bug: https://hibernate.atlassian.net/browse/HHH-3718 +// Fixed at last? +// @Access(value = AccessType.PROPERTY) protected Integer id; protected AbstractBaseEntity() { @@ -44,7 +50,7 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !getClass().equals(Hibernate.getClass(o))) { return false; } AbstractBaseEntity that = (AbstractBaseEntity) o; From 5e022bd91267b7ccb85788374821e49e68054bff Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 30 Nov 2017 16:09:54 +0700 Subject: [PATCH 051/135] 5_3 HW4 optional --- .../topjava/service/MealServiceTest.java | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index ffd33b347e3e..49688d4a6765 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.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.concurrent.TimeUnit; +import static org.slf4j.LoggerFactory.getLogger; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; @@ -22,14 +30,41 @@ "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 = getLogger(MealServiceTest.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("\n%-25s %7d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result); + 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---------------------------------" + + results + + "\n---------------------------------"); + } + @Autowired private MealService service; @@ -39,8 +74,9 @@ public void testDelete() throws Exception { assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); } - @Test(expected = NotFoundException.class) + @Test public void testDeleteNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @@ -57,8 +93,9 @@ public void testGet() throws Exception { assertMatch(actual, ADMIN_MEAL1); } - @Test(expected = NotFoundException.class) + @Test public void testGetNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @@ -69,8 +106,10 @@ public void testUpdate() throws Exception { assertMatch(service.get(MEAL1_ID, USER_ID), updated); } - @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); } From c068222ed0fc3450cd50737e54d3115aa0ea96b7 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 30 Nov 2017 16:17:39 +0700 Subject: [PATCH 052/135] 5_4 log colored --- src/main/resources/logback.xml | 2 +- .../topjava/service/MealServiceTest.java | 2 +- src/test/resources/logback-test.xml | 19 +++++++++++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index c7bffc3a958c..12a4f63c6c15 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -16,7 +16,7 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 49688d4a6765..7516cd6c6f27 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -33,7 +33,7 @@ @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { - private static final Logger log = getLogger(MealServiceTest.class); + private static final Logger log = getLogger("result"); private static StringBuilder results = new StringBuilder(); diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 63a3f3019bbe..019d8a1edcb6 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -7,15 +7,26 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n - - + + + UTF-8 + %magenta(%msg%n) + + + + + + + + + - + From c77da3cc602df4c9414372990cecaeb38e482f25 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 1 Dec 2017 12:57:17 +0700 Subject: [PATCH 053/135] 5_5 profiles connection pool --- pom.xml | 43 +++++++++++++------ .../java/ru/javawebinar/topjava/Profiles.java | 15 +++++++ src/main/resources/db/hsqldb.properties | 1 - src/main/resources/db/initDB.sql | 2 +- 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 ++ 8 files changed, 75 insertions(+), 30 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/Profiles.java diff --git a/pom.xml b/pom.xml index 5783043b4b7c..40830412d880 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ UTF-8 UTF-8 + 8.5.15 5.0.1.RELEASE @@ -105,19 +106,6 @@ ${spring.version} - - - org.postgresql - postgresql - ${postgresql.version} - - - org.hsqldb - hsqldb - 2.3.4 - - - org.hibernate @@ -176,6 +164,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..32b3ff805e6e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -0,0 +1,15 @@ +package ru.javawebinar.topjava; + +public class Profiles { + public static final String + JDBC = "jdbc", + JPA = "jpa"; + + public static final String REPOSITORY_IMPLEMENTATION = JPA; + + public static final String + POSTGRES_DB = "postgres", + HSQL_DB = "hsqldb"; + + public static final String ACTIVE_DB = HSQL_DB; +} diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties index 4b09e70bb431..47d57411244f 100644 --- a/src/main/resources/db/hsqldb.properties +++ b/src/main/resources/db/hsqldb.properties @@ -3,7 +3,6 @@ database.url=jdbc:hsqldb:file:D:/temp/topjava database.username=sa database.password= -database.driverClassName=org.hsqldb.jdbcDriver database.init=true diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index 3b7d07d4b790..c21ef1ddcd2d 100644 --- a/src/main/resources/db/initDB.sql +++ b/src/main/resources/db/initDB.sql @@ -33,4 +33,4 @@ CREATE TABLE meals ( 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 +CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals (user_id, date_time); \ No newline at end of file diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 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 7516cd6c6f27..7b44345742ee 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -10,10 +10,12 @@ import org.slf4j.Logger; 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; @@ -32,6 +34,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 = getLogger("result"); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index d193e95c6d24..0720f123a58c 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; @@ -24,6 +26,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(Profiles.ACTIVE_DB) public class UserServiceTest { static { From 7863d136c92279db1a5234f047ed5e64922df029 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 1 Dec 2017 13:34:46 +0700 Subject: [PATCH 054/135] 5_6 profile resolver --- .../java/ru/javawebinar/topjava/Profiles.java | 15 ++++++++++++++- .../topjava/ActiveDbProfileResolver.java | 12 ++++++++++++ .../topjava/service/MealServiceTest.java | 4 ++-- .../topjava/service/UserServiceTest.java | 4 ++-- 4 files changed, 30 insertions(+), 5 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 32b3ff805e6e..a11b37a256dd 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -11,5 +11,18 @@ public class Profiles { POSTGRES_DB = "postgres", HSQL_DB = "hsqldb"; - public static final String ACTIVE_DB = HSQL_DB; + // 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 find DB driver"); + } + } + } } 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/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 7b44345742ee..36418d0acba6 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -15,7 +15,7 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -34,7 +34,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.ACTIVE_DB) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) public class MealServiceTest { private static final Logger log = getLogger("result"); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 0720f123a58c..c9c32efe847c 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -10,7 +10,7 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -26,7 +26,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.ACTIVE_DB) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) public class UserServiceTest { static { From b73f1d0364cc0be5ce4549e6937ee67fe873aa26 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 1 Dec 2017 15:41:40 +0700 Subject: [PATCH 055/135] 5_7 spring data jpa --- pom.xml | 18 +++++--- .../java/ru/javawebinar/topjava/Profiles.java | 5 ++- .../topjava/model/AbstractBaseEntity.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 ++++++- 8 files changed, 157 insertions(+), 10 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 40830412d880..cbad695ae3db 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 8.5.15 5.0.1.RELEASE + 2.0.2.RELEASE 1.2.3 @@ -92,7 +93,6 @@ org.springframework spring-context - ${spring.version} commons-logging @@ -101,9 +101,9 @@ - org.springframework - spring-orm - ${spring.version} + org.springframework.data + spring-data-jpa + ${spring-data-jpa.version} @@ -151,7 +151,6 @@ org.springframework spring-test - ${spring.version} test @@ -196,5 +195,14 @@ + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index a11b37a256dd..d6ef2e0379ed 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -3,9 +3,10 @@ public class Profiles { public static final String JDBC = "jdbc", - JPA = "jpa"; + JPA = "jpa", + DATAJPA = "datajpa"; - public static final String REPOSITORY_IMPLEMENTATION = JPA; + public static final String REPOSITORY_IMPLEMENTATION = DATAJPA; public static final String POSTGRES_DB = "postgres", diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 384b5725d2d6..e9fd06435871 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.model; import org.hibernate.Hibernate; +import org.springframework.data.domain.Persistable; import javax.persistence.*; @@ -8,7 +9,7 @@ // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) -public abstract class AbstractBaseEntity { +public abstract class AbstractBaseEntity implements Persistable { public static final int START_SEQ = 100000; @Id @@ -31,10 +32,12 @@ public void setId(Integer id) { this.id = id; } + @Override public Integer getId() { return id; } + @Override public boolean isNew() { return this.id == null; } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java new file mode 100644 index 000000000000..a3659675c910 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.javawebinar.topjava.model.Meal; + +public interface CrudMealRepository extends JpaRepository { +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java new file mode 100644 index 000000000000..cd7460d7ba37 --- /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; +import java.util.Optional; + +@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 + Optional findById(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..aa1615e487be --- /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(Sort.Direction.ASC, "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.findById(id).orElse(null); + } + + @Override + public User getByEmail(String email) { + return crudRepository.getByEmail(email); + } + + @Override + public List getAll() { + return crudRepository.findAll(SORT_NAME_EMAIL); + } +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 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 8044c25638fe701d149d38dacaf99911e5a68c43 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 1 Dec 2017 15:55:26 +0700 Subject: [PATCH 056/135] 5_8 spring chache --- pom.xml | 17 ++++++++++++- .../topjava/service/UserServiceImpl.java | 6 +++++ src/main/resources/cache/ehcache.xml | 25 +++++++++++++++++++ src/main/resources/spring/spring-app.xml | 1 + src/main/resources/spring/spring-tools.xml | 22 ++++++++++++++++ .../topjava/service/UserServiceTest.java | 10 ++++++++ 6 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/cache/ehcache.xml create mode 100644 src/main/resources/spring/spring-tools.xml diff --git a/pom.xml b/pom.xml index cbad695ae3db..db914bace806 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,9 @@ 5.2.12.Final 6.0.5.Final 3.0.1-b08 + + + 3.4.0 @@ -92,7 +95,7 @@ org.springframework - spring-context + spring-context-support commons-logging @@ -126,6 +129,18 @@ provided + + javax.cache + cache-api + 1.0.0 + + + org.ehcache + ehcache + runtime + ${ehcache.version} + + diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index a3f090177a3a..84f161955506 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 create(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,11 +48,13 @@ 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"); diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml new file mode 100644 index 000000000000..9df6cc5fc20b --- /dev/null +++ b/src/main/resources/cache/ehcache.xml @@ -0,0 +1,25 @@ + + + + + + + + + + 5 + + 5000 + + + + + + + 5000 + + + + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 4b239a370a1b..f3b905c65456 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..96551dc28daf --- /dev/null +++ b/src/main/resources/spring/spring-tools.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ 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 c9c32efe847c..d53a03a4b0f9 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -1,9 +1,11 @@ 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.cache.CacheManager; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -38,6 +40,14 @@ public class UserServiceTest { @Autowired private UserService service; + @Autowired + private CacheManager cacheManager; + + @Before + public void setUp() throws Exception { + cacheManager.getCache("users").clear(); + } + @Test public void create() throws Exception { User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER)); From 4f6bfe4b8707927e25460f9eb50df7b16e90ca89 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 09:33:07 +0700 Subject: [PATCH 057/135] 6_0 fix --- pom.xml | 8 +------- .../ru/javawebinar/topjava/model/User.java | 19 +++++++++++-------- src/main/resources/cache/ehcache.xml | 6 +++--- .../mock/InMemoryMealRepositoryImpl.java | 2 +- .../topjava/service/MealServiceTest.java | 18 +++++++++--------- .../topjava/service/UserServiceTest.java | 5 +++-- 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index db914bace806..e685d57b377b 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ UTF-8 UTF-8 - 8.5.15 + 8.5.24 5.0.1.RELEASE 2.0.2.RELEASE @@ -96,12 +96,6 @@ org.springframework spring-context-support - - - commons-logging - commons-logging - - org.springframework.data diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 93d5e6f3946f..ae776ff48198 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,15 +1,14 @@ package ru.javawebinar.topjava.model; import org.hibernate.validator.constraints.Range; +import org.springframework.util.CollectionUtils; import javax.persistence.*; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -import java.util.Date; -import java.util.EnumSet; -import java.util.Set; +import java.util.*; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; @@ -38,7 +37,6 @@ public class User extends AbstractNamedEntity { private String password; @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") - @NotNull private boolean enabled = true; @Column(name = "registered", columnDefinition = "timestamp default now()") @@ -59,20 +57,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() { @@ -119,6 +118,10 @@ public String getPassword() { return password; } + 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/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml index 9df6cc5fc20b..c6347bd940d6 100644 --- a/src/main/resources/cache/ehcache.xml +++ b/src/main/resources/cache/ehcache.xml @@ -14,12 +14,12 @@ 5000 - + - 5000 + 1 - + 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 cc601e22ccc4..6504e4a507bd 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -19,7 +19,7 @@ @Repository public class InMemoryMealRepositoryImpl implements MealRepository { - private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); + private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepositoryImpl.class); // Map userId -> (mealId-> meal) private Map> repository = new ConcurrentHashMap<>(); diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 36418d0acba6..2675862e3ebe 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -72,57 +72,57 @@ public static void printResult() { private MealService service; @Test - public void testDelete() throws Exception { + public void delete() throws Exception { service.delete(MEAL1_ID, USER_ID); assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); } @Test - public void testDeleteNotFound() throws Exception { + public void deleteNotFound() throws Exception { thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @Test - public void testSave() throws Exception { + public void create() throws Exception { Meal created = getCreated(); service.create(created, USER_ID); assertMatch(service.getAll(USER_ID), created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); } @Test - public void testGet() throws Exception { + public void get() throws Exception { Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); assertMatch(actual, ADMIN_MEAL1); } @Test - public void testGetNotFound() throws Exception { + public void getNotFound() throws Exception { thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @Test - public void testUpdate() throws Exception { + public void update() throws Exception { Meal updated = getUpdated(); service.update(updated, USER_ID); assertMatch(service.get(MEAL1_ID, USER_ID), updated); } @Test - public void testUpdateNotFound() throws Exception { + public void updateNotFound() throws Exception { thrown.expect(NotFoundException.class); thrown.expectMessage("Not found entity with id=" + MEAL1_ID); service.update(MEAL1, ADMIN_ID); } @Test - public void testGetAll() throws Exception { + public void getAll() throws Exception { assertMatch(service.getAll(USER_ID), MEALS); } @Test - public void testGetBetween() throws Exception { + public void getBetween() throws Exception { assertMatch(service.getBetweenDates( LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID), MEAL3, MEAL2, MEAL1); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index d53a03a4b0f9..ee8e3cd13d15 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -18,6 +18,7 @@ import ru.javawebinar.topjava.util.exception.NotFoundException; import java.util.Collections; +import java.util.Date; import java.util.List; import static ru.javawebinar.topjava.UserTestData.*; @@ -47,10 +48,10 @@ public class UserServiceTest { public void setUp() throws Exception { cacheManager.getCache("users").clear(); } - + @Test public void create() 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.create(newUser); newUser.setId(created.getId()); assertMatch(service.getAll(), ADMIN, newUser, USER); From c3f83198bea6466da5cc1b3ceba56259bb4de1dd Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 10:06:59 +0700 Subject: [PATCH 058/135] 6_01 HW5 data_jpa --- .../datajpa/CrudMealRepository.java | 25 ++++++++++++++++++- .../datajpa/DataJpaMealRepositoryImpl.java | 23 +++++++++++------ 2 files changed, 40 insertions(+), 8 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..579bb7819c36 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,38 @@ 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; + return crudMealRepository.findById(id).filter(meal -> meal.getUser().getId() == userId).orElse(null); } @Override public List getAll(int userId) { - return null; + return crudMealRepository.getAll(userId); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return crudMealRepository.getBetween(startDate, endDate, userId); } } From 31c44d094ed1054e0503b12c219cc582bc9077d8 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 10:11:44 +0700 Subject: [PATCH 059/135] 6_02 HW5 profile test --- src/main/resources/spring/spring-db.xml | 84 ++++++++++--------- ...Test.java => AbstractMealServiceTest.java} | 56 +------------ .../topjava/service/AbstractServiceTest.java | 61 ++++++++++++++ ...Test.java => AbstractUserServiceTest.java} | 23 +---- .../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 +++ 10 files changed, 166 insertions(+), 118 deletions(-) rename src/test/java/ru/javawebinar/topjava/service/{MealServiceTest.java => AbstractMealServiceTest.java} (50%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java rename src/test/java/ru/javawebinar/topjava/service/{UserServiceTest.java => AbstractUserServiceTest.java} (72%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 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 diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java similarity index 50% rename from src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 2675862e3ebe..9e41a569a18d 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,72 +1,18 @@ 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.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.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.time.LocalDate; import java.time.Month; -import java.util.concurrent.TimeUnit; -import static org.slf4j.LoggerFactory.getLogger; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; -@ContextConfiguration({ - "classpath:spring/spring-app.xml", - "classpath:spring/spring-db.xml" -}) -@RunWith(SpringRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(resolver = ActiveDbProfileResolver.class) -public class MealServiceTest { - private static final Logger log = getLogger("result"); - - private static 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("\n%-25s %7d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); - results.append(result); - 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---------------------------------" + - 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..eaa76c8b994a --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -0,0 +1,61 @@ +package ru.javawebinar.topjava.service; + +import org.junit.AfterClass; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.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 ru.javawebinar.topjava.ActiveDbProfileResolver; + +import java.util.concurrent.TimeUnit; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +@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("result"); + + 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 72% rename from src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index ee8e3cd13d15..718c6633f966 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -2,17 +2,9 @@ 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.cache.CacheManager; import org.springframework.dao.DataAccessException; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -23,20 +15,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(resolver = ActiveDbProfileResolver.class) -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; 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 3cbfe36b93b48ff7d90567ec5b19d754d8b0f722 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 10:32:09 +0700 Subject: [PATCH 060/135] 6_03 extract rules --- .../ru/javawebinar/topjava/TimingRules.java | 41 +++++++++++++++++++ .../topjava/service/AbstractServiceTest.java | 36 ++++------------ 2 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/TimingRules.java diff --git a/src/test/java/ru/javawebinar/topjava/TimingRules.java b/src/test/java/ru/javawebinar/topjava/TimingRules.java new file mode 100644 index 000000000000..46f3339011d6 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TimingRules.java @@ -0,0 +1,41 @@ +package ru.javawebinar.topjava; + +import org.junit.rules.ExternalResource; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public class TimingRules { + private static final Logger log = LoggerFactory.getLogger("result"); + + private static StringBuilder results = new StringBuilder(); + + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public static final Stopwatch STOPWATCH = new Stopwatch() { + @Override + protected void finished(long nanos, Description description) { + String result = String.format("%-95s %7d", description.getDisplayName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result).append('\n'); + log.info(result + " ms\n"); + } + }; + + public static final ExternalResource SUMMARY = new ExternalResource() { + @Override + protected void before() throws Throwable { + results.setLength(0); + } + + @Override + protected void after() { + log.info("\n-------------------------------------------------------------------------------------------------------" + + "\nTest Duration, ms" + + "\n-------------------------------------------------------------------------------------------------------\n" + + results + + "-------------------------------------------------------------------------------------------------------\n"); + } + }; +} diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index eaa76c8b994a..4341357169e2 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -1,13 +1,11 @@ package ru.javawebinar.topjava.service; -import org.junit.AfterClass; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.rules.ExpectedException; +import org.junit.rules.ExternalResource; 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; @@ -15,8 +13,7 @@ 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 ru.javawebinar.topjava.TimingRules; @ContextConfiguration({ "classpath:spring/spring-app.xml", @@ -26,36 +23,17 @@ @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("result"); - - private static StringBuilder results = new StringBuilder(); + @ClassRule + public static ExternalResource summary = TimingRules.SUMMARY; @Rule - public ExpectedException thrown = ExpectedException.none(); + public Stopwatch stopwatch = TimingRules.STOPWATCH; @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"); - } - }; + public ExpectedException thrown = ExpectedException.none(); 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 From 15bcd74822a661a0a964fc7e9c2991733c9439c8 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 10:58:06 +0700 Subject: [PATCH 061/135] 6_04 HW5 optional fix jdbc profiles --- .../jdbc/JdbcMealRepositoryImpl.java | 40 ++++++++++++++----- .../javawebinar/topjava/web/MealServlet.java | 9 +++-- .../ru/javawebinar/topjava/SpringMain.java | 11 +++-- 3 files changed, 44 insertions(+), 16 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..c4b322434f72 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,53 @@ 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 +65,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 +105,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 6e936f48a34e..e2614f5e7dbb 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.web.meal.MealRestController; @@ -22,13 +22,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.getActiveDbProfile(), 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..8cc47f2f81d6 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,10 +16,14 @@ 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.getActiveDbProfile(), 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)); + adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); System.out.println(); MealRestController mealController = appCtx.getBean(MealRestController.class); From 786d89d0386bd1e2dc56774095f4aba653e25d0e Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 11:04:57 +0700 Subject: [PATCH 062/135] 6_05 update hsqldb --- pom.xml | 2 +- .../jdbc/JdbcMealRepositoryImpl.java | 40 +++++-------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/pom.xml b/pom.xml index e685d57b377b..686a660db4d9 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,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 c4b322434f72..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,53 +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 @@ -65,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()) { @@ -105,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 e672b3842d832e723e39d62b5d2692771cc091d0 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 11:10:03 +0700 Subject: [PATCH 063/135] 6_06 HW5 optional fetch join --- .../java/ru/javawebinar/topjava/model/User.java | 8 ++++++++ .../topjava/repository/MealRepository.java | 4 ++++ .../topjava/repository/UserRepository.java | 4 ++++ .../repository/datajpa/CrudMealRepository.java | 3 +++ .../repository/datajpa/CrudUserRepository.java | 3 +++ .../datajpa/DataJpaMealRepositoryImpl.java | 5 +++++ .../datajpa/DataJpaUserRepositoryImpl.java | 5 +++++ .../topjava/service/MealService.java | 2 ++ .../topjava/service/MealServiceImpl.java | 5 +++++ .../topjava/service/UserService.java | 2 ++ .../topjava/service/UserServiceImpl.java | 5 +++++ .../ru/javawebinar/topjava/UserTestData.java | 4 ++-- .../service/AbstractMealServiceTest.java | 2 +- .../service/AbstractUserServiceTest.java | 2 +- .../service/datajpa/DataJpaMealServiceTest.java | 17 +++++++++++++++++ .../service/datajpa/DataJpaUserServiceTest.java | 16 ++++++++++++++++ 16 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index ae776ff48198..e94d402107c2 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -53,6 +53,10 @@ public class User extends AbstractNamedEntity { @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") + @OrderBy("dateTime DESC") + protected List meals; + public User() { } @@ -122,6 +126,10 @@ public void setRoles(Collection roles) { this.roles = CollectionUtils.isEmpty(roles) ? Collections.emptySet() : EnumSet.copyOf(roles); } + public List getMeals() { + return meals; + } + @Override public String toString() { return "User{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index f0578ff40457..6d717ecbd033 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -20,4 +20,8 @@ public interface MealRepository { // ORDERED dateTime desc List 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 cd7460d7ba37..fa2bdc469ba2 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,7 @@ public interface CrudUserRepository extends JpaRepository { List findAll(Sort sort); User getByEmail(String email); + + @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") + User getWithMeals(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 579bb7819c36..4db0e631cd06 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -47,4 +47,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 aa1615e487be..4e0b1ac7c043 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 3f956057ea19..70ad29be97a2 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 create(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 406d50175e0f..2c7da78034e1 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -53,4 +53,9 @@ public Meal create(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 d0cf33e30815..aecb82378fd2 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 { void update(User user); List getAll(); + + 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 84f161955506..8173aa3965bb 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -60,4 +60,9 @@ public void update(User user) { Assert.notNull(user, "user must not be null"); checkNotFoundWithId(repository.save(user), user.getId()); } + + @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/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 3cc466c53754..ae3295066d0d 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -16,7 +16,7 @@ public class UserTestData { public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); public static void assertMatch(User actual, User expected) { - assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles"); + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles", "meals"); } public static void assertMatch(Iterable actual, User... expected) { @@ -24,6 +24,6 @@ public static void assertMatch(Iterable actual, User... expected) { } public static void assertMatch(Iterable actual, Iterable expected) { - assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles", "meals").isEqualTo(expected); } } diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 9e41a569a18d..c42c1361006b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -15,7 +15,7 @@ public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired - private MealService service; + protected MealService service; @Test public void delete() 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 718c6633f966..692cb270ca04 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -18,7 +18,7 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired - private UserService service; + protected UserService service; @Autowired private CacheManager cacheManager; 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..2b8363626b13 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); + assertMatch(adminMeal, ADMIN_MEAL1); + UserTestData.assertMatch(adminMeal.getUser(), UserTestData.ADMIN); + } + + @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..b9d42b83f4a1 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); + assertMatch(user, USER); + MealTestData.assertMatch(user.getMeals(), MealTestData.MEALS); + } + + @Test(expected = NotFoundException.class) + public void testGetWithMealsNotFound() throws Exception { + service.getWithMeals(1); + } } \ No newline at end of file From 2fddcf2d3f716515da208c9a0dfaa0d8667ad0d8 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 14:24:29 +0700 Subject: [PATCH 064/135] 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 e94d402107c2..824e2456596d 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import org.hibernate.annotations.BatchSize; import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; @@ -15,12 +16,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 AbstractNamedEntity { +// 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"; @@ -47,6 +51,8 @@ public class User extends AbstractNamedEntity { @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 fa2bdc469ba2..a87a99f71c09 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; @@ -15,7 +16,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); @@ -31,6 +31,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 1018aaa8bc3ffe670e4c2512e403efb57265cecb Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 14:49:20 +0700 Subject: [PATCH 065/135] 6_08 add test validation --- .../javawebinar/topjava/util/ValidationUtil.java | 11 +++++++++++ .../topjava/service/AbstractMealServiceTest.java | 10 ++++++++++ .../topjava/service/AbstractServiceTest.java | 14 ++++++++++++++ .../topjava/service/AbstractUserServiceTest.java | 10 ++++++++++ 4 files changed, 45 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 78da8c6d7abe..961bf2c77140 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -42,4 +42,15 @@ public static void assureIdConsistent(AbstractBaseEntity entity, int id) { throw new IllegalArgumentException(entity + " must be with id=" + id); } } + + // 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/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index c42c1361006b..b93febfcd557 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -5,9 +5,11 @@ 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 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; @@ -73,4 +75,12 @@ public void getBetween() throws Exception { LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID), MEAL3, MEAL2, MEAL1); } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), " ", 300), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.create(new Meal(null, null, "Description", 300), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 9), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.create(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 4341357169e2..42168a33ba0a 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.service; +import org.junit.Assert; import org.junit.ClassRule; import org.junit.Rule; import org.junit.rules.ExpectedException; @@ -15,6 +16,9 @@ import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.TimingRules; +import static org.hamcrest.CoreMatchers.instanceOf; +import static ru.javawebinar.topjava.util.ValidationUtil.getRootCause; + @ContextConfiguration({ "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" @@ -36,4 +40,14 @@ abstract public class AbstractServiceTest { // needed only for java.util.logging (postgres driver) SLF4JBridgeHandler.install(); } + + // Check root cause in JUnit: https://github.com/junit-team/junit4/pull/778 + public void validateRootCause(Runnable runnable, Class exceptionClass) { + try { + runnable.run(); + Assert.fail("Expected " + exceptionClass.getName()); + } catch (Exception e) { + Assert.assertThat(getRootCause(e), instanceOf(exceptionClass)); + } + } } \ 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 692cb270ca04..f7da76476da8 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -9,6 +9,7 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; +import javax.validation.ConstraintViolationException; import java.util.Collections; import java.util.Date; import java.util.List; @@ -83,4 +84,13 @@ public void getAll() throws Exception { List all = service.getAll(); assertMatch(all, ADMIN, USER); } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 10001, true, Collections.emptySet())), ConstraintViolationException.class); + } } \ No newline at end of file From 6ec77d0719604e99fa9dd0d3be5b7f1a4ebf33af Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 15:56:18 +0700 Subject: [PATCH 066/135] 6_09 hibernate cache --- pom.xml | 5 +++++ .../ru/javawebinar/topjava/model/User.java | 4 ++++ .../topjava/repository/JpaUtil.java | 22 +++++++++++++++++++ src/main/resources/spring/spring-db.xml | 8 +++++++ .../service/AbstractUserServiceTest.java | 5 +++++ 5 files changed, 44 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java diff --git a/pom.xml b/pom.xml index 686a660db4d9..bdfd4d35e8ad 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,11 @@ hibernate-validator ${hibernate-validator.version} + + org.hibernate + hibernate-jcache + ${hibernate.version} + diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 824e2456596d..2da768d99310 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.Range; import org.springframework.util.CollectionUtils; @@ -13,6 +15,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"), @@ -47,6 +50,7 @@ public class User extends AbstractNamedEntity { @NotNull 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/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 1e78287feb20..5fbd5c48b718 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 f7da76476da8..6b69126c7c21 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -7,6 +7,7 @@ import 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; @@ -24,9 +25,13 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private CacheManager cacheManager; + @Autowired + protected JpaUtil jpaUtil; + @Before public void setUp() throws Exception { cacheManager.getCache("users").clear(); + jpaUtil.clear2ndLevelHibernateCache(); } @Test From 0fc39d3ebf408ceda7f817faaa49edd155f173d4 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 8 Dec 2017 15:57:50 +0700 Subject: [PATCH 067/135] 6_10 cascade dll --- .../ru/javawebinar/topjava/model/AbstractBaseEntity.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 | 6 ++++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index e9fd06435871..9bc46479d80a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -14,6 +14,7 @@ public abstract class AbstractBaseEntity 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 diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 48989e05fb82..2eade30b4dfb 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.Range; import javax.persistence.*; @@ -41,6 +43,7 @@ public class Meal extends AbstractBaseEntity { @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 2da768d99310..574bae622f4a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -63,7 +63,7 @@ public class User extends AbstractNamedEntity { @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 5fbd5c48b718..a3a5104d389f 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -70,6 +70,12 @@ + From ae938818bf8b0765fdbe77d55273792656347e5a Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 9 Dec 2017 12:18:54 +0600 Subject: [PATCH 068/135] fix error --- .../javawebinar/topjava/service/AbstractUserServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 6b69126c7c21..d3d1cad12b0b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -95,7 +95,7 @@ public void testValidation() throws Exception { validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, Collections.emptySet())), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 10001, true, Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 10001, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); } } \ No newline at end of file From 6eebef7c4b53c99bef9e483d314bf24ccf85749d Mon Sep 17 00:00:00 2001 From: Fabus Date: Mon, 11 Dec 2017 09:29:37 +0700 Subject: [PATCH 069/135] 6_11 spring web --- pom.xml | 11 ++++++++--- .../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, 42 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index bdfd4d35e8ad..df80aa57dacf 100644 --- a/pom.xml +++ b/pom.xml @@ -143,9 +143,14 @@ - javax.servlet - javax.servlet-api - 4.0.0 + org.springframework + spring-web + + + + 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 e2614f5e7dbb..afe36ca8aaab 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.web.meal.MealRestController; @@ -22,25 +22,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 79e55bbc6aa5..8ce52dec52cb 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); + } + @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int userId = Integer.parseInt(request.getParameter("userId")); @@ -23,7 +36,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 d2e475517f7e..2af9fadd1997 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -6,6 +6,22 @@ Topjava + + spring.profiles.default + postgres,datajpa + + + + contextConfigLocation + + classpath:spring/spring-app.xml + classpath:spring/spring-db.xml + + + + + org.springframework.web.context.ContextLoaderListener + userServlet ru.javawebinar.topjava.web.UserServlet From 233257d551b73858b65665de7a960e2ce1269221 Mon Sep 17 00:00:00 2001 From: Fabus Date: Mon, 11 Dec 2017 12:46:19 +0700 Subject: [PATCH 070/135] 6_12 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 | 18 --------- src/main/webapp/index.jsp | 26 ++++++++++++ src/main/webapp/meals.jsp | 5 +-- src/main/webapp/users.jsp | 40 ++++++++++++++++--- 10 files changed, 121 insertions(+), 26 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..7a6deeae1ba6 --- /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 + +user.title=Users +user.name=Name +user.email=Email +user.roles=Roles +user.active=Active +user.registered=Registered + +meal.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..4f68f4cc5c72 --- /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 + +user.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 +user.name=\u0418\u043C\u044F +user.email=\u041F\u043E\u0447\u0442\u0430 +user.roles=\u0420\u043E\u043B\u0438 +user.active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439 +user.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D + +meal.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 886449733a86..000000000000 --- a/src/main/webapp/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - Java Enterprise (Topjava) - - -

    Проект Java Enterprise (Topjava)

    -
    -
    - Meals of  - - - - - diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 000000000000..d43268f4eb18 --- /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 269cc4e964a9..8af6b4b69fd9 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -4,13 +4,12 @@ <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> - Meal list + Calories management
    -

    Home

    -

    Meals

    +

    Meals

    From Date:
    diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp index acf19d7adf09..ff8b417a3fb5 100644 --- a/src/main/webapp/users.jsp +++ b/src/main/webapp/users.jsp @@ -1,10 +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" %> + + + - - Users - + -

    Home

    -

    Users

    + + +
    +

    + +

    + + + + + + + + + + + + + + + + + + + +
    ${user.email}${user.roles}<%=user.isEnabled()%> +
    +
    + \ No newline at end of file From d79331955ff8a1337d538cefb6d1c277f76b5182 Mon Sep 17 00:00:00 2001 From: Fabus Date: Mon, 11 Dec 2017 14:06:07 +0700 Subject: [PATCH 071/135] 6_13 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 df80aa57dacf..8adf2e4a609e 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,70 @@ -Dfile.encoding=UTF-8 + + + + org.codehaus.cargo + cargo-maven2-plugin + 1.6.5 + + + 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 a3a5104d389f..30e0ddf83183 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 d8964fe9ba6c6b43ce5fd710f1bd82f05cbdaa89 Mon Sep 17 00:00:00 2001 From: Fabus Date: Mon, 11 Dec 2017 16:31:49 +0700 Subject: [PATCH 072/135] 6_14 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 .../webapp/{ => WEB-INF/jsp}/mealForm.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}/mealForm.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 8adf2e4a609e..ad376a9c4cde 100644 --- a/pom.xml +++ b/pom.xml @@ -208,7 +208,7 @@ org.springframework - spring-web + spring-webmvc 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..1aa27d51fd6d --- /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.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import ru.javawebinar.topjava.AuthorizedUser; +import ru.javawebinar.topjava.service.UserService; + +import javax.servlet.http.HttpServletRequest; + +@Controller +public class RootController { + @Autowired + private UserService service; + + @GetMapping("/") + public String root() { + return "index"; + } + + @GetMapping("/users") + public String users(Model model) { + model.addAttribute("users", service.getAll()); + return "users"; + } + + @PostMapping("/users") + 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 12a4f63c6c15..809d4c9c3d50 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 f3b905c65456..689a9e68cd0b 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -17,7 +17,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/mealForm.jsp b/src/main/webapp/WEB-INF/jsp/mealForm.jsp similarity index 100% rename from src/main/webapp/mealForm.jsp rename to src/main/webapp/WEB-INF/jsp/mealForm.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 2af9fadd1997..54ccc988f0f8 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -19,9 +19,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 87fb522e7115f6d6f214f33852bd57a7073e91fe Mon Sep 17 00:00:00 2001 From: Fabus Date: Mon, 11 Dec 2017 16:48:21 +0700 Subject: [PATCH 073/135] 6_15 spring i18n --- .../messages/app.properties | 0 .../messages/app_ru.properties | 0 src/main/resources/spring/spring-mvc.xml | 18 ++++++++++++++++++ .../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 +++++++-------- 8 files changed, 36 insertions(+), 24 deletions(-) rename {src/main/resources => config}/messages/app.properties (100%) rename {src/main/resources => config}/messages/app_ru.properties (100%) 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/src/main/resources/messages/app_ru.properties b/config/messages/app_ru.properties similarity index 100% rename from src/main/resources/messages/app_ru.properties rename to config/messages/app_ru.properties diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 7c71d5af5939..ce12ddcc64d5 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -15,4 +15,22 @@ + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 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 d43268f4eb18..93cbd1ed58d7 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 ff8b417a3fb5..997aef28b646 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 dc55bb620467dd48930c4ebca90289839e046d30 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 22 Dec 2017 13:50:58 +0700 Subject: [PATCH 074/135] 7_01 HW6 fix tests --- .../ru/javawebinar/topjava/SpringMain.java | 2 +- .../service/AbstractJpaUserServiceTest.java | 34 +++++++++++++++++++ .../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, 56 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 8cc47f2f81d6..39331e507792 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..c3e7722ee0ff --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java @@ -0,0 +1,34 @@ +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; +import java.util.Date; + +abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest { + + @Autowired + private JpaUtil jpaUtil; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + jpaUtil.clear2ndLevelHibernateCache(); + } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@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/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index b93febfcd557..9c332a8b68bb 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; @@ -78,6 +79,7 @@ public void getBetween() throws Exception { @Test public void testValidation() throws Exception { + Assume.assumeTrue(isJpaBased()); validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), " ", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.create(new Meal(null, null, "Description", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.create(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 42168a33ba0a..233afd3b2a60 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -8,12 +8,15 @@ import org.junit.rules.Stopwatch; import org.junit.runner.RunWith; 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 ru.javawebinar.topjava.TimingRules; import static org.hamcrest.CoreMatchers.instanceOf; @@ -33,6 +36,9 @@ abstract public class AbstractServiceTest { @Rule public Stopwatch stopwatch = TimingRules.STOPWATCH; + @Autowired + public Environment env; + @Rule public ExpectedException thrown = ExpectedException.none(); @@ -41,6 +47,11 @@ abstract public class AbstractServiceTest { SLF4JBridgeHandler.install(); } + public boolean isJpaBased() { +// return Arrays.stream(env.getActiveProfiles()).noneMatch(Profiles.JDBC::equals); + return env.acceptsProfiles(Profiles.JPA, Profiles.DATAJPA); + } + // Check root cause in JUnit: https://github.com/junit-team/junit4/pull/778 public void validateRootCause(Runnable runnable, Class exceptionClass) { try { diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index d3d1cad12b0b..692cb270ca04 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -7,10 +7,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.Collections; import java.util.Date; import java.util.List; @@ -25,13 +23,9 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private CacheManager cacheManager; - @Autowired - protected JpaUtil jpaUtil; - @Before public void setUp() throws Exception { cacheManager.getCache("users").clear(); - jpaUtil.clear2ndLevelHibernateCache(); } @Test @@ -89,13 +83,4 @@ public void getAll() throws Exception { List all = service.getAll(); assertMatch(all, ADMIN, USER); } - - @Test - public void testValidation() throws Exception { - validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@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/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index b9d42b83f4a1..4c25bca7679a 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 3bf1c351c8e7..56557ddb321f 100644 --- a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -27,7 +27,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 daaef506a187c394804aec3776391d7b24301257 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 22 Dec 2017 14:22:54 +0700 Subject: [PATCH 075/135] 7_02 HW6 meals --- config/messages/app.properties | 16 +++- config/messages/app_ru.properties | 23 ++++- .../javawebinar/topjava/web/MealServlet.java | 91 ------------------- .../topjava/web/RootController.java | 16 +++- .../javawebinar/topjava/web/UserServlet.java | 43 --------- .../web/meal/AbstractMealController.java | 78 ++++++++++++++++ .../topjava/web/meal/JspMealController.java | 70 ++++++++++++++ .../topjava/web/meal/MealRestController.java | 79 +--------------- src/main/webapp/WEB-INF/jsp/mealForm.jsp | 24 ++--- src/main/webapp/WEB-INF/jsp/meals.jsp | 41 +++++---- src/main/webapp/WEB-INF/web.xml | 22 ----- 11 files changed, 233 insertions(+), 270 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 7a6deeae1ba6..ab8a69745820 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -11,5 +11,19 @@ user.active=Active user.registered=Registered meal.title=Meals +meal.edit=Edit meal +meal.add=Add meal +meal.filter=Filter +meal.startDate=From date +meal.endDate=To date +meal.startTime=From time +meal.endTime=To time +meal.description=Description +meal.dateTime=Date/Time +meal.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 4f68f4cc5c72..3a7b5fd698f0 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -12,4 +12,25 @@ user.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u04 meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 -common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \ No newline at end of file +common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C +user.registered=Зарегистрирован + +meal.title=Моя еда +meal.edit=Редактирование еды +meal.add=Добавление еды +meal.filter=Отфильтровать +meal.startDate=От даты +meal.endDate=До даты +meal.startTime=От времени +meal.endTime=До времени +meal.description=Описание +meal.dateTime=Дата/Время +meal.calories=Калории + +common.select=Выбрать +common.select=Выбрать +common.delete=Удалить +common.update=Обновить +common.save=Сохранить +common.cancel=Отмена +common.actions=Выполнить \ 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 afe36ca8aaab..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ /dev/null @@ -1,91 +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.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; - -import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; -import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; - -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) { - Meal meal = new Meal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.parseInt(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 = parseLocalDate(request.getParameter("startDate")); - LocalDate endDate = parseLocalDate(request.getParameter("endDate")); - LocalTime startTime = parseLocalTime(request.getParameter("startTime")); - LocalTime endTime = parseLocalTime(request.getParameter("endTime")); - request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); - request.getRequestDispatcher("/meals.jsp").forward(request, response); - } - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String action = request.getParameter("action"); - - switch (action == null ? "all" : action) { - case "delete": - int id = getId(request); - mealController.delete(id); - response.sendRedirect("meals"); - break; - case "create": - case "update": - final Meal meal = "create".equals(action) ? - new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : - mealController.get(getId(request)); - request.setAttribute("meal", meal); - request.getRequestDispatcher("/mealForm.jsp").forward(request, response); - break; - case "all": - default: - request.setAttribute("meals", mealController.getAll()); - request.getRequestDispatcher("/meals.jsp").forward(request, response); - break; - } - } - - private int getId(HttpServletRequest request) { - String paramId = Objects.requireNonNull(request.getParameter("id")); - return Integer.parseInt(paramId); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 1aa27d51fd6d..6766757755f9 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.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; +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; @GetMapping("/") public String root() { @@ -22,7 +27,7 @@ public String root() { @GetMapping("/users") 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"; } + + @GetMapping("/meals") + 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 8ce52dec52cb..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ /dev/null @@ -1,43 +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); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - int userId = Integer.parseInt(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..4b7b61c790f5 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java @@ -0,0 +1,78 @@ +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.assureIdConsistent; +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.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = AuthorizedUser.id(); + assureIdConsistent(meal, id); + log.info("update {} for user {}", meal, userId); + service.update(meal, userId); + } + + /** + *
      Filter separately + *
    1. by date
    2. + *
    3. by time for every date
    4. + *
    + */ + 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); + + List mealsDateFiltered = service.getBetweenDates( + startDate != null ? startDate : DateTimeUtil.MIN_DATE, + endDate != null ? endDate : DateTimeUtil.MAX_DATE, userId); + + return MealsUtil.getFilteredWithExceeded(mealsDateFiltered, + 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..722db641efef --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -0,0 +1,70 @@ +package ru.javawebinar.topjava.web.meal; + +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 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 { + + @GetMapping("/delete") + public String delete(HttpServletRequest request) { + super.delete(getId(request)); + return "redirect:meals"; + } + + @GetMapping("/update") + public String update(HttpServletRequest request, Model model) { + model.addAttribute("meal", super.get(getId(request))); + return "mealForm"; + } + + @GetMapping("/create") + public String create(Model model) { + model.addAttribute("meal", new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000)); + return "mealForm"; + } + + @PostMapping + 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"; + } + + @PostMapping("/filter") + 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 79f8ebdc4b46..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,84 +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.assureIdConsistent; -import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; @Controller -public class MealRestController { - private static final Logger log = LoggerFactory.getLogger(MealRestController.class); - - private final MealService service; - - @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.create(meal, userId); - } - - public void update(Meal meal, int id) { - int userId = AuthorizedUser.id(); - assureIdConsistent(meal, id); - log.info("update {} for user {}", meal, userId); - service.update(meal, userId); - } - - /** - *
      Filter separately - *
    1. by date
    2. - *
    3. by time for every date
    4. - *
    - */ - 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); - - List mealsDateFiltered = service.getBetweenDates( - startDate != null ? startDate : DateTimeUtil.MIN_DATE, - endDate != null ? endDate : DateTimeUtil.MAX_DATE, userId); - - return MealsUtil.getFilteredWithExceeded(mealsDateFiltered, - 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/mealForm.jsp b/src/main/webapp/WEB-INF/jsp/mealForm.jsp index d4509bb3417a..999c1e216ddb 100644 --- a/src/main/webapp/WEB-INF/jsp/mealForm.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealForm.jsp @@ -1,34 +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 8af6b4b69fd9..65ceea40ad7c 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -1,43 +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 +53,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 54ccc988f0f8..70856a94933f 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,26 +37,4 @@ / - - From bbfe972fc2f32b31164f05992ed413495972bafb Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 22 Dec 2017 14:52:15 +0700 Subject: [PATCH 076/135] 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 722db641efef..527f936e60b0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -24,7 +24,7 @@ public class JspMealController extends AbstractMealController { @GetMapping("/delete") public String delete(HttpServletRequest request) { super.delete(getId(request)); - return "redirect:meals"; + return "redirect:/meals"; } @GetMapping("/update") @@ -50,7 +50,7 @@ public String updateOrCreate(HttpServletRequest request) { } else { super.update(meal, getId(request)); } - return "redirect:meals"; + return "redirect:/meals"; } @PostMapping("/filter") 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 70856a94933f..91d304f3c7d1 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,4 +37,21 @@ / + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + + From ae5f524afcab2be1185f2650e16ce8d88d84ee60 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 22 Dec 2017 15:04:59 +0700 Subject: [PATCH 077/135] 7_04 HW6 optional add role --- src/main/java/ru/javawebinar/topjava/model/User.java | 2 +- src/main/resources/db/populateDB.sql | 3 ++- src/test/java/ru/javawebinar/topjava/UserTestData.java | 6 +++--- .../topjava/service/AbstractUserServiceTest.java | 9 +++++---- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 574bae622f4a..8b5de6442dfc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -18,7 +18,7 @@ @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 diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index 8ff6e60e0e70..22ef65d83966 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -9,7 +9,8 @@ INSERT INTO users (name, email, password) VALUES 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 ae3295066d0d..f19252274108 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -13,10 +13,10 @@ 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 void assertMatch(User actual, User expected) { - assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles", "meals"); + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "meals"); } public static void assertMatch(Iterable actual, User... expected) { @@ -24,6 +24,6 @@ public static void assertMatch(Iterable actual, User... expected) { } public static void assertMatch(Iterable actual, Iterable expected) { - assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles", "meals").isEqualTo(expected); + assertThat(actual).usingElementComparatorIgnoringFields("registered", "meals").isEqualTo(expected); } } diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 692cb270ca04..f3f8faf57e75 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -54,8 +54,8 @@ public void notFoundDelete() throws Exception { @Test public void get() throws Exception { - User user = service.get(USER_ID); - assertMatch(user, USER); + User user = service.get(ADMIN_ID); + assertMatch(user, ADMIN); } @Test(expected = NotFoundException.class) @@ -65,8 +65,8 @@ public void getNotFound() throws Exception { @Test public void getByEmail() throws Exception { - User user = service.getByEmail("user@yandex.ru"); - assertMatch(user, USER); + User user = service.getByEmail("admin@gmail.com"); + assertMatch(user, ADMIN); } @Test @@ -74,6 +74,7 @@ public void update() throws Exception { User updated = new User(USER); updated.setName("UpdatedName"); updated.setCaloriesPerDay(330); + updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); service.update(updated); assertMatch(service.get(USER_ID), updated); } From 703fafccd302d8d4d6c5c1053d4fd8d90692250c Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 22 Dec 2017 15:08:35 +0700 Subject: [PATCH 078/135] 7_05 fix hint graph --- .../topjava/repository/datajpa/CrudUserRepository.java | 3 ++- .../topjava/repository/jpa/JpaUserRepositoryImpl.java | 2 ++ .../topjava/service/datajpa/DataJpaUserServiceTest.java | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) 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 a87a99f71c09..7111ca169852 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -34,7 +34,8 @@ public interface CrudUserRepository extends JpaRepository { // @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"}) +// https://stackoverflow.com/a/46013654/548473 + @EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD) @Query("SELECT u FROM User u WHERE u.id=?1") User getWithMeals(int id); } 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 b82dbd73342f..55752ca362c7 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.repository.jpa; +import org.hibernate.jpa.QueryHints; import org.springframework.dao.support.DataAccessUtils; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -61,6 +62,7 @@ public boolean delete(int id) { public User getByEmail(String email) { List users = em.createNamedQuery(User.BY_EMAIL, User.class) .setParameter(1, email) + .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false) .getResultList(); return DataAccessUtils.singleResult(users); } 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 4c25bca7679a..203f984401f9 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -14,9 +14,9 @@ public class DataJpaUserServiceTest extends AbstractJpaUserServiceTest { @Test public void testGetWithMeals() throws Exception { - User user = service.getWithMeals(USER_ID); - assertMatch(user, USER); - MealTestData.assertMatch(user.getMeals(), MealTestData.MEALS); + User admin = service.getWithMeals(ADMIN_ID); + assertMatch(admin, ADMIN); + MealTestData.assertMatch(admin.getMeals(), MealTestData.ADMIN_MEAL2, MealTestData.ADMIN_MEAL1); } @Test(expected = NotFoundException.class) From ad57322ec3ed0a2a837f78dce443f1541d7aafb6 Mon Sep 17 00:00:00 2001 From: Fabus Date: Fri, 22 Dec 2017 15:11:49 +0700 Subject: [PATCH 079/135] 7_06 HW6 optional jdbc --- .../jdbc/JdbcMealRepositoryImpl.java | 4 ++ .../jdbc/JdbcUserRepositoryImpl.java | 61 ++++++++++++++++--- src/main/resources/spring/spring-db.xml | 9 ++- 3 files changed, 64 insertions(+), 10 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 0a802046b891..4a89633a23eb 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -8,13 +8,17 @@ 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 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,21 +40,31 @@ 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()); - } else if (namedParameterJdbcTemplate.update( - "UPDATE users SET name=:name, email=:email, password=:password, " + - "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { - return null; + insertRoles(user); + } else { + if (namedParameterJdbcTemplate.update( + "UPDATE users SET name=:name, email=:email, password=:password, " + + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { + return null; + } + // 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); } return user; } @Override + @Transactional public boolean delete(int id) { return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0; } @@ -58,18 +72,49 @@ 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); + Map> map = new HashMap<>(); + jdbcTemplate.query("SELECT * FROM user_roles", rs -> { + map.computeIfAbsent(rs.getInt("user_id"), userId -> EnumSet.noneOf(Role.class)) + .add(Role.valueOf(rs.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 30e0ddf83183..0e3fbfbd13e6 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 @@ + + + + @@ -93,8 +100,6 @@
    - - From 9a1d32098a8bcbd6dce4e84a2d8c8e6ec8d03627 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 23 Dec 2017 13:35:35 +0600 Subject: [PATCH 080/135] 7_07 controller test --- pom.xml | 14 ++++ .../topjava/AllActiveProfileResolver.java | 12 ++++ .../topjava/web/AbstractControllerTest.java | 69 +++++++++++++++++++ .../topjava/web/RootControllerTest.java | 29 ++++++++ 4 files changed, 124 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 ad376a9c4cde..ff39c5b781a7 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 42.1.4 4.12 + 1.3 5.2.12.Final @@ -230,7 +231,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..f2e7bb0e3db4 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -0,0 +1,69 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +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.repository.JpaUtil; +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 + private CacheManager cacheManager; + + @Autowired(required = false) + private JpaUtil jpaUtil; + + @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() { + cacheManager.getCache("users").clear(); + if (jpaUtil != null) { + jpaUtil.clear2ndLevelHibernateCache(); + } + } +} 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..dfe9870e413e --- /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.AbstractBaseEntity.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 42bd71683f830c480409ee56ceeed31744f1f7b5 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sat, 23 Dec 2017 15:06:44 +0600 Subject: [PATCH 081/135] 7_08 rest controller --- .../topjava/web/user/AdminRestController.java | 38 ++++++++++++++----- .../web/user/ProfileRestController.java | 12 ++++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index b37a8ed6c8a5..1ad69faff6c3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,40 +1,60 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.User; +import java.net.URI; import java.util.List; -@Controller +@RestController +@RequestMapping(AdminRestController.REST_URL) public class AdminRestController extends AbstractUserController { + static final String REST_URL = "/rest/admin/users"; @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 eb4c0f3f3ada..c1775ac88715 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,21 +1,27 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; -@Controller +@RestController +@RequestMapping(ProfileRestController.REST_URL) public class ProfileRestController extends AbstractUserController { + static final String REST_URL = "/rest/profile"; + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public User get() { return super.get(AuthorizedUser.id()); } + @DeleteMapping public void delete() { super.delete(AuthorizedUser.id()); } - public void update(User user) { + @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody User user) { super.update(user, AuthorizedUser.id()); } } \ No newline at end of file From a8b18e6b2de393a63e1f74da6d23e6f454f7b752 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 24 Dec 2017 13:24:40 +0600 Subject: [PATCH 082/135] 7_09 rest test jackson --- pom.xml | 8 ++++++ .../topjava/model/AbstractBaseEntity.java | 6 +++++ .../ru/javawebinar/topjava/model/User.java | 2 ++ .../web/user/AdminRestControllerTest.java | 25 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index ff39c5b781a7..8c0f0a6222ec 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 8.5.24 5.0.1.RELEASE 2.0.2.RELEASE + 2.9.3 1.2.3 @@ -225,6 +226,13 @@ 1.2 + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-json.version} + + junit diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 9bc46479d80a..a1939f350b40 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,14 +1,20 @@ 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; + + @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public abstract class AbstractBaseEntity 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 8b5de6442dfc..50a11574b4a9 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -65,6 +66,7 @@ public class User extends AbstractNamedEntity { @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..7b9d2a82b5d5 --- /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 { + + private 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 205c180909ff1355918137ed7576e9df3847e930 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 24 Dec 2017 14:12:52 +0600 Subject: [PATCH 083/135] 7_10 jackson object mapper --- pom.xml | 54 +++++++++++-------- .../topjava/model/AbstractBaseEntity.java | 7 +-- .../ru/javawebinar/topjava/model/User.java | 3 +- .../topjava/web/json/JacksonObjectMapper.java | 31 +++++++++++ src/main/resources/spring/spring-mvc.xml | 33 +++++++----- 5 files changed, 86 insertions(+), 42 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java diff --git a/pom.xml b/pom.xml index 8c0f0a6222ec..6345a1411b94 100644 --- a/pom.xml +++ b/pom.xml @@ -103,28 +103,28 @@ - + @@ -232,6 +232,16 @@ jackson-databind ${jackson-json.version} + + com.fasterxml.jackson.datatype + 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/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index a1939f350b40..8ba10962ba0b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,20 +1,15 @@ 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; - - @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) -@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) +//@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public abstract class AbstractBaseEntity 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 50a11574b4a9..84a5d0ec7779 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,6 +1,5 @@ package ru.javawebinar.topjava.model; -import com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -66,7 +65,7 @@ public class User extends AbstractNamedEntity { @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/web/json/JacksonObjectMapper.java b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java new file mode 100644 index 000000000000..47a13a9a6174 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -0,0 +1,31 @@ +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.SerializationFeature; +import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +/** + *

    + * 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 JacksonObjectMapper() { + registerModule(new Hibernate5Module()); + + registerModule(new JavaTimeModule()); + configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + + 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/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index ce12ddcc64d5..06fd91206891 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -5,7 +5,16 @@ 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"> - + + + + + + + + + + @@ -16,17 +25,17 @@ p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> - + From a8f03b9d066407b1051e6c24cebfe1a5c9c4918c Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 24 Dec 2017 14:39:48 +0600 Subject: [PATCH 084/135] 7_11 json assert tests --- pom.xml | 7 ++ .../topjava/web/json/JacksonObjectMapper.java | 6 ++ .../topjava/web/json/JsonUtil.java | 61 +++++++++++++++++ src/main/resources/spring/spring-mvc.xml | 7 +- .../java/ru/javawebinar/topjava/TestUtil.java | 22 +++++++ .../ru/javawebinar/topjava/UserTestData.java | 13 +++- .../topjava/web/json/JsonUtilTest.java | 27 ++++++++ .../web/user/AdminRestControllerTest.java | 66 ++++++++++++++++++- .../web/user/ProfileRestControllerTest.java | 47 +++++++++++++ 9 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java create mode 100644 src/test/java/ru/javawebinar/topjava/TestUtil.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java diff --git a/pom.xml b/pom.xml index 6345a1411b94..2c0e2184251a 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ 4.12 1.3 + 1.5.0 5.2.12.Final @@ -262,6 +263,12 @@ ${hamcrest.version} test + + org.skyscreamer + jsonassert + ${jsonassert.version} + test + org.springframework 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 47a13a9a6174..28474a7d5676 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -18,6 +18,8 @@ */ public class JacksonObjectMapper extends ObjectMapper { + private static final ObjectMapper MAPPER = new JacksonObjectMapper(); + private JacksonObjectMapper() { registerModule(new Hibernate5Module()); @@ -28,4 +30,8 @@ private JacksonObjectMapper() { setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); setSerializationInclusion(JsonInclude.Include.NON_NULL); } + + public static ObjectMapper getMapper() { + return MAPPER; + } } \ No newline at end of file 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..dcb6d6543fe5 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java @@ -0,0 +1,61 @@ +package ru.javawebinar.topjava.web.json; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectReader; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +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); + } + } + + public static String writeIgnoreProps(Collection collection, String... ignoreProps) { + List> list = collection.stream() + .map(e -> getAsMapWithIgnore(e, ignoreProps)) + .collect(Collectors.toList()); + return writeValue(list); + } + + public static String writeIgnoreProps(T obj, String... ignoreProps) { + Map map = getAsMapWithIgnore(obj, ignoreProps); + return writeValue(map); + } + + private static Map getAsMapWithIgnore(T obj, String[] ignoreProps) { + Map map = getMapper().convertValue(obj, new TypeReference>() {}); + for (String prop : ignoreProps) { + map.remove(prop); + } + return map; + } +} diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 06fd91206891..56dce69916bd 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -5,17 +5,16 @@ 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/TestUtil.java b/src/test/java/ru/javawebinar/topjava/TestUtil.java new file mode 100644 index 000000000000..fe9e35f96b1f --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava; + +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.web.json.JsonUtil; + +import java.io.UnsupportedEncodingException; + +public class TestUtil { + + public static String getContent(ResultActions action) throws UnsupportedEncodingException { + return action.andReturn().getResponse().getContentAsString(); + } + + public static ResultActions print(ResultActions action) throws UnsupportedEncodingException { + System.out.println(getContent(action)); + return action; + } + + public static T readFromJson(ResultActions action, Class clazz) throws UnsupportedEncodingException { + return JsonUtil.readValue(getContent(action), clazz); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index f19252274108..361b25a266c7 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -1,12 +1,15 @@ package ru.javawebinar.topjava; +import org.springframework.test.web.servlet.ResultMatcher; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; +import static ru.javawebinar.topjava.web.json.JsonUtil.writeIgnoreProps; public class UserTestData { public static final int USER_ID = START_SEQ; @@ -26,4 +29,12 @@ public static void assertMatch(Iterable actual, User... expected) { public static void assertMatch(Iterable actual, Iterable expected) { assertThat(actual).usingElementComparatorIgnoringFields("registered", "meals").isEqualTo(expected); } -} + + public static ResultMatcher contentJson(User... expected) { + return content().json(writeIgnoreProps(Arrays.asList(expected), "registered")); + } + + public static ResultMatcher contentJson(User expected) { + return content().json(writeIgnoreProps(expected, "registered")); + } +} \ No newline at end of file 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..458700a56bd6 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.web.json; + +import org.junit.Test; +import ru.javawebinar.topjava.model.Meal; + +import java.util.List; + +import static ru.javawebinar.topjava.MealTestData.*; + +public class JsonUtilTest { + + @Test + public void testReadWriteValue() throws Exception { + String json = JsonUtil.writeValue(ADMIN_MEAL1); + System.out.println(json); + Meal meal = JsonUtil.readValue(json, Meal.class); + assertMatch(meal, ADMIN_MEAL1); + } + + @Test + public void testReadWriteValues() throws Exception { + String json = JsonUtil.writeValue(MEALS); + System.out.println(json); + List meals = JsonUtil.readValues(json, Meal.class); + assertMatch(meals, MEALS); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 7b9d2a82b5d5..daa29d07cbf0 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -2,13 +2,20 @@ 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.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 { @@ -20,6 +27,59 @@ 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(contentJson(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(contentJson(USER)); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + USER_ID)) + .andDo(print()) + .andExpect(status().isOk()); + assertMatch(userService.getAll(), ADMIN); + } + + @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()); + + assertMatch(userService.get(USER_ID), updated); + } + + @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 = TestUtil.readFromJson(action, User.class); + expected.setId(returned.getId()); + + assertMatch(returned, expected); + assertMatch(userService.getAll(), ADMIN, expected, USER); + } + + @Test + public void testGetAll() throws Exception { + TestUtil.print(mockMvc.perform(get(REST_URL)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(contentJson(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..c97b309ffa2f --- /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 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(contentJson(USER)) + ); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL)) + .andExpect(status().isOk()); + assertMatch(userService.getAll(), ADMIN); + } + + @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()); + + assertMatch(new User(userService.getByEmail("newemail@ya.ru")), updated); + } +} \ No newline at end of file From 04f05c65e683299699eccdf0ef38199c4bf42766 Mon Sep 17 00:00:00 2001 From: "aleksandrfab@gmail.com" Date: Sun, 24 Dec 2017 14:53:38 +0600 Subject: [PATCH 085/135] 7_12 soapui utf8 converter --- config/Topjava-soapui-project.xml | 17 +++++++++++++++++ .../topjava/web/user/ProfileRestController.java | 5 +++++ src/main/resources/spring/spring-mvc.xml | 8 ++++++++ 3 files changed, 30 insertions(+) create mode 100644 config/Topjava-soapui-project.xml diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml new file mode 100644 index 000000000000..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 c1775ac88715..3b378d91803a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -24,4 +24,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 56dce69916bd..b81d43aa8da2 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -12,6 +12,14 @@ + + + + text/plain;charset=UTF-8 + text/html;charset=UTF-8 + + + From 5c4df1e2fdc4b05bce0c214667d7fe134d07f704 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 09:27:42 +0700 Subject: [PATCH 086/135] 8_01 HW7 controller test --- .../topjava/to/MealWithExceed.java | 18 +++++++++++++++++ src/main/resources/logback.xml | 3 ++- .../topjava/web/ResourceControllerTest.java | 20 +++++++++++++++++++ .../topjava/web/RootControllerTest.java | 14 +++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java index c8ab125a80a4..4fc1789a7136 100644 --- a/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.to; import java.time.LocalDateTime; +import java.util.Objects; public class MealWithExceed { private final Integer id; @@ -41,6 +42,23 @@ public boolean isExceed() { return exceed; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MealWithExceed that = (MealWithExceed) o; + return calories == that.calories && + exceed == that.exceed && + Objects.equals(id, that.id) && + Objects.equals(dateTime, that.dateTime) && + Objects.equals(description, that.description); + } + + @Override + public int hashCode() { + return Objects.hash(id, dateTime, description, calories, exceed); + } + @Override public String toString() { return "MealWithExceed{" + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 809d4c9c3d50..7f5b7b049490 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -21,7 +21,8 @@ - + + diff --git a/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java new file mode 100644 index 000000000000..de7bc0e6c11d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Test; +import org.springframework.http.MediaType; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class ResourceControllerTest extends AbstractControllerTest { + + @Test + public void testResources() throws Exception { + mockMvc.perform(get("/resources/css/style.css")) + .andDo(print()) + .andExpect(content().contentType(MediaType.valueOf("text/css"))) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index dfe9870e413e..597f893197a5 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,6 +1,9 @@ package ru.javawebinar.topjava.web; import org.junit.Test; +import ru.javawebinar.topjava.AuthorizedUser; +import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.util.MealsUtil; import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -26,4 +29,15 @@ public void testUsers() throws Exception { ) ))); } + + @Test + public void testMeals() throws Exception { + mockMvc.perform(get("/meals")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(view().name("meals")) + .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")) + .andExpect(model().attribute("meals", MealsUtil.getWithExceeded(MealTestData.MEALS, AuthorizedUser.getCaloriesPerDay()))); + } + } \ No newline at end of file From df7ef7a305e2a5a1af1b06bc43369a8b4103600a Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 09:42:15 +0700 Subject: [PATCH 087/135] 8_02 HW7 rest controller --- .../javawebinar/topjava/util/MealsUtil.java | 2 +- .../topjava/web/meal/MealRestController.java | 58 +++++++++++- .../java/ru/javawebinar/topjava/TestUtil.java | 12 +++ .../web/meal/MealRestControllerTest.java | 90 +++++++++++++++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index b7c9a769f776..28ec36fd4034 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -36,7 +36,7 @@ public static List getFilteredWithExceeded(Collection meal .collect(toList()); } - private static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } 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 c3daf68535d7..7577db296713 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,7 +1,61 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.stereotype.Controller; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.to.MealWithExceed; -@Controller +import java.net.URI; +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping(value = MealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) public class MealRestController extends AbstractMealController { + static final String REST_URL = "/rest/profile/meals"; + + @Override + @GetMapping("/{id}") + public Meal get(@PathVariable("id") int id) { + return super.get(id); + } + + @Override + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @Override + @GetMapping + public List getAll() { + return super.getAll(); + } + + @Override + @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) + public void update(@RequestBody Meal meal, @PathVariable("id") int id) { + super.update(meal, id); + } + + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity createWithLocation(@RequestBody Meal meal) { + Meal created = super.create(meal); + + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(REST_URL + "/{id}") + .buildAndExpand(created.getId()).toUri(); + + return ResponseEntity.created(uriOfNewResource).body(created); + } + + @GetMapping(value = "/between") + public List getBetween( + @RequestParam(value = "startDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDateTime, + @RequestParam(value = "endDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDateTime) { + return super.getBetween(startDateTime.toLocalDate(), startDateTime.toLocalTime(), endDateTime.toLocalDate(), endDateTime.toLocalTime()); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/TestUtil.java b/src/test/java/ru/javawebinar/topjava/TestUtil.java index fe9e35f96b1f..15361c1a28b0 100644 --- a/src/test/java/ru/javawebinar/topjava/TestUtil.java +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -1,10 +1,14 @@ package ru.javawebinar.topjava; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; import ru.javawebinar.topjava.web.json.JsonUtil; import java.io.UnsupportedEncodingException; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static ru.javawebinar.topjava.web.json.JsonUtil.writeValue; + public class TestUtil { public static String getContent(ResultActions action) throws UnsupportedEncodingException { @@ -19,4 +23,12 @@ public static ResultActions print(ResultActions action) throws UnsupportedEncodi public static T readFromJson(ResultActions action, Class clazz) throws UnsupportedEncodingException { return JsonUtil.readValue(getContent(action), clazz); } + + public static ResultMatcher contentJson(T expected) { + return content().json(writeValue(expected)); + } + + public static ResultMatcher contentJsonArray(T... expected) { + return contentJson(expected); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java new file mode 100644 index 000000000000..83552afa4716 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -0,0 +1,90 @@ +package ru.javawebinar.topjava.web.meal; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.TestUtil; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.web.AbstractControllerTest; +import ru.javawebinar.topjava.web.json.JsonUtil; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.TestUtil.contentJson; +import static ru.javawebinar.topjava.TestUtil.contentJsonArray; +import static ru.javawebinar.topjava.UserTestData.USER; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class MealRestControllerTest extends AbstractControllerTest { + + private static final String REST_URL = MealRestController.REST_URL + '/'; + + @Autowired + private MealService service; + + @Test + public void testGet() throws Exception { + mockMvc.perform(get(REST_URL + MEAL1_ID)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(contentJson(MEAL1)); + } + + @Test + public void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + MEAL1_ID)) + .andExpect(status().isOk()); + assertMatch(service.getAll(START_SEQ), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); + } + + @Test + public void testUpdate() throws Exception { + Meal updated = getUpdated(); + + mockMvc.perform(put(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated))) + .andExpect(status().isOk()); + + assertMatch(service.get(MEAL1_ID, START_SEQ), updated); + } + + @Test + public void testCreate() throws Exception { + Meal created = getCreated(); + ResultActions action = mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(created))); + + Meal returned = TestUtil.readFromJson(action, Meal.class); + created.setId(returned.getId()); + + assertMatch(returned, created); + assertMatch(service.getAll(START_SEQ), created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + } + + @Test + public void testGetAll() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(contentJson(MealsUtil.getWithExceeded(MEALS, USER.getCaloriesPerDay()))); + } + + @Test + public void testGetBetween() throws Exception { + mockMvc.perform(get(REST_URL + "between?startDateTime=2015-05-30T07:00&endDateTime=2015-05-31T11:00:00")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(contentJsonArray( + MealsUtil.createWithExceed(MEAL4, true), + MealsUtil.createWithExceed(MEAL1, false))); + } +} \ No newline at end of file From bfdcb657eb9e8e18209b4ae634f990e379b76720 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 09:54:05 +0700 Subject: [PATCH 088/135] 8_03 HW7 formatters --- .../web/converter/DateTimeFormatters.java | 44 +++++++++++++++++++ .../topjava/web/meal/MealRestController.java | 13 +++--- src/main/resources/spring/spring-mvc.xml | 11 ++++- .../web/meal/MealRestControllerTest.java | 16 ++++++- 4 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java diff --git a/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java b/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java new file mode 100644 index 000000000000..c308ec9df57f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java @@ -0,0 +1,44 @@ +package ru.javawebinar.topjava.web.converter; + +import org.springframework.format.Formatter; + +import java.text.ParseException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; + +/** + * gkislin + * 25.10.2016 + */ +public class DateTimeFormatters { + public static class LocalDateFormatter implements Formatter { + + @Override + public LocalDate parse(String text, Locale locale) throws ParseException { + return parseLocalDate(text); + } + + @Override + public String print(LocalDate lt, Locale locale) { + return lt.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + } + + public static class LocalTimeFormatter implements Formatter { + + @Override + public LocalTime parse(String text, Locale locale) throws ParseException { + return parseLocalTime(text); + } + + @Override + public String print(LocalTime lt, Locale locale) { + return lt.format(DateTimeFormatter.ISO_LOCAL_TIME); + } + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index 7577db296713..7362ecaed39d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,6 +1,5 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -9,7 +8,8 @@ import ru.javawebinar.topjava.to.MealWithExceed; import java.net.URI; -import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.LocalTime; import java.util.List; @RestController @@ -52,10 +52,11 @@ public ResponseEntity createWithLocation(@RequestBody Meal meal) { return ResponseEntity.created(uriOfNewResource).body(created); } - @GetMapping(value = "/between") + @Override + @GetMapping(value = "/filter") public List getBetween( - @RequestParam(value = "startDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDateTime, - @RequestParam(value = "endDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDateTime) { - return super.getBetween(startDateTime.toLocalDate(), startDateTime.toLocalTime(), endDateTime.toLocalDate(), endDateTime.toLocalTime()); + @RequestParam(value = "startDate", required = false) LocalDate startDate, @RequestParam(value = "startTime", required = false) LocalTime startTime, + @RequestParam(value = "endDate", required = false) LocalDate endDate, @RequestParam(value = "endTime", required = false) LocalTime endTime) { + return super.getBetween(startDate, startTime, endDate, endTime); } } \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index b81d43aa8da2..68f5ca9ea5b6 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -7,7 +7,7 @@ - + @@ -23,6 +23,15 @@ + + + + + + + + + diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 83552afa4716..f44606029637 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -11,6 +11,8 @@ import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; +import java.util.Arrays; + 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; @@ -79,12 +81,22 @@ public void testGetAll() throws Exception { } @Test - public void testGetBetween() throws Exception { - mockMvc.perform(get(REST_URL + "between?startDateTime=2015-05-30T07:00&endDateTime=2015-05-31T11:00:00")) + public void testFilter() throws Exception { + mockMvc.perform(get(REST_URL + "filter") + .param("startDate", "2015-05-30").param("startTime", "07:00") + .param("endDate", "2015-05-31").param("endTime", "11:00")) .andExpect(status().isOk()) .andDo(print()) .andExpect(contentJsonArray( MealsUtil.createWithExceed(MEAL4, true), MealsUtil.createWithExceed(MEAL1, false))); } + + @Test + public void testFilterAll() throws Exception { + mockMvc.perform(get(REST_URL + "filter?startDate=&endTime=")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(contentJson(MealsUtil.getWithExceeded(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), USER.getCaloriesPerDay()))); + } } \ No newline at end of file From 2736761a4154dde98c203e5a5860fac1f8209037 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 09:55:52 +0700 Subject: [PATCH 089/135] 8_04 HW7 soapui curl --- config/Topjava-soapui-project.xml | 587 +++++++++++++++++++++++++++++- config/curl.md | 29 ++ 2 files changed, 600 insertions(+), 16 deletions(-) create mode 100644 config/curl.md diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml index 4f3f6179155e..c52ef2a0fbb5 100644 --- a/config/Topjava-soapui-project.xml +++ b/config/Topjava-soapui-project.xml @@ -1,17 +1,572 @@ -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 + + + + + + + http://localhost:8080 + + + + + + + + + text/html;charset=utf-8 + 500 401 403 + + html + + + application/json + 200 + + ns:Response + + + + 0 + + data + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/admin/users + + admin@gmail.com + admin + Basic + Basic + Global HTTP Settings + + + + + + + + + + + application/json + + + + text/html;charset=utf-8 + 500 + + html + + + application/json + 201 + + user:Response + + + application/json;charset=UTF-8 + 201 + + user:Response + + + + <xml-fragment/> + + http://localhost:8080 + { + "name": "New2", + "email": "new2@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] + } + + http://localhost/topjava/rest/admin/users + + admin@gmail.comadminBasic + Basic + Global HTTP Settings + + + + + + + + + + + + + + + text/html;charset=utf-8 + 500 401 + + html + + + application/json + 200 + + ns:Response + + + application/json;charset=UTF-8 + 200 + + ns:Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/admin/users/100000 + + admin@gmail.comadminBasic + Basic + Global HTTP Settings + + + + + + + + + + + text/html;charset=utf-8 + 405 500 + + html + + + application/json + + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + 200data + + <xml-fragment/> + + http://localhost:8080 + { + "name": "UserUpdated", + "email": "user@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] + } + + http://localhost/topjava/rest/admin/users/100000 + + admin@gmail.comadminBasic + Basic + Global HTTP Settings + + + + + + + + + + + + + + + text/html;charset=utf-8 + 500 401 + + html + + + application/json + 200 + + ns:Response + + + application/json;charset=UTF-8 + 200 + + prof:Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile + + user@yandex.rupasswordBasic + Basic + Global HTTP Settings + + + + + + + + + + + application/json + + + + text/html;charset=utf-8 + 500 405 + + html + + + application/json + 201 + + user:Response + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + 200data + + <xml-fragment/> + + http://localhost:8080 + { + "name": "New777", + "email": "new777@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] +} + + http://localhost/topjava/rest/profile + + user@yandex.rupasswordBasic + Basic + Global HTTP Settings + + + + + + + + + + + + 200 + + data + + + text/html;charset=utf-8 + 500 + + html + + + application/json + + + + + 200 + + data + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile + + No Authorization + Basic + Global HTTP Settings + + + + + + + + + + + + + + + application/json;charset=UTF-8 + 200 + + Response + + text/html;charset=utf-8401html + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals + + user@yandex.rupasswordBasicBasicGlobal HTTP Settings + + + + + + + + + + + text/html;charset=utf-8 + 400 401 + + html + + + application/json + + + + + 0 + + data + + + application/json;charset=UTF-8 + 201 + + meal:Response + + + + <xml-fragment/> + true + UTF-8http://localhost:8080 + { + "dateTime": "2015-06-01T10:00", + "description": "Новый завтрак", + "calories": 777 +} + + http://localhost/topjava/rest/profile/meals + + user@yandex.rupasswordBasicBasicGlobal HTTP Settings + + + + + + + + + + + + + + + application/json;charset=UTF-8 + 200 + + ns:Response + + text/html;charset=utf-8401html + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals/100002 + + user@yandex.rupasswordBasicBasicGlobal HTTP Settings + + + + + + + + + + + text/html;charset=utf-8 + 400 401 + + html + + + application/json + + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + 200data200data200data + + <xml-fragment/> + + UTF-8http://localhost:8080 + { + "id": 100002, + "dateTime": "2015-05-30T10:00", + "description": "Обновленный завтрак", + "calories": 500 + } + + http://localhost/topjava/rest/profile/meals/100002 + + user@yandex.rupasswordBasicBasicGlobal HTTP Settings + + + + + + + + + + + + + startDate + + QUERY + + + + endTimeQUERY + + + + + application/json;charset=UTF-8 + 200 + + Response + + text/html;charset=utf-8401html + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals/filter + + user@yandex.rupasswordBasicBasicGlobal HTTP Settings + + + + + + + + + startDateendTime + + + + + + + + + \ No newline at end of file diff --git a/config/curl.md b/config/curl.md new file mode 100644 index 000000000000..c6bb0b8ebcc7 --- /dev/null +++ b/config/curl.md @@ -0,0 +1,29 @@ +### Test MealRestController (application deployed in application context `topjava`). +> For windows use `Git Bash` + +#### get All Users +`curl -s http://localhost:8080/topjava/rest/admin/users` + +#### get Users 100001 +`curl -s http://localhost:8080/topjava/rest/admin/users/100001` + +#### get All Meals +`curl -s http://localhost:8080/topjava/rest/profile/meals` + +#### get Meals 100003 +`curl -s http://localhost:8080/topjava/rest/profile/meals/100003` + +#### filter Meals +`curl -s "http://localhost:8080/topjava/rest/profile/meals/filter?startDate=2015-05-30&startTime=07:00:00&endDate=2015-05-31&endTime=11:00:00"` + +#### get Meals not found +`curl -s -v http://localhost:8080/topjava/rest/profile/meals/100008` + +#### delete Meals +`curl -s -X DELETE http://localhost:8080/topjava/rest/profile/meals/100002` + +#### create Meals +`curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals` + +#### update Meals +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003` \ No newline at end of file From be23081bbf66eac054160978a1b0fb1b259870f3 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 10:11:57 +0700 Subject: [PATCH 090/135] 8_05 webjars --- pom.xml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/pom.xml b/pom.xml index 2c0e2184251a..5e61bd40c1b0 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,13 @@ 3.4.0 + + + 3.3.7-1 + 3.2.1 + 2.5.11 + 3.1.0 + 1.10.16 @@ -227,6 +234,51 @@ 1.2 + + + org.webjars.bower + jquery + ${webjars-jquery.version} + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + org.webjars + jquery + + + + + org.webjars + datatables + ${webjars-datatables.version} + + + org.webjars.bower + jquery + + + + + org.webjars.bower + datetimepicker + ${webjars-datetimepicker.version} + + + org.webjars.bower + jquery + + + + + org.webjars.bower + noty + ${webjars-noty.version} + + com.fasterxml.jackson.core From 100930ff709c46d43baf24c73a60e35a06e7f090 Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 10:28:07 +0700 Subject: [PATCH 091/135] 8_06 bootstrap --- config/messages/app.properties | 3 + config/messages/app_ru.properties | 12 ++++ src/main/resources/spring/spring-mvc.xml | 3 + .../WEB-INF/jsp/fragments/bodyHeader.jsp | 16 ++++- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 8 ++- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 3 + src/main/webapp/WEB-INF/jsp/index.jsp | 30 +++++---- src/main/webapp/WEB-INF/jsp/users.jsp | 58 ++++++++++-------- src/main/webapp/resources/css/style.css | 4 ++ .../webapp/resources/images/icon-meal.png | Bin 0 -> 1898 bytes 10 files changed, 97 insertions(+), 40 deletions(-) create mode 100644 src/main/webapp/resources/images/icon-meal.png diff --git a/config/messages/app.properties b/config/messages/app.properties index ab8a69745820..dcd98d4a7bfd 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -4,6 +4,8 @@ app.footer=Project Ma app.login=Login as user.title=Users +user.edit=Edit user +user.add=Add user user.name=Name user.email=Email user.roles=Roles @@ -22,6 +24,7 @@ meal.description=Description meal.dateTime=Date/Time meal.calories=Calories +common.add=Add common.select=Select common.delete=Delete common.update=Update diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 3a7b5fd698f0..7ab1a799fd82 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -10,6 +10,17 @@ user.roles=\u0420\u043E\u043B\u0438 user.active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439 user.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D +app.login=Зайти как + +user.title=Пользователи + +user.edit=Редактирование пользователя +user.add=Добавление пользователя + +user.name=Имя +user.email=Почта +user.roles=Роли + meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C @@ -27,6 +38,7 @@ meal.description=Описание meal.dateTime=Дата/Время meal.calories=Калории +common.add=Добавить common.select=Выбрать common.select=Выбрать common.delete=Удалить diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 68f5ca9ea5b6..671d73571d8e 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -37,6 +37,9 @@ + + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index fcafd0e813f2..f5f3f024e332 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,4 +1,18 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="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 0935c441a36b..aec18c705061 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,4 +1,8 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="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 0c77f10859f8..ddd4c21ed5d4 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -6,5 +6,8 @@ <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 93cbd1ed58d7..5595a2024e3f 100644 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -6,19 +6,23 @@ -
    -
    - : - - -
      -
    • -
    • -
    -
    +
    +
    +

    + +

    + : + + +
      +
    • +
    • +
    +
    +
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 997aef28b646..b73f8f1ffa38 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -8,32 +8,42 @@ -
    -

    - - - - - - - - - - - - - +
    +
    +

    +
    + + + + +

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

    + \ No newline at end of file diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index 36305de387ee..279954086eea 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -29,4 +29,8 @@ header, footer { font-size: 20px; padding: 5px 20px; margin: 6px 0; +} + +.glyphicon { + cursor: pointer; } \ No newline at end of file diff --git a/src/main/webapp/resources/images/icon-meal.png b/src/main/webapp/resources/images/icon-meal.png new file mode 100644 index 0000000000000000000000000000000000000000..b4fc54ad01294abb103d9238c12b1826a9240dfd GIT binary patch literal 1898 zcmV-w2bK7VP)PS&+k>eS5@~O7WLfN z#z>r1)zQ7@JLlXpd}L<$FKLxT1mO7i`0((sD2l48LI|y1MB+H^bUOWhKS`1&t~@IA z1b~^D88e&NXf)c{*(uAi)iCqP{SZP3!OTRY*Xx}A^;#TU>54SHnYXW#g&y6RSh99^HTs$tZ#2`udlC*h;z;d0}zWj3TB}R zFn|flWT07)I)DiT%x1GFidI)w^E?kB5K$Y-9N@(I#>U3x=4P6v0K6d;BWwP3q;Kx$ z_a^B<71hQG`=w-hwO4#}p&VpZ25mf4sA(GS{pHJ-yWQ>y2H8Vl=B=%*jg5^g%R(>^ zpsL+&Chb!3l!F8yFZyq(Lj5wj%J{QS=1FV`-RXv|gz1JiLuH$O`+tkPjc%tTC8 zFh=;G>b&~)rPnv3Ze$)5$8pm%o12^M8_WSlqmlRCIcLNj^|#;a|88fwA6w$=xgmY{ z3@O6{xx>xRAXX4z=JADR>E-A6R2)3HpZxQ}ojuAO`G7P{_xJaw(g~evn6dlC zz2#Zsoia0v$b$zDh=_^k=;$bf;GAP-3;wnBGbR$umb>)D6^v`t0WV%eKS2{4Wl=yZli zQ3XsrIx+ew0zib-1)S^QbOHd(2!?BCD8SOl{xn)BYg1tn0Wg_N*vzV`a?TO4WBc1d zR;d)!V2&CN+Nj4+MfR07QZ`!jIp;_5>jO?C-d9fIKE* zu-I^;zq+&~{XOY%# z5#czF=Rpv~PA_$85Q)&-ghCj?bBuPEs6ZGZPzDp6%=1G+xi%NO7D!e2q_RG|zaIE4tDSi`uGlo$I_gH^9nL%#7Qm zwy>};o6WrUPPl}7=~DWQ)4QejGoqQdqOrEijv55RG{6antne1h4gzPPym{gH%CI-} zh+{V%kK;IQ>CAj(647`(-rL(-UVf&o8)A;F{=@y^)=no$GbJ3?U|1qpi10xPAv8v@ zz){Gm34shDAOyfb1Qrm7fW`-5YB|H<;o)E~NYk{<`O}$Qi%}HC?(S}yru}~3 zIcGo!W-x#-pHg5VCNi_KEDsJ2h-fevsOqEEp9DC8X(Ky0I4FujRns(`7d6kT&U;_i z^>jKd%aVwCy Date: Thu, 28 Dec 2017 13:31:59 +0700 Subject: [PATCH 092/135] 8_07 ajax datatables --- config/messages/app.properties | 1 + config/messages/app_ru.properties | 1 + .../topjava/web/user/AdminAjaxController.java | 37 ++++++++++++ .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 7 +++ src/main/webapp/WEB-INF/jsp/users.jsp | 59 +++++++++++++++++-- .../webapp/resources/js/datatablesUtil.js | 44 ++++++++++++++ .../webapp/resources/js/userDatatables.js | 42 +++++++++++++ 7 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java create mode 100644 src/main/webapp/resources/js/datatablesUtil.js create mode 100644 src/main/webapp/resources/js/userDatatables.js diff --git a/config/messages/app.properties b/config/messages/app.properties index dcd98d4a7bfd..49139dd6f2d0 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -11,6 +11,7 @@ user.email=Email user.roles=Roles user.active=Active user.registered=Registered +user.password=Password meal.title=Meals meal.edit=Edit meal diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 7ab1a799fd82..2f3b97ce22b0 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -25,6 +25,7 @@ meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C user.registered=Зарегистрирован +user.password=Пароль meal.title=Моя еда meal.edit=Редактирование еды diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java new file mode 100644 index 000000000000..db140a52abcb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -0,0 +1,37 @@ +package ru.javawebinar.topjava.web.user; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +@RestController +@RequestMapping("/ajax/admin/users") +public class AdminAjaxController extends AbstractUserController { + + @Override + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + return super.getAll(); + } + + @Override + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @PostMapping + public void createOrUpdate(@RequestParam("id") Integer id, + @RequestParam("name") String name, + @RequestParam("email") String email, + @RequestParam("password") String password) { + + User user = new User(id, name, email, password, Role.ROLE_USER); + if (user.isNew()) { + super.create(user); + } + } +} diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index ddd4c21ed5d4..42cf68b4aea4 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -9,5 +9,12 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index b73f8f1ffa38..f88a5ca8a97d 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -6,18 +6,19 @@ + +


    - + -

    - +
    @@ -35,15 +36,63 @@ - + - +
    ${user.email} ${user.roles}checked/>checked id="${user.id}"/>
    + + \ No newline at end of file diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js new file mode 100644 index 000000000000..9b1c9307a5f9 --- /dev/null +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -0,0 +1,44 @@ +function makeEditable() { + $(".delete").click(function () { + deleteRow($(this).attr("id")); + }); + + $("#detailsForm").submit(function () { + save(); + return false; + }); +} + +function add() { + $("#detailsForm").find(":input").val(""); + $("#editRow").modal(); +} + +function deleteRow(id) { + $.ajax({ + url: ajaxUrl + id, + type: "DELETE", + success: function () { + updateTable(); + } + }); +} + +function updateTable() { + $.get(ajaxUrl, function (data) { + datatableApi.clear().rows.add(data).draw(); + }); +} + +function save() { + var form = $("#detailsForm"); + $.ajax({ + type: "POST", + url: ajaxUrl, + data: form.serialize(), + success: function () { + $("#editRow").modal("hide"); + updateTable(); + } + }); +} \ No newline at end of file diff --git a/src/main/webapp/resources/js/userDatatables.js b/src/main/webapp/resources/js/userDatatables.js new file mode 100644 index 000000000000..1f9c6e5a190a --- /dev/null +++ b/src/main/webapp/resources/js/userDatatables.js @@ -0,0 +1,42 @@ +var ajaxUrl = "ajax/admin/users/"; +var datatableApi; + +// $(document).ready(function () { +$(function () { + datatableApi = $("#datatable").DataTable({ + "paging": false, + "info": true, + "columns": [ + { + "data": "name" + }, + { + "data": "email" + }, + { + "data": "roles" + }, + { + "data": "enabled" + }, + { + "data": "registered" + }, + { + "defaultContent": "Edit", + "orderable": false + }, + { + "defaultContent": "Delete", + "orderable": false + } + ], + "order": [ + [ + 0, + "asc" + ] + ] + }); + makeEditable(); +}); \ No newline at end of file From ab91e5a9ede6e2e28749f3121b9cf56a38bfe42f Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 14:30:55 +0700 Subject: [PATCH 093/135] 8_08 notification --- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 2 + .../webapp/resources/js/datatablesUtil.js | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 42cf68b4aea4..2d03b845e5e7 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -10,6 +10,7 @@ + @@ -17,4 +18,5 @@ + \ No newline at end of file diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 9b1c9307a5f9..002c3235f346 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -7,6 +7,13 @@ function makeEditable() { save(); return false; }); + + $(document).ajaxError(function (event, jqXHR, options, jsExc) { + failNoty(jqXHR); + }); + + // solve problem with cache in IE: https://stackoverflow.com/a/4303862/548473 + $.ajaxSetup({cache: false}); } function add() { @@ -20,6 +27,7 @@ function deleteRow(id) { type: "DELETE", success: function () { updateTable(); + successNoty("Deleted"); } }); } @@ -39,6 +47,35 @@ function save() { success: function () { $("#editRow").modal("hide"); updateTable(); + successNoty("Saved"); } }); +} + +var failedNote; + +function closeNoty() { + if (failedNote) { + failedNote.close(); + failedNote = undefined; + } +} + +function successNoty(text) { + closeNoty(); + new Noty({ + text: "  " + text, + type: 'success', + layout: "bottomRight", + timeout: 1000 + }).show(); +} + +function failNoty(jqXHR) { + closeNoty(); + failedNote = new Noty({ + text: "  Error status: " + jqXHR.status, + type: "error", + layout: "bottomRight" + }).show(); } \ No newline at end of file From 91adca7bc07b1d952177b9cd3e4906d1c967f0ab Mon Sep 17 00:00:00 2001 From: Fabus Date: Thu, 28 Dec 2017 15:15:12 +0700 Subject: [PATCH 094/135] 8_09 add security --- config/curl.md | 18 ++++++------ pom.xml | 13 +++++++++ src/main/resources/logback.xml | 1 + src/main/resources/spring/spring-app.xml | 8 +----- src/main/resources/spring/spring-security.xml | 28 +++++++++++++++++++ src/main/webapp/WEB-INF/web.xml | 10 +++++++ 6 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 src/main/resources/spring/spring-security.xml diff --git a/config/curl.md b/config/curl.md index c6bb0b8ebcc7..8e197ec254bb 100644 --- a/config/curl.md +++ b/config/curl.md @@ -2,28 +2,28 @@ > For windows use `Git Bash` #### get All Users -`curl -s http://localhost:8080/topjava/rest/admin/users` +`curl -s http://localhost:8080/topjava/rest/admin/users --user admin@gmail.com:admin` #### get Users 100001 -`curl -s http://localhost:8080/topjava/rest/admin/users/100001` +`curl -s http://localhost:8080/topjava/rest/admin/users/100001 --user admin@gmail.com:admin` #### get All Meals -`curl -s http://localhost:8080/topjava/rest/profile/meals` +`curl -s http://localhost:8080/topjava/rest/profile/meals --user user@yandex.ru:password` #### get Meals 100003 -`curl -s http://localhost:8080/topjava/rest/profile/meals/100003` +`curl -s http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` #### filter Meals -`curl -s "http://localhost:8080/topjava/rest/profile/meals/filter?startDate=2015-05-30&startTime=07:00:00&endDate=2015-05-31&endTime=11:00:00"` +`curl -s "http://localhost:8080/topjava/rest/profile/meals/filter?startDate=2015-05-30&startTime=07:00:00&endDate=2015-05-31&endTime=11:00:00" --user user@yandex.ru:password` #### get Meals not found -`curl -s -v http://localhost:8080/topjava/rest/profile/meals/100008` +`curl -s -v http://localhost:8080/topjava/rest/profile/meals/100008 --user user@yandex.ru:password` #### delete Meals -`curl -s -X DELETE http://localhost:8080/topjava/rest/profile/meals/100002` +`curl -s -X DELETE http://localhost:8080/topjava/rest/profile/meals/100002 --user user@yandex.ru:password` #### create Meals -`curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals` +`curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals --user user@yandex.ru:password` #### update Meals -`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003` \ No newline at end of file +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5e61bd40c1b0..ed5c4c9f4780 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 8.5.24 5.0.1.RELEASE 2.0.2.RELEASE + 5.0.0.RELEASE 2.9.3 @@ -177,6 +178,18 @@ ${spring-data-jpa.version} + + + org.springframework.security + spring-security-web + ${spring-security.version} + + + org.springframework.security + spring-security-config + ${spring-security.version} + + org.hibernate diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 7f5b7b049490..dc5b67763c59 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -23,6 +23,7 @@ + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 689a9e68cd0b..b669b4c80685 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -4,14 +4,8 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - + diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml new file mode 100644 index 000000000000..dc2f5114db94 --- /dev/null +++ b/src/main/resources/spring/spring-security.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ 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 91d304f3c7d1..c471a447ca14 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,6 +37,16 @@ / + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + encodingFilter org.springframework.web.filter.CharacterEncodingFilter From 4685660427062f4e507622ace88a136373eb139b Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 9 Jan 2018 13:28:14 +0700 Subject: [PATCH 095/135] 9_0 fix --- pom.xml | 4 ++-- .../javawebinar/topjava/web/converter/DateTimeFormatters.java | 4 ---- .../ru/javawebinar/topjava/web/meal/MealRestController.java | 2 ++ .../ru/javawebinar/topjava/web/user/AdminRestController.java | 2 ++ .../javawebinar/topjava/web/user/ProfileRestController.java | 2 ++ .../javawebinar/topjava/web/meal/MealRestControllerTest.java | 2 +- .../javawebinar/topjava/web/user/AdminRestControllerTest.java | 2 +- .../topjava/web/user/ProfileRestControllerTest.java | 2 +- 8 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index ed5c4c9f4780..d21120a178af 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ UTF-8 8.5.24 - 5.0.1.RELEASE + 5.0.2.RELEASE 2.0.2.RELEASE 5.0.0.RELEASE 2.9.3 @@ -35,7 +35,7 @@ 5.2.12.Final - 6.0.5.Final + 6.0.7.Final 3.0.1-b08 diff --git a/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java b/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java index c308ec9df57f..6e4492724aff 100644 --- a/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java +++ b/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java @@ -11,10 +11,6 @@ import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; -/** - * gkislin - * 25.10.2016 - */ public class DateTimeFormatters { public static class LocalDateFormatter implements Formatter { diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index 7362ecaed39d..c395cbb5787d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.meal; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -25,6 +26,7 @@ public Meal get(@PathVariable("id") int id) { @Override @DeleteMapping("/{id}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) public void delete(@PathVariable("id") int id) { super.delete(id); } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index 1ad69faff6c3..fe869a482bf5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -42,6 +43,7 @@ public ResponseEntity createWithLocation(@RequestBody User user) { @Override @DeleteMapping(value = "/{id}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) public void delete(@PathVariable("id") int id) { super.delete(id); } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index 3b378d91803a..1def55442c56 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.AuthorizedUser; @@ -16,6 +17,7 @@ public User get() { } @DeleteMapping + @ResponseStatus(value = HttpStatus.NO_CONTENT) public void delete() { super.delete(AuthorizedUser.id()); } diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index f44606029637..89213d0bfb84 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -42,7 +42,7 @@ public void testGet() throws Exception { @Test public void testDelete() throws Exception { mockMvc.perform(delete(REST_URL + MEAL1_ID)) - .andExpect(status().isOk()); + .andExpect(status().isNoContent()); assertMatch(service.getAll(START_SEQ), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); } 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 daa29d07cbf0..3543a41c88a7 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -43,7 +43,7 @@ public void testGetByEmail() throws Exception { public void testDelete() throws Exception { mockMvc.perform(delete(REST_URL + USER_ID)) .andDo(print()) - .andExpect(status().isOk()); + .andExpect(status().isNoContent()); assertMatch(userService.getAll(), ADMIN); } diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index c97b309ffa2f..59ae122f99f6 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -30,7 +30,7 @@ public void testGet() throws Exception { @Test public void testDelete() throws Exception { mockMvc.perform(delete(REST_URL)) - .andExpect(status().isOk()); + .andExpect(status().isNoContent()); assertMatch(userService.getAll(), ADMIN); } From 9b1e6788ab94cc7fd2bf608bfddfdfd05dd1a78a Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 9 Jan 2018 14:42:05 +0700 Subject: [PATCH 096/135] 9_01 HW8 --- config/messages/app.properties | 8 +- config/messages/app_ru.properties | 11 +- .../topjava/web/meal/JspMealController.java | 70 ------- .../topjava/web/meal/MealAjaxController.java | 50 +++++ src/main/webapp/WEB-INF/jsp/mealForm.jsp | 34 ---- src/main/webapp/WEB-INF/jsp/meals.jsp | 183 +++++++++++++----- src/main/webapp/WEB-INF/jsp/users.jsp | 6 +- src/main/webapp/resources/css/style.css | 25 +-- .../webapp/resources/js/datatablesUtil.js | 21 +- .../webapp/resources/js/mealDatatables.js | 44 +++++ .../webapp/resources/js/userDatatables.js | 4 + 11 files changed, 250 insertions(+), 206 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java delete mode 100644 src/main/webapp/WEB-INF/jsp/mealForm.jsp create mode 100644 src/main/webapp/resources/js/mealDatatables.js diff --git a/config/messages/app.properties b/config/messages/app.properties index 49139dd6f2d0..cb364673d06e 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -1,5 +1,4 @@ app.title=Calories management -app.home=Home app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application app.login=Login as @@ -16,7 +15,6 @@ user.password=Password meal.title=Meals meal.edit=Edit meal meal.add=Add meal -meal.filter=Filter meal.startDate=From date meal.endDate=To date meal.startTime=From time @@ -26,8 +24,4 @@ meal.dateTime=Date/Time meal.calories=Calories common.add=Add -common.select=Select -common.delete=Delete -common.update=Update -common.save=Save -common.cancel=Cancel \ No newline at end of file +common.select=Select \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 2f3b97ce22b0..1821a6808b0d 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -1,7 +1,7 @@ -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 +app.title=Подсчет калорий +app.home=Главная +app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery +app.login=Зайти как user.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 user.name=\u0418\u043C\u044F @@ -46,4 +46,5 @@ common.delete=Удалить common.update=Обновить common.save=Сохранить common.cancel=Отмена -common.actions=Выполнить \ No newline at end of file +common.actions=Выполнить +common.select=Выбрать \ 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 deleted file mode 100644 index 527f936e60b0..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java +++ /dev/null @@ -1,70 +0,0 @@ -package ru.javawebinar.topjava.web.meal; - -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 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 { - - @GetMapping("/delete") - public String delete(HttpServletRequest request) { - super.delete(getId(request)); - return "redirect:/meals"; - } - - @GetMapping("/update") - public String update(HttpServletRequest request, Model model) { - model.addAttribute("meal", super.get(getId(request))); - return "mealForm"; - } - - @GetMapping("/create") - public String create(Model model) { - model.addAttribute("meal", new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000)); - return "mealForm"; - } - - @PostMapping - 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"; - } - - @PostMapping("/filter") - 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/MealAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java new file mode 100644 index 000000000000..898ca3864cc5 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java @@ -0,0 +1,50 @@ +package ru.javawebinar.topjava.web.meal; + +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.to.MealWithExceed; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@RestController +@RequestMapping(value = "/ajax/profile/meals") +public class MealAjaxController extends AbstractMealController { + + @Override + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + return super.getAll(); + } + + @Override + @DeleteMapping(value = "/{id}") + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @PostMapping + public void createOrUpdate(@RequestParam("id") Integer id, + @RequestParam("dateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime, + @RequestParam("description") String description, + @RequestParam("calories") int calories) { + Meal meal = new Meal(id, dateTime, description, calories); + if (meal.isNew()) { + super.create(meal); + } + } + + @Override + @PostMapping(value = "/filter", produces = MediaType.APPLICATION_JSON_VALUE) + public List getBetween( + @RequestParam(value = "startDate", required = false) LocalDate startDate, + @RequestParam(value = "startTime", required = false) LocalTime startTime, + @RequestParam(value = "endDate", required = false) LocalDate endDate, + @RequestParam(value = "endTime", required = false) LocalTime endTime) { + return super.getBetween(startDate, startTime, endDate, endTime); + } +} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/mealForm.jsp b/src/main/webapp/WEB-INF/jsp/mealForm.jsp deleted file mode 100644 index 999c1e216ddb..000000000000 --- a/src/main/webapp/WEB-INF/jsp/mealForm.jsp +++ /dev/null @@ -1,34 +0,0 @@ -<%@ 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" %> - - - - - - -
    - -

    -
    -
    - -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    - - -
    -
    - - - diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 65ceea40ad7c..a267bdcabe3b 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -5,60 +5,143 @@ + + -
    -

    +
    +
    +

    -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    - -
    -
    - -
    - - - - - - - - - - - - - - - - - - +
    +
    +
    +
    +
    +
    + + +
    + +
    + + + +
    + +
    +
    +
    + + +
    + +
    + + + +
    + +
    +
    + +
    + +
    +
    +
    + + + + +
    - <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> - <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> - <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> - ${fn:formatDateTime(meal.dateTime)} - ${meal.description}${meal.calories}
    + + + + + + + - -
    -
    + + + + + + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> + ${fn:formatDateTime(meal.dateTime)} + + ${meal.description} + ${meal.calories} + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index f88a5ca8a97d..c17165745ebc 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -39,7 +39,9 @@ checked id="${user.id}"/> - + + + @@ -83,7 +85,7 @@
    -
    diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index 279954086eea..3dc625f258ee 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -1,20 +1,3 @@ -dl { - background: none repeat scroll 0 0 #FAFAFA; - margin: 8px 0; - padding: 0; -} - -dt { - display: inline-block; - width: 170px; -} - -dd { - display: inline-block; - margin-left: 8px; - vertical-align: top; -} - .normal { color: green; } @@ -23,12 +6,8 @@ dd { color: red; } -header, footer { - background: none repeat scroll 0 0 #A6C9E2; - color: #2E6E9E; - font-size: 20px; - padding: 5px 20px; - margin: 6px 0; +.footer { + padding-bottom: 15px; } .glyphicon { diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 002c3235f346..4d26337dcb3e 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -1,13 +1,7 @@ -function makeEditable() { - $(".delete").click(function () { - deleteRow($(this).attr("id")); - }); - - $("#detailsForm").submit(function () { - save(); - return false; - }); +var form; +function makeEditable() { + form = $('#detailsForm'); $(document).ajaxError(function (event, jqXHR, options, jsExc) { failNoty(jqXHR); }); @@ -17,7 +11,7 @@ function makeEditable() { } function add() { - $("#detailsForm").find(":input").val(""); + form.find(":input").val(""); $("#editRow").modal(); } @@ -32,14 +26,11 @@ function deleteRow(id) { }); } -function updateTable() { - $.get(ajaxUrl, function (data) { - datatableApi.clear().rows.add(data).draw(); - }); +function updateTableByData(data) { + datatableApi.clear().rows.add(data).draw(); } function save() { - var form = $("#detailsForm"); $.ajax({ type: "POST", url: ajaxUrl, diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js new file mode 100644 index 000000000000..165ea9b67e03 --- /dev/null +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -0,0 +1,44 @@ +var ajaxUrl = "ajax/profile/meals/"; +var datatableApi; + +function updateTable() { + $.ajax({ + type: "POST", + url: ajaxUrl + "filter", + data: $("#filter").serialize(), + success: updateTableByData + }); +} + +$(function () { + datatableApi = $("#datatable").DataTable({ + "paging": false, + "info": true, + "columns": [ + { + "data": "dateTime" + }, + { + "data": "description" + }, + { + "data": "calories" + }, + { + "defaultContent": "Edit", + "orderable": false + }, + { + "defaultContent": "Delete", + "orderable": false + } + ], + "order": [ + [ + 0, + "desc" + ] + ] + }); + makeEditable(); +}); \ No newline at end of file diff --git a/src/main/webapp/resources/js/userDatatables.js b/src/main/webapp/resources/js/userDatatables.js index 1f9c6e5a190a..24de4a187e43 100644 --- a/src/main/webapp/resources/js/userDatatables.js +++ b/src/main/webapp/resources/js/userDatatables.js @@ -1,6 +1,10 @@ var ajaxUrl = "ajax/admin/users/"; var datatableApi; +function updateTable() { + $.get(ajaxUrl, updateTableByData); +} + // $(document).ready(function () { $(function () { datatableApi = $("#datatable").DataTable({ From 12b2cd08eaaaea45448b88e116544cc3be9987e8 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 9 Jan 2018 14:46:02 +0700 Subject: [PATCH 097/135] 9_02 HW8 clear filter --- src/main/webapp/WEB-INF/jsp/meals.jsp | 3 +++ src/main/webapp/resources/js/mealDatatables.js | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index a267bdcabe3b..871d546122f8 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -51,6 +51,9 @@
    diff --git a/src/main/webapp/resources/js/userDatatables.js b/src/main/webapp/resources/js/userDatatables.js index da051b59f703..5c9dc8a9edc6 100644 --- a/src/main/webapp/resources/js/userDatatables.js +++ b/src/main/webapp/resources/js/userDatatables.js @@ -23,6 +23,10 @@ function enable(chkbox, id) { // $(document).ready(function () { $(function () { datatableApi = $("#datatable").DataTable({ + "ajax": { + "url": ajaxUrl, + "dataSrc": "" + }, "paging": false, "info": true, "columns": [ @@ -30,24 +34,44 @@ $(function () { "data": "name" }, { - "data": "email" + "data": "email", + "render": function (data, type, row) { + if (type === "display") { + return "" + data + ""; + } + return data; + } }, { "data": "roles" }, { - "data": "enabled" + "data": "enabled", + "render": function (data, type, row) { + if (type === "display") { + return ""; + } + return data; + } }, { - "data": "registered" + "data": "registered", + "render": function (date, type, row) { + if (type === "display") { + return date.substring(0, 10); + } + return date; + } }, { - "defaultContent": "Edit", - "orderable": false + "orderable": false, + "defaultContent": "", + "render": renderEditBtn }, { - "defaultContent": "Delete", - "orderable": false + "orderable": false, + "defaultContent": "", + "render": renderDeleteBtn } ], "order": [ @@ -55,7 +79,12 @@ $(function () { 0, "asc" ] - ] + ], + "createdRow": function (row, data, dataIndex) { + if (!data.enabled) { + $(row).addClass("disabled"); + } + }, + "initComplete": makeEditable }); - makeEditable(); }); \ No newline at end of file From 63f41ab894571b37083fa3cd02b451e6c4a18565 Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 10 Jan 2018 11:17:00 +0700 Subject: [PATCH 104/135] 9_08 js i18n --- config/messages/app.properties | 7 ++++++- config/messages/app_ru.properties | 5 +++++ src/main/webapp/WEB-INF/jsp/users.jsp | 11 ++++++++++- src/main/webapp/resources/js/datatablesUtil.js | 12 +++++++----- src/main/webapp/resources/js/userDatatables.js | 2 +- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index cb364673d06e..8050dfc0fd03 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -24,4 +24,9 @@ meal.dateTime=Date/Time meal.calories=Calories common.add=Add -common.select=Select \ No newline at end of file +common.select=Select +common.deleted=Record deleted +common.saved=Record saved +common.enabled=Record enabled +common.disabled=Record disabled +common.errorStatus=Error status \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 1821a6808b0d..631ae33fbc89 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -41,6 +41,11 @@ meal.calories=Калории common.add=Добавить common.select=Выбрать +common.deleted=Запись удалена +common.saved=Запись сохранена +common.enabled=Запись активирована +common.disabled=Запись деактивирована +common.errorStatus=Статус ошибки common.select=Выбрать common.delete=Удалить common.update=Обновить diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 3ab88e59d7e5..57e3c0de9bdf 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -39,7 +39,7 @@ From 62fe3a03d2c40ef353bc486831f468cd93a9dba0 Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 10 Jan 2018 13:34:42 +0700 Subject: [PATCH 106/135] 9_10 jsp form login --- config/messages/app.properties | 2 +- config/messages/app_ru.properties | 2 +- .../topjava/web/RootController.java | 13 +-- src/main/resources/spring/spring-mvc.xml | 9 +- src/main/resources/spring/spring-security.xml | 9 +- src/main/webapp/WEB-INF/jsp/index.jsp | 28 ------ src/main/webapp/WEB-INF/jsp/login.jsp | 92 +++++++++++++++++++ src/main/webapp/resources/css/style.css | 18 ++++ src/main/webapp/test.html | 13 +++ 9 files changed, 142 insertions(+), 44 deletions(-) delete mode 100644 src/main/webapp/WEB-INF/jsp/index.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/login.jsp create mode 100644 src/main/webapp/test.html diff --git a/config/messages/app.properties b/config/messages/app.properties index 8050dfc0fd03..7deaf8f14a4b 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -1,6 +1,6 @@ app.title=Calories management app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application -app.login=Login as +app.enter=Enter as user.title=Users user.edit=Edit user diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 631ae33fbc89..e31cfc22722f 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -1,7 +1,7 @@ app.title=Подсчет калорий app.home=Главная app.footer=Приложение по проекту Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery -app.login=Зайти как +app.enter=Войти как user.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 user.name=\u0418\u043C\u044F diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 8914925f952c..f7be0f1c2309 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -4,13 +4,10 @@ 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 ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.util.MealsUtil; -import javax.servlet.http.HttpServletRequest; - @Controller public class RootController { @@ -19,7 +16,7 @@ public class RootController { @GetMapping("/") public String root() { - return "index"; + return "redirect:meals"; } @GetMapping("/users") @@ -27,11 +24,9 @@ public String users() { return "users"; } - @PostMapping("/users") - public String setUser(HttpServletRequest request) { - int userId = Integer.valueOf(request.getParameter("userId")); - AuthorizedUser.setId(userId); - return "redirect:meals"; + @GetMapping(value = "/login") + public String login() { + return "login"; } @GetMapping("/meals") diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 671d73571d8e..f5b51c0a82c6 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -1,12 +1,15 @@ - + + + diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml index 844e77310c4c..0b553b8394d5 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -17,10 +17,15 @@ + + - - + + + diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp deleted file mode 100644 index 5595a2024e3f..000000000000 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ /dev/null @@ -1,28 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> - - - - - -
    -
    -

    - -

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

    + + +

    +
    +

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

    +
    +
    +
    +
    +    Java Enterprise проект с + регистрацией/авторизацией и интерфейсом на основе ролей (USER, ADMIN). + Администратор может создавать/редактировать/удалять пользователей, а пользователи - управлять своим + профилем и данными (день, еда, калории) через UI (по AJAX) и по REST интерфейсу с базовой авторизацией. + Возможна фильтрация данных по датам и времени, при этом цвет записи таблицы еды зависит от того, превышает ли + сумма + калорий за день норму (редактируемый параметр в профиле пользователя). + Весь REST интерфейс покрывается JUnit тестами, используя Spring MVC Test и Spring Security Test. +
    +
    + + + + \ No newline at end of file diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index df82f4976793..bb06ce993a36 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -14,6 +14,24 @@ opacity: 0.3; } +.error, .message { + padding: 10px; + border-radius: 4px; + font-size: 16px; +} + +.error { + color: #a94442; + background-color: #f2dede; + border: 1px solid #ebccd1; +} + +.message { + color: #2f9635; + background-color: #c6fbc2; + border: 1px solid #9feba6; +} + .glyphicon { cursor: pointer; } \ No newline at end of file diff --git a/src/main/webapp/test.html b/src/main/webapp/test.html new file mode 100644 index 000000000000..62e151e5806f --- /dev/null +++ b/src/main/webapp/test.html @@ -0,0 +1,13 @@ + + + + + +
    + +
    + + \ No newline at end of file From d46553930691478bb71176fa26f78304d1edc043 Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 10 Jan 2018 15:15:38 +0700 Subject: [PATCH 107/135] 9_11 auth via user service --- .../javawebinar/topjava/AuthorizedUser.java | 49 +++++++++++++++---- .../ru/javawebinar/topjava/model/Role.java | 11 ++++- .../topjava/service/UserServiceImpl.java | 16 +++++- .../ru/javawebinar/topjava/to/UserTo.java | 20 +++++++- .../ru/javawebinar/topjava/util/UserUtil.java | 4 ++ src/main/resources/spring/spring-security.xml | 9 +++- .../web/user/ProfileRestControllerTest.java | 2 +- 7 files changed, 94 insertions(+), 17 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index c5ba78487453..d3d468ec21a4 100644 --- a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -1,21 +1,52 @@ package ru.javawebinar.topjava; -import ru.javawebinar.topjava.model.AbstractBaseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.UserTo; +import ru.javawebinar.topjava.util.UserUtil; -import static ru.javawebinar.topjava.util.UserUtil.DEFAULT_CALORIES_PER_DAY; +import static java.util.Objects.requireNonNull; -public class AuthorizedUser { - private static int id = AbstractBaseEntity.START_SEQ; +public class AuthorizedUser extends org.springframework.security.core.userdetails.User { + private static final long serialVersionUID = 1L; - public static int id() { - return id; + private final UserTo userTo; + + public AuthorizedUser(User user) { + super(user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, user.getRoles()); + this.userTo = UserUtil.asTo(user); + } + + public static AuthorizedUser safeGet() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) { + return null; + } + Object principal = auth.getPrincipal(); + return (principal instanceof AuthorizedUser) ? (AuthorizedUser) principal : null; } - public static void setId(int id) { - AuthorizedUser.id = id; + public static AuthorizedUser get() { + AuthorizedUser user = safeGet(); + requireNonNull(user, "No authorized user found"); + return user; + } + + public static int id() { + return get().userTo.getId(); } public static int getCaloriesPerDay() { - return DEFAULT_CALORIES_PER_DAY; + return get().userTo.getCaloriesPerDay(); + } + + public UserTo getUserTo() { + return userTo; + } + + @Override + public String toString() { + return userTo.toString(); } } \ 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 index 84d62071ad9c..e7c8739712d1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Role.java +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -1,6 +1,13 @@ package ru.javawebinar.topjava.model; -public enum Role { +import org.springframework.security.core.GrantedAuthority; + +public enum Role implements GrantedAuthority { ROLE_USER, - ROLE_ADMIN + ROLE_ADMIN; + + @Override + public String getAuthority() { + return name(); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 11ce90a1c5c5..a28a2cb27957 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -3,9 +3,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; +import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.to.UserTo; @@ -17,8 +20,8 @@ import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; -@Service -public class UserServiceImpl implements UserService { +@Service("userService") +public class UserServiceImpl implements UserService, UserDetailsService { private final UserRepository repository; @@ -82,6 +85,15 @@ public void enable(int id, boolean enabled) { repository.save(user); } + @Override + public AuthorizedUser loadUserByUsername(String email) throws UsernameNotFoundException { + User user = repository.getByEmail(email.toLowerCase()); + if (user == null) { + throw new UsernameNotFoundException("User " + email + " is not found"); + } + return new AuthorizedUser(user); + } + @Override public User getWithMeals(int id) { return checkNotFoundWithId(repository.getWithMeals(id), id); diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index 975801eb1fd1..d4833fcffeea 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -1,10 +1,16 @@ package ru.javawebinar.topjava.to; +import org.hibernate.validator.constraints.Range; +import ru.javawebinar.topjava.util.UserUtil; + import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import java.io.Serializable; -public class UserTo extends BaseTo { +public class UserTo extends BaseTo implements Serializable { + private static final long serialVersionUID = 1L; @NotBlank private String name; @@ -16,14 +22,19 @@ public class UserTo extends BaseTo { @Size(min = 5, max = 32, message = "length must between 5 and 32 characters") private String password; + @Range(min = 10, max = 10000) + @NotNull + private Integer caloriesPerDay = UserUtil.DEFAULT_CALORIES_PER_DAY; + public UserTo() { } - public UserTo(Integer id, String name, String email, String password) { + public UserTo(Integer id, String name, String email, String password, int caloriesPerDay) { super(id); this.name = name; this.email = email; this.password = password; + this.caloriesPerDay = caloriesPerDay; } public String getPassword() { @@ -50,12 +61,17 @@ public void setEmail(String email) { this.email = email; } + public Integer getCaloriesPerDay() { + return caloriesPerDay; + } + @Override public String toString() { return "UserTo{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + + ", caloriesPerDay='" + caloriesPerDay + '\'' + '}'; } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java index 9a3c0e9cbf56..e2dfdd5a9a45 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java @@ -12,6 +12,10 @@ public static User createNewFromTo(UserTo newUser) { return new User(null, newUser.getName(), newUser.getEmail().toLowerCase(), newUser.getPassword(), Role.ROLE_USER); } + public static UserTo asTo(User user) { + return new UserTo(user.getId(), user.getName(), user.getEmail(), user.getPassword(), user.getCaloriesPerDay()); + } + public static User updateFromTo(User user, UserTo userTo) { user.setName(userTo.getName()); user.setEmail(userTo.getEmail().toLowerCase()); diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml index 0b553b8394d5..cece85fda954 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -30,12 +30,19 @@ - + + \ 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 index 7dde6e1e90f0..3a46d6f6e382 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -37,7 +37,7 @@ public void testDelete() throws Exception { @Test public void testUpdate() throws Exception { - UserTo updatedTo = new UserTo(null, "newName", "newemail@ya.ru", "newPassword"); + UserTo updatedTo = new UserTo(null, "newName", "newemail@ya.ru", "newPassword", 1500); mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) .content(JsonUtil.writeValue(updatedTo))) From b39c7aa3278c8ffd3b52a8ee4d0dec803c2a654c Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 10 Jan 2018 15:48:28 +0700 Subject: [PATCH 108/135] 9_12 spring security test --- pom.xml | 5 +++ .../ru/javawebinar/topjava/SpringMain.java | 5 +++ .../java/ru/javawebinar/topjava/TestUtil.java | 18 +++++++++++ .../topjava/web/AbstractControllerTest.java | 3 ++ .../topjava/web/RootControllerTest.java | 31 ++++++++++--------- .../web/user/AdminRestControllerTest.java | 30 +++++++++++++++--- .../web/user/ProfileRestControllerTest.java | 14 +++++++-- 7 files changed, 84 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index d21120a178af..20dd185cbe8a 100644 --- a/pom.xml +++ b/pom.xml @@ -346,6 +346,11 @@ 3.8.0 test
    + + org.springframework.security + spring-security-test + ${spring-security.version} + diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 39331e507792..b3123f44550a 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -13,6 +13,9 @@ import java.util.Arrays; import java.util.List; +import static ru.javawebinar.topjava.TestUtil.mockAuthorize; +import static ru.javawebinar.topjava.UserTestData.USER; + public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management @@ -21,6 +24,8 @@ public static void main(String[] args) { appCtx.load("spring/spring-app.xml", "spring/mock.xml"); appCtx.refresh(); + mockAuthorize(USER); + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); diff --git a/src/test/java/ru/javawebinar/topjava/TestUtil.java b/src/test/java/ru/javawebinar/topjava/TestUtil.java index 15361c1a28b0..5b681ca142a3 100644 --- a/src/test/java/ru/javawebinar/topjava/TestUtil.java +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -1,7 +1,12 @@ package ru.javawebinar.topjava; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.web.json.JsonUtil; import java.io.UnsupportedEncodingException; @@ -31,4 +36,17 @@ public static ResultMatcher contentJson(T expected) { public static ResultMatcher contentJsonArray(T... expected) { return contentJson(expected); } + + public static void mockAuthorize(User user) { + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(new AuthorizedUser(user), null, user.getRoles())); + } + + public static RequestPostProcessor userHttpBasic(User user) { + return SecurityMockMvcRequestPostProcessors.httpBasic(user.getEmail(), user.getPassword()); + } + + public static RequestPostProcessor userAuth(User user) { + return SecurityMockMvcRequestPostProcessors.authentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword())); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index f2e7bb0e3db4..d881384f4b80 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -19,6 +19,8 @@ import javax.annotation.PostConstruct; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + @ContextConfiguration({ "classpath:spring/spring-app.xml", "classpath:spring/spring-mvc.xml", @@ -56,6 +58,7 @@ private void postConstruct() { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .addFilter(CHARACTER_ENCODING_FILTER) + .apply(springSecurity()) .build(); } diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index 597f893197a5..9fed82a03c04 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,43 +1,44 @@ package ru.javawebinar.topjava.web; import org.junit.Test; -import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.util.MealsUtil; -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.TestUtil.userAuth; +import static ru.javawebinar.topjava.UserTestData.ADMIN; import static ru.javawebinar.topjava.UserTestData.USER; -import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; public class RootControllerTest extends AbstractControllerTest { @Test public void testUsers() throws Exception { - mockMvc.perform(get("/users")) + mockMvc.perform(get("/users") + .with(userAuth(ADMIN))) .andDo(print()) .andExpect(status().isOk()) .andExpect(view().name("users")) - .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) - .andExpect(model().attribute("users", hasSize(2))) - .andExpect(model().attribute("users", hasItem( - allOf( - hasProperty("id", is(START_SEQ)), - hasProperty("name", is(USER.getName())) - ) - ))); + .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")); + } + + @Test + public void testUnAuth() throws Exception { + mockMvc.perform(get("/users")) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost/login")); } @Test public void testMeals() throws Exception { - mockMvc.perform(get("/meals")) + mockMvc.perform(get("/meals") + .with(userAuth(USER))) .andDo(print()) - .andExpect(status().isOk()) .andExpect(view().name("meals")) .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")) - .andExpect(model().attribute("meals", MealsUtil.getWithExceeded(MealTestData.MEALS, AuthorizedUser.getCaloriesPerDay()))); + .andExpect(model().attribute("meals", MealsUtil.getWithExceeded(MealTestData.MEALS, USER.getCaloriesPerDay()))); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 3543a41c88a7..ba4709e502a8 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -15,6 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; public class AdminRestControllerTest extends AbstractControllerTest { @@ -23,7 +24,8 @@ public class AdminRestControllerTest extends AbstractControllerTest { @Test public void testGet() throws Exception { - mockMvc.perform(get(REST_URL + ADMIN_ID)) + mockMvc.perform(get(REST_URL + ADMIN_ID) + .with(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andDo(print()) // https://jira.spring.io/browse/SPR-14472 @@ -33,20 +35,35 @@ public void testGet() throws Exception { @Test public void testGetByEmail() throws Exception { - mockMvc.perform(get(REST_URL + "by?email=" + USER.getEmail())) + mockMvc.perform(get(REST_URL + "by?email=" + ADMIN.getEmail()) + .with(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(contentJson(USER)); + .andExpect(contentJson(ADMIN)); } @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL + USER_ID)) + mockMvc.perform(delete(REST_URL + USER_ID) + .with(userHttpBasic(ADMIN))) .andDo(print()) .andExpect(status().isNoContent()); assertMatch(userService.getAll(), ADMIN); } + @Test + public void testGetUnauth() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testGetForbidden() throws Exception { + mockMvc.perform(get(REST_URL) + .with(userHttpBasic(USER))) + .andExpect(status().isForbidden()); + } + @Test public void testUpdate() throws Exception { User updated = new User(USER); @@ -54,6 +71,7 @@ public void testUpdate() throws Exception { updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); mockMvc.perform(put(REST_URL + USER_ID) .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(updated))) .andExpect(status().isOk()); @@ -65,6 +83,7 @@ public void testCreate() throws Exception { User expected = new User(null, "New", "new@gmail.com", "newPass", Role.ROLE_USER, Role.ROLE_ADMIN); ResultActions action = mockMvc.perform(post(REST_URL) .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(expected))) .andExpect(status().isCreated()); @@ -77,7 +96,8 @@ public void testCreate() throws Exception { @Test public void testGetAll() throws Exception { - TestUtil.print(mockMvc.perform(get(REST_URL)) + TestUtil.print(mockMvc.perform(get(REST_URL) + .with(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(contentJson(ADMIN, USER))); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index 3a46d6f6e382..89d90d751bef 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -13,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; @@ -21,16 +22,24 @@ public class ProfileRestControllerTest extends AbstractControllerTest { @Test public void testGet() throws Exception { TestUtil.print( - mockMvc.perform(get(REST_URL)) + mockMvc.perform(get(REST_URL) + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(contentJson(USER)) ); } + @Test + public void testGetUnauth() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isUnauthorized()); + } + @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL)) + mockMvc.perform(delete(REST_URL) + .with(userHttpBasic(USER))) .andExpect(status().isNoContent()); assertMatch(userService.getAll(), ADMIN); } @@ -40,6 +49,7 @@ public void testUpdate() throws Exception { UserTo updatedTo = new UserTo(null, "newName", "newemail@ya.ru", "newPassword", 1500); mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER)) .content(JsonUtil.writeValue(updatedTo))) .andDo(print()) .andExpect(status().isOk()); From b33405014fb9e9d421ee7aa9ef5dee0cf89b4b72 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 16 Jan 2018 10:24:28 +0700 Subject: [PATCH 109/135] 10_01 HW9 binding ajax --- .../ru/javawebinar/topjava/model/Meal.java | 8 +++--- .../topjava/util/ValidationUtil.java | 19 ++++++++++++- .../topjava/web/RootController.java | 12 +-------- .../topjava/web/meal/MealAjaxController.java | 25 ++++++++++++----- .../topjava/web/user/AdminAjaxController.java | 13 ++------- .../webapp/WEB-INF/jsp/fragments/i18n.jsp | 10 +++++++ src/main/webapp/WEB-INF/jsp/meals.jsp | 24 +++++------------ src/main/webapp/WEB-INF/jsp/users.jsp | 6 +---- .../webapp/resources/js/mealDatatables.js | 27 ++++++++++++++----- 9 files changed, 82 insertions(+), 62 deletions(-) create mode 100644 src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 2eade30b4dfb..919fcd26c726 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -3,6 +3,7 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.Range; +import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; import javax.validation.constraints.NotBlank; @@ -30,6 +31,7 @@ public class Meal extends AbstractBaseEntity { @Column(name = "date_time", nullable = false) @NotNull + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime dateTime; @Column(name = "description", nullable = false) @@ -39,12 +41,12 @@ public class Meal extends AbstractBaseEntity { @Column(name = "calories", nullable = false) @Range(min = 10, max = 5000) - private int calories; + @NotNull + private Integer calories; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) @OnDelete(action = OnDeleteAction.CASCADE) - @NotNull private User user; public Meal() { @@ -89,7 +91,7 @@ public void setDescription(String description) { this.description = description; } - public void setCalories(int calories) { + public void setCalories(Integer calories) { this.calories = calories; } diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 769f13358ebe..7507a9530dbd 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -1,9 +1,13 @@ package ru.javawebinar.topjava.util; - +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; import ru.javawebinar.topjava.HasId; import ru.javawebinar.topjava.util.exception.NotFoundException; +import java.util.StringJoiner; + public class ValidationUtil { private ValidationUtil() { @@ -53,4 +57,17 @@ public static Throwable getRootCause(Throwable t) { } return result; } + + public static ResponseEntity getErrorResponse(BindingResult result) { + StringJoiner joiner = new StringJoiner("
    "); + result.getFieldErrors().forEach( + fe -> { + String msg = fe.getDefaultMessage(); + if (!msg.startsWith(fe.getField())) { + msg = fe.getField() + ' ' + msg; + } + joiner.add(msg); + }); + return new ResponseEntity<>(joiner.toString(), HttpStatus.UNPROCESSABLE_ENTITY); + } } \ 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 f7be0f1c2309..142996663d1a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,19 +1,11 @@ package ru.javawebinar.topjava.web; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import ru.javawebinar.topjava.AuthorizedUser; -import ru.javawebinar.topjava.service.MealService; -import ru.javawebinar.topjava.util.MealsUtil; @Controller public class RootController { - @Autowired - private MealService mealService; - @GetMapping("/") public String root() { return "redirect:meals"; @@ -30,9 +22,7 @@ public String login() { } @GetMapping("/meals") - public String meals(Model model) { - model.addAttribute("meals", - MealsUtil.getWithExceeded(mealService.getAll(AuthorizedUser.id()), AuthorizedUser.getCaloriesPerDay())); + public String meals() { return "meals"; } } diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java index 898ca3864cc5..fac211ac8092 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java @@ -1,13 +1,16 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealWithExceed; +import ru.javawebinar.topjava.util.ValidationUtil; +import javax.validation.Valid; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; @@ -21,6 +24,12 @@ public List getAll() { return super.getAll(); } + @Override + @GetMapping(value = "/{id}") + public Meal get(@PathVariable("id") int id) { + return super.get(id); + } + @Override @DeleteMapping(value = "/{id}") public void delete(@PathVariable("id") int id) { @@ -28,14 +37,16 @@ public void delete(@PathVariable("id") int id) { } @PostMapping - public void createOrUpdate(@RequestParam("id") Integer id, - @RequestParam("dateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime, - @RequestParam("description") String description, - @RequestParam("calories") int calories) { - Meal meal = new Meal(id, dateTime, description, calories); + public ResponseEntity createOrUpdate(@Valid Meal meal, BindingResult result) { + if (result.hasErrors()) { + return ValidationUtil.getErrorResponse(result); + } if (meal.isNew()) { super.create(meal); + } else { + super.update(meal, meal.getId()); } + return new ResponseEntity<>(HttpStatus.OK); } @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index d15864a905ee..0f04d1c45f0c 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -8,10 +8,10 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; +import ru.javawebinar.topjava.util.ValidationUtil; import javax.validation.Valid; import java.util.List; -import java.util.StringJoiner; @RestController @RequestMapping("/ajax/admin/users") @@ -38,16 +38,7 @@ public void delete(@PathVariable("id") int id) { @PostMapping public ResponseEntity createOrUpdate(@Valid UserTo userTo, BindingResult result) { if (result.hasErrors()) { - StringJoiner joiner = new StringJoiner("
    "); - result.getFieldErrors().forEach( - fe -> { - String msg = fe.getDefaultMessage(); - if (!msg.startsWith(fe.getField())) { - msg = fe.getField() + ' ' + msg; - } - joiner.add(msg); - }); - return new ResponseEntity<>(joiner.toString(), HttpStatus.UNPROCESSABLE_ENTITY); + return ValidationUtil.getErrorResponse(result); } if (userTo.isNew()) { super.create(UserUtil.createNewFromTo(userTo)); diff --git a/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp b/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp new file mode 100644 index 000000000000..c669984f6340 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp @@ -0,0 +1,10 @@ +<%@ page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 871d546122f8..ace4499279a7 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -75,23 +75,6 @@ - - - - - <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> - <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> - <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> - ${fn:formatDateTime(meal.dateTime)} - - ${meal.description} - ${meal.calories} - - - - - - @@ -101,7 +84,7 @@ + \ No newline at end of file diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index 3e83a59937b4..15dddd2441f4 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -5,7 +5,7 @@ function updateTable() { $.ajax({ type: "POST", url: ajaxUrl + "filter", - data: $("#filter").serialize(), + data: $("#filter").serialize() }).done(updateTableByData); } @@ -16,11 +16,21 @@ function clearFilter() { $(function () { datatableApi = $("#datatable").DataTable({ + "ajax": { + "url": ajaxUrl, + "dataSrc": "" + }, "paging": false, "info": true, "columns": [ { - "data": "dateTime" + "data": "dateTime", + "render": function (date, type, row) { + if (type === 'display') { + return date.replace('T', ' ').substr(0, 16); + } + return date; + } }, { "data": "description" @@ -29,11 +39,13 @@ $(function () { "data": "calories" }, { - "defaultContent": "Edit", + "render": renderEditBtn, + "defaultContent": "", "orderable": false }, { - "defaultContent": "Delete", + "render": renderDeleteBtn, + "defaultContent": "", "orderable": false } ], @@ -42,7 +54,10 @@ $(function () { 0, "desc" ] - ] + ], + "createdRow": function (row, data, dataIndex) { + $(row).addClass(data.exceed ? 'exceeded' : 'normal'); + }, + "initComplete": makeEditable }); - makeEditable(); }); \ No newline at end of file From 4949b1d637e940c2c9dbf7820ab5bd932b368ac4 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 16 Jan 2018 10:28:43 +0700 Subject: [PATCH 110/135] 10_02 HW9 test --- .../topjava/web/RootControllerTest.java | 6 +-- .../web/meal/MealRestControllerTest.java | 39 +++++++++++++------ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index 9fed82a03c04..7b147cb48fb5 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,8 +1,6 @@ package ru.javawebinar.topjava.web; import org.junit.Test; -import ru.javawebinar.topjava.MealTestData; -import ru.javawebinar.topjava.util.MealsUtil; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -37,8 +35,6 @@ public void testMeals() throws Exception { .with(userAuth(USER))) .andDo(print()) .andExpect(view().name("meals")) - .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")) - .andExpect(model().attribute("meals", MealsUtil.getWithExceeded(MealTestData.MEALS, USER.getCaloriesPerDay()))); + .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")); } - } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 89213d0bfb84..de57341d99d1 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -18,9 +18,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.MealTestData.assertMatch; import static ru.javawebinar.topjava.TestUtil.contentJson; -import static ru.javawebinar.topjava.TestUtil.contentJsonArray; -import static ru.javawebinar.topjava.UserTestData.USER; +import static ru.javawebinar.topjava.TestUtil.*; +import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; public class MealRestControllerTest extends AbstractControllerTest { @@ -32,16 +33,24 @@ public class MealRestControllerTest extends AbstractControllerTest { @Test public void testGet() throws Exception { - mockMvc.perform(get(REST_URL + MEAL1_ID)) + mockMvc.perform(get(REST_URL + ADMIN_MEAL_ID) + .with(userHttpBasic(ADMIN))) .andExpect(status().isOk()) .andDo(print()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(contentJson(MEAL1)); + .andExpect(contentJson(ADMIN_MEAL1)); + } + + @Test + public void testGetUnauth() throws Exception { + mockMvc.perform(get(REST_URL + MEAL1_ID)) + .andExpect(status().isUnauthorized()); } @Test public void testDelete() throws Exception { - mockMvc.perform(delete(REST_URL + MEAL1_ID)) + mockMvc.perform(delete(REST_URL + MEAL1_ID) + .with(userHttpBasic(USER))) .andExpect(status().isNoContent()); assertMatch(service.getAll(START_SEQ), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); } @@ -50,8 +59,10 @@ public void testDelete() throws Exception { public void testUpdate() throws Exception { Meal updated = getUpdated(); - mockMvc.perform(put(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON) - .content(JsonUtil.writeValue(updated))) + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated)) + .with(userHttpBasic(USER))) .andExpect(status().isOk()); assertMatch(service.get(MEAL1_ID, START_SEQ), updated); @@ -62,18 +73,20 @@ public void testCreate() throws Exception { Meal created = getCreated(); ResultActions action = mockMvc.perform(post(REST_URL) .contentType(MediaType.APPLICATION_JSON) - .content(JsonUtil.writeValue(created))); + .content(JsonUtil.writeValue(created)) + .with(userHttpBasic(ADMIN))); Meal returned = TestUtil.readFromJson(action, Meal.class); created.setId(returned.getId()); assertMatch(returned, created); - assertMatch(service.getAll(START_SEQ), created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + assertMatch(service.getAll(ADMIN_ID), ADMIN_MEAL2, created, ADMIN_MEAL1); } @Test public void testGetAll() throws Exception { - mockMvc.perform(get(REST_URL)) + mockMvc.perform(get(REST_URL) + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) @@ -84,7 +97,8 @@ public void testGetAll() throws Exception { public void testFilter() throws Exception { mockMvc.perform(get(REST_URL + "filter") .param("startDate", "2015-05-30").param("startTime", "07:00") - .param("endDate", "2015-05-31").param("endTime", "11:00")) + .param("endDate", "2015-05-31").param("endTime", "11:00") + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(contentJsonArray( @@ -94,7 +108,8 @@ public void testFilter() throws Exception { @Test public void testFilterAll() throws Exception { - mockMvc.perform(get(REST_URL + "filter?startDate=&endTime=")) + mockMvc.perform(get(REST_URL + "filter?startDate=&endTime=") + .with(userHttpBasic(USER))) .andExpect(status().isOk()) .andDo(print()) .andExpect(contentJson(MealsUtil.getWithExceeded(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), USER.getCaloriesPerDay()))); From 3fbca7da2b195c8191b8a1dfb28e832895309794 Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 17 Jan 2018 11:14:01 +0700 Subject: [PATCH 111/135] 10_03 HW9 datetimepicker --- .../ru/javawebinar/topjava/model/Meal.java | 3 +- .../topjava/util/DateTimeUtil.java | 3 +- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 2 ++ src/main/webapp/WEB-INF/jsp/meals.jsp | 26 +++++++------- .../webapp/resources/js/datatablesUtil.js | 8 ++++- .../webapp/resources/js/mealDatatables.js | 35 ++++++++++++++++++- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 919fcd26c726..0880a338a886 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -4,6 +4,7 @@ import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.Range; import org.springframework.format.annotation.DateTimeFormat; +import ru.javawebinar.topjava.util.DateTimeUtil; import javax.persistence.*; import javax.validation.constraints.NotBlank; @@ -31,7 +32,7 @@ public class Meal extends AbstractBaseEntity { @Column(name = "date_time", nullable = false) @NotNull - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN) private LocalDateTime dateTime; @Column(name = "description", nullable = false) diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 11269a5ede40..2025cdf2341f 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -8,7 +8,8 @@ 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 String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm"; + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN); public static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 2d03b845e5e7..2e0e09eda9e3 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -11,6 +11,7 @@ + @@ -19,4 +20,5 @@ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index ace4499279a7..8a4e6ce49e1b 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -19,33 +19,33 @@
    - -
    - +
    +
    - -
    - +
    +
    - -
    - +
    +
    - -
    - +
    +
    @@ -95,7 +95,7 @@ code="meal.dateTime"/>
    - ">
    diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 9960aef7126e..6a00b8413f6a 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -20,12 +20,18 @@ function updateRow(id) { $("#modalTitle").html(i18n["editTitle"]); $.get(ajaxUrl + id, function (data) { $.each(data, function (key, value) { - form.find("input[name='" + key + "']").val(value); + form.find("input[name='" + key + "']").val( + key === "dateTime" ? formatDate(value) : value + ); }); $('#editRow').modal(); }); } +function formatDate(date) { + return date.replace('T', ' ').substr(0, 16); +} + function deleteRow(id) { $.ajax({ url: ajaxUrl + id, diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index 15dddd2441f4..e77c3deb07e7 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -27,7 +27,7 @@ $(function () { "data": "dateTime", "render": function (date, type, row) { if (type === 'display') { - return date.replace('T', ' ').substr(0, 16); + return formatDate(date); } return date; } @@ -60,4 +60,37 @@ $(function () { }, "initComplete": makeEditable }); + +// http://xdsoft.net/jqplugins/datetimepicker/ + var startDate = $('#startDate'); + var endDate = $('#endDate'); + startDate.datetimepicker({ + timepicker: false, + format: 'Y-m-d', + formatDate: 'Y-m-d', + onShow: function (ct) { + this.setOptions({ + maxDate: endDate.val() ? endDate.val() : false + }) + } + }); + endDate.datetimepicker({ + timepicker: false, + format: 'Y-m-d', + formatDate: 'Y-m-d', + onShow: function (ct) { + this.setOptions({ + minDate: startDate.val() ? startDate.val() : false + }) + } + }); + + $('#startTime, #endTime').datetimepicker({ + datepicker: false, + format: 'H:i' + }); + + $('#dateTime').datetimepicker({ + format: 'Y-m-d H:i' + }); }); \ No newline at end of file From 1c35ff59bf7cad3c36e27dd67ab20fe7d3393b4b Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 17 Jan 2018 15:54:18 +0700 Subject: [PATCH 112/135] 10_04 jquery converters --- .../webapp/resources/js/datatablesUtil.js | 12 ++++------- .../webapp/resources/js/mealDatatables.js | 21 ++++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 6a00b8413f6a..3c6910c36d9b 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -20,18 +20,12 @@ function updateRow(id) { $("#modalTitle").html(i18n["editTitle"]); $.get(ajaxUrl + id, function (data) { $.each(data, function (key, value) { - form.find("input[name='" + key + "']").val( - key === "dateTime" ? formatDate(value) : value - ); + form.find("input[name='" + key + "']").val(value); }); $('#editRow').modal(); }); } -function formatDate(date) { - return date.replace('T', ' ').substr(0, 16); -} - function deleteRow(id) { $.ajax({ url: ajaxUrl + id, @@ -79,8 +73,10 @@ function successNoty(key) { function failNoty(jqXHR) { closeNoty(); + // https://stackoverflow.com/questions/48229776 + var responseJSON = JSON.parse(jqXHR.responseText); failedNote = new Noty({ - text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + (jqXHR.responseJSON ? "
    " + jqXHR.responseJSON : ""), + text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + (responseJSON ? "
    " + responseJSON : ""), type: "error", layout: "bottomRight" }).show(); diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index e77c3deb07e7..17e7a90feab2 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -14,6 +14,19 @@ function clearFilter() { $.get(ajaxUrl, updateTableByData); } +// http://api.jquery.com/jQuery.ajax/#using-converters +$.ajaxSetup({ + converters: { + "text json": function (stringData) { + var json = JSON.parse(stringData); + $(json).each(function () { + this.dateTime = this.dateTime.replace('T', ' ').substr(0, 16); + }); + return json; + } + } +}); + $(function () { datatableApi = $("#datatable").DataTable({ "ajax": { @@ -24,13 +37,7 @@ $(function () { "info": true, "columns": [ { - "data": "dateTime", - "render": function (date, type, row) { - if (type === 'display') { - return formatDate(date); - } - return date; - } + "data": "dateTime" }, { "data": "description" From 0bed56db66d0539735ba2063749dd338d30ea318 Mon Sep 17 00:00:00 2001 From: Fabus Date: Wed, 17 Jan 2018 16:00:09 +0700 Subject: [PATCH 113/135] 10_05 persist validate group --- src/main/java/ru/javawebinar/topjava/View.java | 7 +++++++ src/main/java/ru/javawebinar/topjava/model/Meal.java | 2 ++ src/main/resources/spring/spring-db.xml | 3 +++ 3 files changed, 12 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/View.java diff --git a/src/main/java/ru/javawebinar/topjava/View.java b/src/main/java/ru/javawebinar/topjava/View.java new file mode 100644 index 000000000000..62c332bcf970 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/View.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava; + +import javax.validation.groups.Default; + +public class View { + public interface Persist extends Default {} +} \ 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 0880a338a886..5b6fb05cde98 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -4,6 +4,7 @@ import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.Range; import org.springframework.format.annotation.DateTimeFormat; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.util.DateTimeUtil; import javax.persistence.*; @@ -48,6 +49,7 @@ public class Meal extends AbstractBaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) @OnDelete(action = OnDeleteAction.CASCADE) + @NotNull(groups = View.Persist.class) private User user; public Meal() { diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 0e3fbfbd13e6..0d0ee5071d6a 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -84,6 +84,9 @@ + + + diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 142996663d1a..25cf714376ba 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -11,6 +12,8 @@ public String root() { return "redirect:meals"; } +// @Secured("ROLE_ADMIN") + @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/users") public String users() { return "users"; diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index f5b51c0a82c6..f16f294da1b1 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -2,14 +2,19 @@ xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns="http://www.springframework.org/schema/beans" + xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index bf7c615f4d74..373dff572732 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,5 +1,6 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index d18a1fae368e..30bc5919ead0 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; -import ru.javawebinar.topjava.TestUtil; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.util.MealsUtil; @@ -91,7 +90,7 @@ public void testCreate() throws Exception { .content(JsonUtil.writeValue(created)) .with(userHttpBasic(ADMIN))); - Meal returned = TestUtil.readFromJson(action, Meal.class); + Meal returned = readFromJson(action, Meal.class); created.setId(returned.getId()); assertMatch(returned, created); 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 b8f1c84a6861..dcad2666a197 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -4,7 +4,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; import ru.javawebinar.topjava.TestUtil; -import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.web.AbstractControllerTest; @@ -101,7 +100,7 @@ public void testCreate() throws Exception { ResultActions action = mockMvc.perform(post(REST_URL) .contentType(MediaType.APPLICATION_JSON) .with(userHttpBasic(ADMIN)) - .content(UserTestData.jsonWithPassword(expected, "newPass"))) + .content(jsonWithPassword(expected, "newPass"))) .andExpect(status().isCreated()); User returned = TestUtil.readFromJson(action, User.class); From 9514b5e4b2852787f963fae9425ab8fbdd3d7825 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 14:05:14 +0700 Subject: [PATCH 125/135] 11_01 #HW10 fix encoding --- src/main/webapp/WEB-INF/web.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index c471a447ca14..f40dd731cc78 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,16 +37,6 @@ / - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - springSecurityFilterChain - /* - - encodingFilter org.springframework.web.filter.CharacterEncodingFilter @@ -64,4 +54,14 @@ /* + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + From 7eed5fa80d69dd0ab6ac09a36c7f365fda5281c9 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 14:10:15 +0700 Subject: [PATCH 126/135] 11_01 #HW10 validation --- config/curl.md | 6 +++- pom.xml | 7 ++++ .../topjava/util/ValidationUtil.java | 18 ----------- .../topjava/util/exception/ErrorInfo.java | 6 ++-- .../topjava/util/exception/ErrorType.java | 5 +-- .../topjava/web/ExceptionInfoHandler.java | 32 ++++++++++++++++--- .../topjava/web/meal/MealAjaxController.java | 11 +------ .../topjava/web/meal/MealRestController.java | 5 +-- .../topjava/web/user/AdminAjaxController.java | 11 +------ .../topjava/web/user/AdminRestController.java | 5 +-- .../web/user/ProfileRestController.java | 4 ++- .../webapp/resources/js/datatablesUtil.js | 2 +- .../web/meal/MealRestControllerTest.java | 30 +++++++++++++++-- .../web/user/AdminRestControllerTest.java | 32 +++++++++++++++++-- .../web/user/ProfileRestControllerTest.java | 17 ++++++++-- 15 files changed, 129 insertions(+), 62 deletions(-) diff --git a/config/curl.md b/config/curl.md index 8e197ec254bb..94127472ca58 100644 --- a/config/curl.md +++ b/config/curl.md @@ -26,4 +26,8 @@ `curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals --user user@yandex.ru:password` #### update Meals -`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` \ No newline at end of file +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password`#### update Meals + +#### validate with Error +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00"}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` +`curl -s -X POST -d '{}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/admin/users --user admin@gmail.com:admin` \ No newline at end of file diff --git a/pom.xml b/pom.xml index d2a9950ec32a..fb4eb3429570 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ 4.12 1.3 1.5.0 + 2.4.0 5.2.12.Final @@ -339,6 +340,12 @@ ${jsonassert.version} test + + com.jayway.jsonpath + json-path + ${json-path.version} + test + org.springframework diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 7507a9530dbd..0e71260a5240 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -1,13 +1,8 @@ package ru.javawebinar.topjava.util; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; import ru.javawebinar.topjava.HasId; import ru.javawebinar.topjava.util.exception.NotFoundException; -import java.util.StringJoiner; - public class ValidationUtil { private ValidationUtil() { @@ -57,17 +52,4 @@ public static Throwable getRootCause(Throwable t) { } return result; } - - public static ResponseEntity getErrorResponse(BindingResult result) { - StringJoiner joiner = new StringJoiner("
    "); - result.getFieldErrors().forEach( - fe -> { - String msg = fe.getDefaultMessage(); - if (!msg.startsWith(fe.getField())) { - msg = fe.getField() + ' ' + msg; - } - joiner.add(msg); - }); - return new ResponseEntity<>(joiner.toString(), HttpStatus.UNPROCESSABLE_ENTITY); - } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java index d43323590d95..0c5e15a94910 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java @@ -3,11 +3,11 @@ public class ErrorInfo { private final String url; private final ErrorType type; - private final String detail; + private final String[] details; - public ErrorInfo(CharSequence url, ErrorType type, String detail) { + public ErrorInfo(CharSequence url, ErrorType type, String... details) { this.url = url.toString(); this.type = type; - this.detail = detail; + this.details = details; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java index 700a432d3b41..0801d939460b 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java @@ -3,5 +3,6 @@ public enum ErrorType { APP_ERROR, DATA_NOT_FOUND, - DATA_ERROR -} + DATA_ERROR, + VALIDATION_ERROR +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index b6380f7520ab..f902f371e5ce 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -6,6 +6,9 @@ import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -17,6 +20,8 @@ import javax.servlet.http.HttpServletRequest; +import static ru.javawebinar.topjava.util.exception.ErrorType.*; + @RestControllerAdvice(annotations = RestController.class) @Order(Ordered.HIGHEST_PRECEDENCE + 5) public class ExceptionInfoHandler { @@ -26,28 +31,45 @@ public class ExceptionInfoHandler { @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(NotFoundException.class) public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) { - return logAndGetErrorInfo(req, e, false, ErrorType.DATA_NOT_FOUND); + return logAndGetErrorInfo(req, e, false, DATA_NOT_FOUND); } @ResponseStatus(value = HttpStatus.CONFLICT) // 409 @ExceptionHandler(DataIntegrityViolationException.class) public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) { - return logAndGetErrorInfo(req, e, true, ErrorType.DATA_ERROR); + return logAndGetErrorInfo(req, e, true, DATA_ERROR); + } + + @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) // 422 + @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class}) + public ErrorInfo bindValidationError(HttpServletRequest req, Exception e) { + BindingResult result = e instanceof BindException ? + ((BindException) e).getBindingResult() : ((MethodArgumentNotValidException) e).getBindingResult(); + + String[] details = result.getFieldErrors().stream() + .map(fe -> { + String msg = fe.getDefaultMessage(); + return (msg.startsWith(fe.getField())) ? msg : fe.getField() + ' ' + msg; + }) + .toArray(String[]::new); + + return logAndGetErrorInfo(req, e, false, VALIDATION_ERROR, details); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) public ErrorInfo handleError(HttpServletRequest req, Exception e) { - return logAndGetErrorInfo(req, e, true, ErrorType.APP_ERROR); + return logAndGetErrorInfo(req, e, true, APP_ERROR); } - private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType) { + private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String... details) { Throwable rootCause = ValidationUtil.getRootCause(e); if (logException) { log.error(errorType + " at request " + req.getRequestURL(), rootCause); } else { log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString()); } - return new ErrorInfo(req.getRequestURL(), errorType, rootCause.toString()); + return new ErrorInfo(req.getRequestURL(), errorType, + details.length != 0 ? details : new String[]{rootCause.toString()}); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java index 088c6d6a159b..db1448fd4164 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java @@ -1,13 +1,9 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealWithExceed; -import ru.javawebinar.topjava.util.ValidationUtil; import javax.validation.Valid; import java.time.LocalDate; @@ -37,17 +33,12 @@ public void delete(@PathVariable("id") int id) { } @PostMapping - public ResponseEntity createOrUpdate(@Valid Meal meal, BindingResult result) { - if (result.hasErrors()) { - // TODO change to exception handler - return ValidationUtil.getErrorResponse(result); - } + public void createOrUpdate(@Valid Meal meal) { if (meal.isNew()) { super.create(meal); } else { super.update(meal, meal.getId()); } - return new ResponseEntity<>(HttpStatus.OK); } @Override 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 c395cbb5787d..396ed1a7a0f0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -8,6 +8,7 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealWithExceed; +import javax.validation.Valid; import java.net.URI; import java.time.LocalDate; import java.time.LocalTime; @@ -39,12 +40,12 @@ public List getAll() { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@RequestBody Meal meal, @PathVariable("id") int id) { + public void update(@Valid @RequestBody Meal meal, @PathVariable("id") int id) { super.update(meal, id); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@RequestBody Meal meal) { + public ResponseEntity createWithLocation(@Valid @RequestBody Meal meal) { Meal created = super.create(meal); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index b3f75f43caf3..eeb67e9407c0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -1,14 +1,10 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; -import ru.javawebinar.topjava.util.ValidationUtil; import javax.validation.Valid; import java.util.List; @@ -36,17 +32,12 @@ public void delete(@PathVariable("id") int id) { } @PostMapping - public ResponseEntity createOrUpdate(@Valid UserTo userTo, BindingResult result) { - if (result.hasErrors()) { - // TODO change to exception handler - return ValidationUtil.getErrorResponse(result); - } + public void createOrUpdate(@Valid UserTo userTo) { if (userTo.isNew()) { super.create(UserUtil.createNewFromTo(userTo)); } else { super.update(userTo, userTo.getId()); } - return new ResponseEntity<>(HttpStatus.OK); } @Override 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 fe869a482bf5..4a6d1247d0bb 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -7,6 +7,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.User; +import javax.validation.Valid; import java.net.URI; import java.util.List; @@ -28,7 +29,7 @@ public User get(@PathVariable("id") int id) { } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@RequestBody User user) { + public ResponseEntity createWithLocation(@Valid @RequestBody User user) { User created = super.create(user); // HttpHeaders httpHeaders = new HttpHeaders(); @@ -50,7 +51,7 @@ public void delete(@PathVariable("id") int id) { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@RequestBody User user, @PathVariable("id") int id) { + public void update(@Valid @RequestBody User user, @PathVariable("id") int id) { super.update(user, id); } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index 629019ddb517..ef253bae38a1 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -7,6 +7,8 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; +import javax.validation.Valid; + @RestController @RequestMapping(ProfileRestController.REST_URL) public class ProfileRestController extends AbstractUserController { @@ -24,7 +26,7 @@ public void delete() { } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@RequestBody UserTo userTo) { + public void update(@Valid @RequestBody UserTo userTo) { super.update(userTo, AuthorizedUser.id()); } diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 6ea2fc6d0b0d..9d588d347bba 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -82,7 +82,7 @@ function failNoty(jqXHR) { // https://stackoverflow.com/questions/48229776 var errorInfo = JSON.parse(jqXHR.responseText); failedNote = new Noty({ - text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + "
    " + errorInfo.type + "
    " + errorInfo.detail, + text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + "
    " + errorInfo.type + "
    " + errorInfo.details.join("
    "), type: "error", layout: "bottomRight" }).show(); diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 30bc5919ead0..6dd19059e355 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -7,6 +7,7 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; @@ -14,8 +15,7 @@ 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 org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.MealTestData.assertMatch; import static ru.javawebinar.topjava.TestUtil.contentJson; @@ -128,4 +128,30 @@ public void testFilterAll() throws Exception { .andDo(print()) .andExpect(contentJson(MealsUtil.getWithExceeded(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), USER.getCaloriesPerDay()))); } + + @Test + public void testCreateInvalid() throws Exception { + Meal invalid = new Meal(null, null, "Dummy", 200); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(ADMIN))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } + + @Test + public void testUpdateInvalid() throws Exception { + Meal invalid = new Meal(MEAL1_ID, null, null, 6000); + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index dcad2666a197..37fdb467afda 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -6,6 +6,7 @@ import ru.javawebinar.topjava.TestUtil; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; @@ -13,8 +14,7 @@ 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 org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; @@ -88,7 +88,7 @@ public void testUpdate() throws Exception { mockMvc.perform(put(REST_URL + USER_ID) .contentType(MediaType.APPLICATION_JSON) .with(userHttpBasic(ADMIN)) - .content(JsonUtil.writeValue(updated))) + .content(jsonWithPassword(updated, USER.getPassword()))) .andExpect(status().isOk()); assertMatch(userService.get(USER_ID), updated); @@ -118,4 +118,30 @@ public void testGetAll() throws Exception { .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(contentJson(ADMIN, USER))); } + + @Test + public void testCreateInvalid() throws Exception { + User expected = new User(null, null, "", "newPass", 7300, Role.ROLE_USER, Role.ROLE_ADMIN); + ResultActions action = mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(JsonUtil.writeValue(expected))) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } + + @Test + public void testUpdateInvalid() throws Exception { + User updated = new User(USER); + updated.setName(""); + mockMvc.perform(put(REST_URL + USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(JsonUtil.writeValue(updated))) + .andExpect(status().isUnprocessableEntity()) + .andDo(print()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } } \ 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 index 89d90d751bef..0d6d6208bb7a 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -6,13 +6,13 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; +import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; 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 org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; @@ -56,4 +56,17 @@ public void testUpdate() throws Exception { assertMatch(userService.getByEmail("newemail@ya.ru"), UserUtil.updateFromTo(new User(USER), updatedTo)); } + + @Test + public void testUpdateInvalid() throws Exception { + UserTo updatedTo = new UserTo(null, null, "password", null, 1500); + + mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER)) + .content(JsonUtil.writeValue(updatedTo))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } } \ No newline at end of file From 6f7a1c275a9da1ff79d051436f355081c08ce2f7 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 14:17:29 +0700 Subject: [PATCH 127/135] 11_03 #HW10 duplicate email --- config/messages/app.properties | 4 +++- config/messages/app_ru.properties | 4 +++- .../topjava/web/RootController.java | 14 ++++++++++++-- .../web/user/AbstractUserController.java | 2 ++ .../topjava/web/user/AdminAjaxController.java | 19 +++++++++++++++---- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index e877dbd197f8..e261a247e11d 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -34,4 +34,6 @@ common.saved=Record saved common.enabled=Record enabled common.disabled=Record disabled common.errorStatus=Error status -common.appError=Application error \ No newline at end of file +common.appError=Application error + +exception.user.duplicateEmail=User with this email already exists \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 206dba80d9df..87366f3bde72 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -68,4 +68,6 @@ common.saved=Запись сохранена common.enabled=Запись активирована common.disabled=Запись деактивирована common.errorStatus=Статус ошибки -common.appError=Ошибка приложения \ No newline at end of file +common.appError=Ошибка приложения + +exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении \ 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 77c943029a9a..81aab8daacb7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.web; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; @@ -48,11 +49,15 @@ public String profile() { public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status) { if (result.hasErrors()) { return "profile"; - } else { + } + try { super.update(userTo, AuthorizedUser.id()); AuthorizedUser.get().update(userTo); status.setComplete(); return "redirect:meals"; + } catch (DataIntegrityViolationException ex) { + result.rejectValue("email", EXCEPTION_DUPLICATE_EMAIL); + return "profile"; } } @@ -68,10 +73,15 @@ public String saveRegister(@Valid UserTo userTo, BindingResult result, SessionSt if (result.hasErrors()) { model.addAttribute("register", true); return "profile"; - } else { + } + try { super.create(UserUtil.createNewFromTo(userTo)); status.setComplete(); return "redirect:login?message=app.registered&username=" + userTo.getEmail(); + } catch (DataIntegrityViolationException ex) { + result.rejectValue("email", EXCEPTION_DUPLICATE_EMAIL); + model.addAttribute("register", true); + return "profile"; } } } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index f6bd95465fe2..9eeb4f5d1dc8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -15,6 +15,8 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); + public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail"; + @Autowired private UserService service; diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index eeb67e9407c0..e929ae0ebc20 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -1,5 +1,9 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; @@ -13,6 +17,9 @@ @RequestMapping("/ajax/admin/users") public class AdminAjaxController extends AbstractUserController { + @Autowired + private MessageSource messageSource; + @Override @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { @@ -33,10 +40,14 @@ public void delete(@PathVariable("id") int id) { @PostMapping public void createOrUpdate(@Valid UserTo userTo) { - if (userTo.isNew()) { - super.create(UserUtil.createNewFromTo(userTo)); - } else { - super.update(userTo, userTo.getId()); + try { + if (userTo.isNew()) { + super.create(UserUtil.createNewFromTo(userTo)); + } else { + super.update(userTo, userTo.getId()); + } + } catch (DataIntegrityViolationException e) { + throw new DataIntegrityViolationException(messageSource.getMessage(EXCEPTION_DUPLICATE_EMAIL, null, LocaleContextHolder.getLocale())); } } From 72af31a0f1794493713bf72f708f8a2a32ca3352 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 14:22:00 +0700 Subject: [PATCH 128/135] 11_04 #HW10 duplicate datetime --- .travis.yml | 4 ++ config/messages/app.properties | 3 +- config/messages/app_ru.properties | 3 +- .../topjava/web/ExceptionInfoHandler.java | 29 ++++++++++++++ .../javawebinar/topjava/web/MessageUtil.java | 27 +++++++++++++ .../topjava/web/RootController.java | 2 + .../web/user/AbstractUserController.java | 2 - .../topjava/web/user/AdminAjaxController.java | 19 ++------- .../topjava/web/AbstractControllerTest.java | 19 +++++++++ .../web/meal/MealRestControllerTest.java | 39 +++++++++++++++++-- .../web/user/AdminRestControllerTest.java | 39 +++++++++++++++++-- .../web/user/ProfileRestControllerTest.java | 22 ++++++++++- 12 files changed, 181 insertions(+), 27 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/MessageUtil.java diff --git a/.travis.yml b/.travis.yml index 65c75d2dd58f..27f07bc9297b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,10 @@ branches: only: - master +#https://docs.travis-ci.com/user/environment-variables#Default-Environment-Variables +env: +- TOPJAVA_ROOT=$TRAVIS_BUILD_DIR + # https://docs.travis-ci.com/user/notifications#Configuring-email-notifications #notifications: # email: false \ No newline at end of file diff --git a/config/messages/app.properties b/config/messages/app.properties index e261a247e11d..7474f0538714 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -36,4 +36,5 @@ common.disabled=Record disabled common.errorStatus=Error status common.appError=Application error -exception.user.duplicateEmail=User with this email already exists \ No newline at end of file +exception.user.duplicateEmail=User with this email already exists +exception.meal.duplicateDateTime=You already have meal with this date/time \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 87366f3bde72..45a70f564fe4 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -70,4 +70,5 @@ common.disabled=Запись деактивирована common.errorStatus=Статус ошибки common.appError=Ошибка приложения -exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении \ No newline at end of file +exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении +exception.meal.duplicateDateTime=У вас уже есть еда с такой датой/временем \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index f902f371e5ce..7078173057ae 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; @@ -19,6 +20,10 @@ import ru.javawebinar.topjava.util.exception.NotFoundException; import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import static ru.javawebinar.topjava.util.exception.ErrorType.*; @@ -27,6 +32,20 @@ public class ExceptionInfoHandler { private static Logger log = LoggerFactory.getLogger(ExceptionInfoHandler.class); + public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail"; + public static final String EXCEPTION_DUPLICATE_DATETIME = "exception.meal.duplicateDateTime"; + + private static final Map CONSTRAINS_I18N_MAP = Collections.unmodifiableMap( + new HashMap() { + { + put("users_unique_email_idx", EXCEPTION_DUPLICATE_EMAIL); + put("meals_unique_user_datetime_idx", EXCEPTION_DUPLICATE_DATETIME); + } + }); + + @Autowired + private MessageUtil messageUtil; + // http://stackoverflow.com/a/22358422/548473 @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(NotFoundException.class) @@ -37,6 +56,16 @@ public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) { @ResponseStatus(value = HttpStatus.CONFLICT) // 409 @ExceptionHandler(DataIntegrityViolationException.class) public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) { + String rootMsg = ValidationUtil.getRootCause(e).getMessage(); + if (rootMsg != null) { + String lowerCaseMsg = rootMsg.toLowerCase(); + Optional> entry = CONSTRAINS_I18N_MAP.entrySet().stream() + .filter(it -> lowerCaseMsg.contains(it.getKey())) + .findAny(); + if (entry.isPresent()) { + return logAndGetErrorInfo(req, e, false, DATA_ERROR, messageUtil.getMessage(entry.get().getValue())); + } + } return logAndGetErrorInfo(req, e, true, DATA_ERROR); } diff --git a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java new file mode 100644 index 000000000000..d4f2add25f95 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Locale; + +@Component +public class MessageUtil { + + private final MessageSource messageSource; + + @Autowired + public MessageUtil(MessageSource messageSource) { + this.messageSource = messageSource; + } + + public String getMessage(String code, Locale locale) { + return messageSource.getMessage(code, null, locale); + } + + public String getMessage(String code) { + return getMessage(code, LocaleContextHolder.getLocale()); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 81aab8daacb7..aedcdaa90e4e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -15,6 +15,8 @@ import javax.validation.Valid; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL; + @Controller public class RootController extends AbstractUserController { 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 9eeb4f5d1dc8..f6bd95465fe2 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -15,8 +15,6 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); - public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail"; - @Autowired private UserService service; diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java index e929ae0ebc20..eeb67e9407c0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminAjaxController.java @@ -1,9 +1,5 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.MessageSource; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; @@ -17,9 +13,6 @@ @RequestMapping("/ajax/admin/users") public class AdminAjaxController extends AbstractUserController { - @Autowired - private MessageSource messageSource; - @Override @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { @@ -40,14 +33,10 @@ public void delete(@PathVariable("id") int id) { @PostMapping public void createOrUpdate(@Valid UserTo userTo) { - try { - if (userTo.isNew()) { - super.create(UserUtil.createNewFromTo(userTo)); - } else { - super.update(userTo, userTo.getId()); - } - } catch (DataIntegrityViolationException e) { - throw new DataIntegrityViolationException(messageSource.getMessage(EXCEPTION_DUPLICATE_EMAIL, null, LocaleContextHolder.getLocale())); + if (userTo.isNew()) { + super.create(UserUtil.createNewFromTo(userTo)); + } else { + super.update(userTo, userTo.getId()); } } diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index d881384f4b80..111436bb3d6a 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -9,6 +9,7 @@ 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.ResultMatcher; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; @@ -16,10 +17,13 @@ import ru.javawebinar.topjava.AllActiveProfileResolver; import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.util.exception.ErrorType; import javax.annotation.PostConstruct; +import java.util.Locale; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @ContextConfiguration({ "classpath:spring/spring-app.xml", @@ -53,6 +57,9 @@ abstract public class AbstractControllerTest { @Autowired private WebApplicationContext webApplicationContext; + @Autowired + protected MessageUtil messageUtil; + @PostConstruct private void postConstruct() { mockMvc = MockMvcBuilders @@ -69,4 +76,16 @@ public void setUp() { jpaUtil.clear2ndLevelHibernateCache(); } } + + protected String getMessage(String code) { + return messageUtil.getMessage(code, Locale.ENGLISH); + } + + public ResultMatcher errorType(ErrorType type) { + return jsonPath("$.type").value(type.name()); + } + + public ResultMatcher jsonMessage(String path, String code) { + return jsonPath(path).value(getMessage(code)); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 6dd19059e355..182991a2643f 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.util.MealsUtil; @@ -15,13 +17,15 @@ 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.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.MealTestData.assertMatch; import static ru.javawebinar.topjava.TestUtil.contentJson; import static ru.javawebinar.topjava.TestUtil.*; import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_DATETIME; public class MealRestControllerTest extends AbstractControllerTest { @@ -138,7 +142,7 @@ public void testCreateInvalid() throws Exception { .with(userHttpBasic(ADMIN))) .andDo(print()) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(ErrorType.VALIDATION_ERROR)) .andDo(print()); } @@ -151,7 +155,36 @@ public void testUpdateInvalid() throws Exception { .with(userHttpBasic(USER))) .andDo(print()) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(ErrorType.VALIDATION_ERROR)) .andDo(print()); } + + @Test + @Transactional(propagation = Propagation.NEVER) + public void testUpdateDuplicate() throws Exception { + Meal invalid = new Meal(MEAL1_ID, MEAL2.getDateTime(), "Dummy", 200); + + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isConflict()) + .andExpect(errorType(ErrorType.DATA_ERROR)) + .andExpect(jsonMessage("$.details", EXCEPTION_DUPLICATE_DATETIME)); + } + + @Test + @Transactional(propagation = Propagation.NEVER) + public void testCreateDuplicate() throws Exception { + Meal invalid = new Meal(null, ADMIN_MEAL1.getDateTime(), "Dummy", 200); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(ADMIN))) + .andDo(print()) + .andExpect(status().isConflict()) + .andExpect(errorType(ErrorType.DATA_ERROR)) + .andExpect(jsonMessage("$.details", EXCEPTION_DUPLICATE_DATETIME)); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 37fdb467afda..e4c3bdf699f4 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -3,6 +3,8 @@ import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.TestUtil; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; @@ -14,9 +16,11 @@ 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.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL; public class AdminRestControllerTest extends AbstractControllerTest { @@ -127,7 +131,7 @@ public void testCreateInvalid() throws Exception { .with(userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(expected))) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(ErrorType.VALIDATION_ERROR)) .andDo(print()); } @@ -141,7 +145,36 @@ public void testUpdateInvalid() throws Exception { .content(JsonUtil.writeValue(updated))) .andExpect(status().isUnprocessableEntity()) .andDo(print()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(ErrorType.VALIDATION_ERROR)) .andDo(print()); } + + @Test + @Transactional(propagation = Propagation.NEVER) + public void testUpdateDuplicate() throws Exception { + User updated = new User(USER); + updated.setEmail("admin@gmail.com"); + mockMvc.perform(put(REST_URL + USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(jsonWithPassword(updated, "password"))) + .andExpect(status().isConflict()) + .andExpect(errorType(ErrorType.DATA_ERROR)) + .andExpect(jsonMessage("$.details", EXCEPTION_DUPLICATE_EMAIL)) + .andDo(print()); + } + + @Test + @Transactional(propagation = Propagation.NEVER) + public void testCreateDuplicate() throws Exception { + User expected = new User(null, "New", "user@yandex.ru", "newPass", 2300, Role.ROLE_USER, Role.ROLE_ADMIN); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(jsonWithPassword(expected, "newPass"))) + .andExpect(status().isConflict()) + .andExpect(errorType(ErrorType.DATA_ERROR)) + .andExpect(jsonMessage("$.details", EXCEPTION_DUPLICATE_EMAIL)); + + } } \ 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 index 0d6d6208bb7a..239f172cb8d4 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -2,6 +2,8 @@ import org.junit.Test; import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.TestUtil; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; @@ -12,9 +14,11 @@ 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.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL; import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; public class ProfileRestControllerTest extends AbstractControllerTest { @@ -66,7 +70,21 @@ public void testUpdateInvalid() throws Exception { .content(JsonUtil.writeValue(updatedTo))) .andDo(print()) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(ErrorType.VALIDATION_ERROR)) + .andDo(print()); + } + + @Test + @Transactional(propagation = Propagation.NEVER) + public void testDuplicate() throws Exception { + UserTo updatedTo = new UserTo(null, "newName", "admin@gmail.com", "newPassword", 1500); + + mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER)) + .content(JsonUtil.writeValue(updatedTo))) + .andExpect(status().isConflict()) + .andExpect(errorType(ErrorType.DATA_ERROR)) + .andExpect(jsonMessage("$.details", EXCEPTION_DUPLICATE_EMAIL)) .andDo(print()); } } \ No newline at end of file From 40f2c4b9b8eb753005a6aecfa9d326762468622a Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 15:09:58 +0700 Subject: [PATCH 129/135] 11_05 #HW10 change locale --- src/main/resources/spring/spring-mvc.xml | 8 +++ .../WEB-INF/jsp/fragments/bodyHeader.jsp | 51 ++++++++++++++----- src/main/webapp/WEB-INF/jsp/login.jsp | 19 +------ .../webapp/resources/js/mealDatatables.js | 2 + .../topjava/web/AbstractControllerTest.java | 3 +- 5 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 1fc18104664c..cd06762108d5 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -70,7 +70,15 @@ + + + + + + + + \ 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 f472a71270ca..95acfadc4033 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -6,19 +6,46 @@ \ No newline at end of file +
    + diff --git a/src/main/webapp/WEB-INF/jsp/login.jsp b/src/main/webapp/WEB-INF/jsp/login.jsp index 3d70bcb0be4d..90414000708a 100644 --- a/src/main/webapp/WEB-INF/jsp/login.jsp +++ b/src/main/webapp/WEB-INF/jsp/login.jsp @@ -6,24 +6,7 @@ - +
    diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index 17e7a90feab2..e9ced4efa01d 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -68,6 +68,8 @@ $(function () { "initComplete": makeEditable }); + $.datetimepicker.setLocale(localeCode); + // http://xdsoft.net/jqplugins/datetimepicker/ var startDate = $('#startDate'); var endDate = $('#endDate'); diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index 111436bb3d6a..ad027ef6f6a1 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -20,7 +20,6 @@ import ru.javawebinar.topjava.util.exception.ErrorType; import javax.annotation.PostConstruct; -import java.util.Locale; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -78,7 +77,7 @@ public void setUp() { } protected String getMessage(String code) { - return messageUtil.getMessage(code, Locale.ENGLISH); + return messageUtil.getMessage(code); } public ResultMatcher errorType(ErrorType type) { From 3987be1d84445aa33df78e03a48d8813a72d6767 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 15:20:23 +0700 Subject: [PATCH 130/135] 11_06 i18n --- config/messages/app.properties | 23 ++++++++++++++++--- config/messages/app_ru.properties | 23 ++++++++++++++++--- .../topjava/util/exception/ErrorInfo.java | 4 +++- .../topjava/util/exception/ErrorType.java | 18 +++++++++++---- .../topjava/web/ExceptionInfoHandler.java | 8 +++---- .../web/GlobalControllerExceptionHandler.java | 10 ++++++-- .../javawebinar/topjava/web/MessageUtil.java | 5 ++++ .../WEB-INF/jsp/exception/exception.jsp | 2 +- .../webapp/WEB-INF/jsp/fragments/i18n.jsp | 2 +- .../webapp/resources/js/datatablesUtil.js | 21 ++++++++++++++++- .../webapp/resources/js/mealDatatables.js | 13 +++-------- .../webapp/resources/js/userDatatables.js | 13 +++-------- 12 files changed, 101 insertions(+), 41 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index 7474f0538714..dd48a296ab40 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -16,6 +16,11 @@ user.registered=Registered user.password=Password user.caloriesPerDay=Daily calorie limit +userTo.name=Name +userTo.email=Email +userTo.password=Password +userTo.caloriesPerDay=Daily calorie limit + meal.title=Meals meal.edit=Edit meal meal.add=Add meal @@ -33,8 +38,20 @@ common.deleted=Record deleted common.saved=Record saved common.enabled=Record enabled common.disabled=Record disabled -common.errorStatus=Error status -common.appError=Application error +common.search=Search + +error.appError=Application error +error.dataNotFound=Data not found +error.dataError=Data error +error.validationError=Validation error exception.user.duplicateEmail=User with this email already exists -exception.meal.duplicateDateTime=You already have meal with this date/time \ No newline at end of file +exception.meal.duplicateDateTime=You already have meal with this date/time + +NotEmpty="{0}" must not be empty +NotBlank="{0}" must not be empty +NotNull="{0}" must not be empty +Email= Invalid format of "{0}" +Range="{0}" must be between {2} and {1} +Length="{0}" length must be between {2} and {1} +Size="{0}" size must be between {2} and {1} \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 45a70f564fe4..401991915a1b 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -32,6 +32,11 @@ user.registered=Зарегистрирован user.password=Пароль user.caloriesPerDay=Норма калорий в день +userTo.name=Имя +userTo.email=Почта +userTo.password=Пароль +userTo.caloriesPerDay=Норма калорий в день + meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 meal.edit=\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0435\u0434\u044B meal.add=\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0435\u0434\u044B @@ -67,8 +72,20 @@ common.deleted=Запись удалена common.saved=Запись сохранена common.enabled=Запись активирована common.disabled=Запись деактивирована -common.errorStatus=Статус ошибки -common.appError=Ошибка приложения +common.search=Искать + +error.appError=Ошибка приложения +error.dataNotFound=Данные не найдены +error.dataError=Ошибка в данных +error.validationError=Ошибка формата данных exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении -exception.meal.duplicateDateTime=У вас уже есть еда с такой датой/временем \ No newline at end of file +exception.meal.duplicateDateTime=У вас уже есть еда с такой датой/временем + +NotEmpty=Поле "{0}" не должно быть пустым +NotBlank=Поле "{0}" не должно быть пустым +NotNull=Поле "{0}" не должно быть пустым +Email=Неверный формат поля "{0}" +Range=Поле "{0}" должно быть между {2} и {1} +Length=Длинна поля "{0}" должена быть между {2} и {1} +Size=Размер поля "{0}" должен быть между {2} и {1} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java index 0c5e15a94910..6a1f7a16a247 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java @@ -3,11 +3,13 @@ public class ErrorInfo { private final String url; private final ErrorType type; + private final String typeMessage; private final String[] details; - public ErrorInfo(CharSequence url, ErrorType type, String... details) { + public ErrorInfo(CharSequence url, ErrorType type, String typeMessage, String... details) { this.url = url.toString(); this.type = type; + this.typeMessage = typeMessage; this.details = details; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java index 0801d939460b..e731d70215a3 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java @@ -1,8 +1,18 @@ package ru.javawebinar.topjava.util.exception; public enum ErrorType { - APP_ERROR, - DATA_NOT_FOUND, - DATA_ERROR, - VALIDATION_ERROR + APP_ERROR("error.appError"), + DATA_NOT_FOUND("error.dataNotFound"), + DATA_ERROR("error.dataError"), + VALIDATION_ERROR("error.validationError"); + + private final String errorCode; + + ErrorType(String errorCode) { + this.errorCode = errorCode; + } + + public String getErrorCode() { + return errorCode; + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 7078173057ae..4bbc90dc1c0f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -76,10 +76,7 @@ public ErrorInfo bindValidationError(HttpServletRequest req, Exception e) { ((BindException) e).getBindingResult() : ((MethodArgumentNotValidException) e).getBindingResult(); String[] details = result.getFieldErrors().stream() - .map(fe -> { - String msg = fe.getDefaultMessage(); - return (msg.startsWith(fe.getField())) ? msg : fe.getField() + ' ' + msg; - }) + .map(fe -> messageUtil.getMessage(fe)) .toArray(String[]::new); return logAndGetErrorInfo(req, e, false, VALIDATION_ERROR, details); @@ -91,7 +88,7 @@ public ErrorInfo handleError(HttpServletRequest req, Exception e) { return logAndGetErrorInfo(req, e, true, APP_ERROR); } - private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String... details) { + private ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String... details) { Throwable rootCause = ValidationUtil.getRootCause(e); if (logException) { log.error(errorType + " at request " + req.getRequestURL(), rootCause); @@ -99,6 +96,7 @@ private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString()); } return new ErrorInfo(req.getRequestURL(), errorType, + messageUtil.getMessage(errorType.getErrorCode()), details.length != 0 ? details : new String[]{rootCause.toString()}); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index 0784561cd6ac..4a17313f8b58 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -2,11 +2,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.util.ValidationUtil; +import ru.javawebinar.topjava.util.exception.ErrorType; import javax.servlet.http.HttpServletRequest; @@ -14,11 +16,15 @@ public class GlobalControllerExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalControllerExceptionHandler.class); + @Autowired + private MessageUtil messageUtil; + @ExceptionHandler(Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { - log.error("Exception at request " + req.getRequestURL(), e); - ModelAndView mav = new ModelAndView("exception/exception"); Throwable rootCause = ValidationUtil.getRootCause(e); + log.error("Exception at request " + req.getRequestURL(), rootCause); + ModelAndView mav = new ModelAndView("exception/exception"); + mav.addObject("typeMessage", messageUtil.getMessage(ErrorType.APP_ERROR.getErrorCode())); mav.addObject("exception", rootCause); mav.addObject("message", rootCause.toString()); diff --git a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java index d4f2add25f95..414ca3e6f626 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; @@ -24,4 +25,8 @@ public String getMessage(String code, Locale locale) { public String getMessage(String code) { return getMessage(code, LocaleContextHolder.getLocale()); } + + public String getMessage(MessageSourceResolvable resolvable) { + return messageSource.getMessage(resolvable, LocaleContextHolder.getLocale()); + } } diff --git a/src/main/webapp/WEB-INF/jsp/exception/exception.jsp b/src/main/webapp/WEB-INF/jsp/exception/exception.jsp index a598a65bfc73..b027313d32a0 100644 --- a/src/main/webapp/WEB-INF/jsp/exception/exception.jsp +++ b/src/main/webapp/WEB-INF/jsp/exception/exception.jsp @@ -11,7 +11,7 @@

    -

    +

    ${typeMessage}

    ${message}

    diff --git a/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp b/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp index c669984f6340..15785fc819d6 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp @@ -4,7 +4,7 @@ \ No newline at end of file diff --git a/src/main/webapp/resources/js/datatablesUtil.js b/src/main/webapp/resources/js/datatablesUtil.js index 9d588d347bba..ff99b118dbdf 100644 --- a/src/main/webapp/resources/js/datatablesUtil.js +++ b/src/main/webapp/resources/js/datatablesUtil.js @@ -16,6 +16,25 @@ function makeEditable() { }); } +// https://api.jquery.com/jquery.extend/#jQuery-extend-deep-target-object1-objectN +function extendsOpts(opts) { + $.extend(true, opts, + { + "ajax": { + "url": ajaxUrl, + "dataSrc": "" + }, + "paging": false, + "info": true, + "language": { + "search": i18n["common.search"] + }, + "initComplete": makeEditable + } + ); + return opts; +} + function add() { $("#modalTitle").html(i18n["addTitle"]); form.find(":input").val(""); @@ -82,7 +101,7 @@ function failNoty(jqXHR) { // https://stackoverflow.com/questions/48229776 var errorInfo = JSON.parse(jqXHR.responseText); failedNote = new Noty({ - text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + "
    " + errorInfo.type + "
    " + errorInfo.details.join("
    "), + text: "  " + errorInfo.typeMessage + "
    " + errorInfo.details.join("
    "), type: "error", layout: "bottomRight" }).show(); diff --git a/src/main/webapp/resources/js/mealDatatables.js b/src/main/webapp/resources/js/mealDatatables.js index e9ced4efa01d..266d7f781c44 100644 --- a/src/main/webapp/resources/js/mealDatatables.js +++ b/src/main/webapp/resources/js/mealDatatables.js @@ -28,13 +28,7 @@ $.ajaxSetup({ }); $(function () { - datatableApi = $("#datatable").DataTable({ - "ajax": { - "url": ajaxUrl, - "dataSrc": "" - }, - "paging": false, - "info": true, + datatableApi = $('#datatable').DataTable(extendsOpts({ "columns": [ { "data": "dateTime" @@ -64,9 +58,8 @@ $(function () { ], "createdRow": function (row, data, dataIndex) { $(row).addClass(data.exceed ? 'exceeded' : 'normal'); - }, - "initComplete": makeEditable - }); + } + })); $.datetimepicker.setLocale(localeCode); diff --git a/src/main/webapp/resources/js/userDatatables.js b/src/main/webapp/resources/js/userDatatables.js index 8581d45606dd..3f6a76537e8b 100644 --- a/src/main/webapp/resources/js/userDatatables.js +++ b/src/main/webapp/resources/js/userDatatables.js @@ -22,13 +22,7 @@ function enable(chkbox, id) { // $(document).ready(function () { $(function () { - datatableApi = $("#datatable").DataTable({ - "ajax": { - "url": ajaxUrl, - "dataSrc": "" - }, - "paging": false, - "info": true, + datatableApi = $('#datatable').DataTable(extendsOpts({ "columns": [ { "data": "name" @@ -84,7 +78,6 @@ $(function () { if (!data.enabled) { $(row).addClass("disabled"); } - }, - "initComplete": makeEditable - }); + } + })); }); \ No newline at end of file From c3ae03483804040394c831db0079dcbb5ada6e61 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 16:10:03 +0700 Subject: [PATCH 131/135] 11_07 XSS --- config/messages/app.properties | 3 ++- config/messages/app_ru.properties | 3 ++- pom.xml | 6 ++++++ src/main/java/ru/javawebinar/topjava/View.java | 4 ++++ .../topjava/model/AbstractNamedEntity.java | 4 ++++ .../java/ru/javawebinar/topjava/model/Meal.java | 2 ++ .../java/ru/javawebinar/topjava/model/User.java | 3 +++ .../java/ru/javawebinar/topjava/to/UserTo.java | 3 +++ .../topjava/web/meal/MealAjaxController.java | 5 +++-- .../topjava/web/meal/MealRestController.java | 7 ++++--- .../topjava/web/user/AdminRestController.java | 7 ++++--- .../topjava/web/meal/MealRestControllerTest.java | 15 +++++++++++++++ 12 files changed, 52 insertions(+), 10 deletions(-) diff --git a/config/messages/app.properties b/config/messages/app.properties index dd48a296ab40..5f8338ca970d 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -54,4 +54,5 @@ NotNull="{0}" must not be empty Email= Invalid format of "{0}" Range="{0}" must be between {2} and {1} Length="{0}" length must be between {2} and {1} -Size="{0}" size must be between {2} and {1} \ No newline at end of file +Size="{0}" size must be between {2} and {1} +SafeHtml="{0}" may has unsafe html content \ No newline at end of file diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 401991915a1b..39220d32a1a6 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -88,4 +88,5 @@ NotNull=Поле "{0}" не должно быть пустым Email=Неверный формат поля "{0}" Range=Поле "{0}" должно быть между {2} и {1} Length=Длинна поля "{0}" должена быть между {2} и {1} -Size=Размер поля "{0}" должен быть между {2} и {1} \ No newline at end of file +Size=Размер поля "{0}" должен быть между {2} и {1} +SafeHtml=Поле "{0}" может содержать небезопасный html контент \ No newline at end of file diff --git a/pom.xml b/pom.xml index fb4eb3429570..b99127527782 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ 5.2.12.Final 6.0.7.Final + 1.11.2 3.0.1-b08 @@ -207,6 +208,11 @@ hibernate-validator ${hibernate-validator.version} + + org.jsoup + jsoup + ${jsoup.version} + org.hibernate hibernate-jcache diff --git a/src/main/java/ru/javawebinar/topjava/View.java b/src/main/java/ru/javawebinar/topjava/View.java index 62c332bcf970..f1623c0536b0 100644 --- a/src/main/java/ru/javawebinar/topjava/View.java +++ b/src/main/java/ru/javawebinar/topjava/View.java @@ -3,5 +3,9 @@ import javax.validation.groups.Default; public class View { + // Validate only form UI/REST + public interface Web extends Default {} + + // Validate only when DB save/update public interface Persist extends Default {} } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index be41aa890966..128a2abaebd4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -1,5 +1,8 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.SafeHtml; +import ru.javawebinar.topjava.View; + import javax.persistence.Column; import javax.persistence.MappedSuperclass; import javax.validation.constraints.NotBlank; @@ -12,6 +15,7 @@ public abstract class AbstractNamedEntity extends AbstractBaseEntity { @NotBlank @Size(min = 2, max = 100) @Column(name = "name", nullable = false) + @SafeHtml(groups = {View.Web.class}) protected String name; public AbstractNamedEntity() { diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 5b6fb05cde98..b0b3ab6bae55 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -3,6 +3,7 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import org.springframework.format.annotation.DateTimeFormat; import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.util.DateTimeUtil; @@ -39,6 +40,7 @@ public class Meal extends AbstractBaseEntity { @Column(name = "description", nullable = false) @NotBlank @Size(min = 2, max = 120) + @SafeHtml(groups = {View.Web.class}) private String description; @Column(name = "calories", nullable = false) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index f33a84bbdd28..b6c0a77c5145 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -5,7 +5,9 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import org.springframework.util.CollectionUtils; +import ru.javawebinar.topjava.View; import javax.persistence.*; import javax.validation.constraints.Email; @@ -37,6 +39,7 @@ public class User extends AbstractNamedEntity { @Email @NotBlank @Size(max = 100) + @SafeHtml(groups = {View.Web.class}) // https://stackoverflow.com/questions/17480809 private String email; @Column(name = "password", nullable = false) diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index 64788665c494..03994e84dd79 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.to; import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import ru.javawebinar.topjava.util.UserUtil; import javax.validation.constraints.Email; @@ -14,11 +15,13 @@ public class UserTo extends BaseTo implements Serializable { @NotBlank @Size(min = 2, max = 100) + @SafeHtml private String name; @Email @NotBlank @Size(max = 100) + @SafeHtml // https://stackoverflow.com/questions/17480809 private String email; @Size(min = 5, max = 32, message = "length must between 5 and 32 characters") diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java index db1448fd4164..5985a693817d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealAjaxController.java @@ -1,11 +1,12 @@ package ru.javawebinar.topjava.web.meal; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealWithExceed; -import javax.validation.Valid; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; @@ -33,7 +34,7 @@ public void delete(@PathVariable("id") int id) { } @PostMapping - public void createOrUpdate(@Valid Meal meal) { + public void createOrUpdate(@Validated(View.Web.class) Meal meal) { if (meal.isNew()) { super.create(meal); } else { 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 396ed1a7a0f0..04aa2416b01d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -3,12 +3,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealWithExceed; -import javax.validation.Valid; import java.net.URI; import java.time.LocalDate; import java.time.LocalTime; @@ -40,12 +41,12 @@ public List getAll() { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@Valid @RequestBody Meal meal, @PathVariable("id") int id) { + public void update(@Validated(View.Web.class) @RequestBody Meal meal, @PathVariable("id") int id) { super.update(meal, id); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@Valid @RequestBody Meal meal) { + public ResponseEntity createWithLocation(@Validated(View.Web.class) @RequestBody Meal meal) { Meal created = super.create(meal); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() 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 4a6d1247d0bb..c43bb443fe8b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -3,11 +3,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.User; -import javax.validation.Valid; import java.net.URI; import java.util.List; @@ -29,7 +30,7 @@ public User get(@PathVariable("id") int id) { } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@Valid @RequestBody User user) { + public ResponseEntity createWithLocation(@Validated(View.Web.class) @RequestBody User user) { User created = super.create(user); // HttpHeaders httpHeaders = new HttpHeaders(); @@ -51,7 +52,7 @@ public void delete(@PathVariable("id") int id) { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@Valid @RequestBody User user, @PathVariable("id") int id) { + public void update(@Validated(View.Web.class) @RequestBody User user, @PathVariable("id") int id) { super.update(user, id); } diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 182991a2643f..81a2248701a7 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -13,6 +13,7 @@ import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; +import java.time.LocalDateTime; import java.util.Arrays; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -159,6 +160,20 @@ public void testUpdateInvalid() throws Exception { .andDo(print()); } + + @Test + public void testUpdateHtmlUnsafe() throws Exception { + Meal invalid = new Meal(MEAL1_ID, LocalDateTime.now(), "", 200); + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(errorType(ErrorType.VALIDATION_ERROR)) + .andDo(print()); + } + @Test @Transactional(propagation = Propagation.NEVER) public void testUpdateDuplicate() throws Exception { From e634eace29426694e01b221f80503acc01edc412 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 16:14:33 +0700 Subject: [PATCH 132/135] 11_08 404 --- src/main/resources/spring/spring-mvc.xml | 3 ++- src/main/webapp/WEB-INF/web.xml | 4 ++++ src/main/webapp/test.html | 13 ------------- 3 files changed, 6 insertions(+), 14 deletions(-) delete mode 100644 src/main/webapp/test.html diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index cd06762108d5..9fa5361ac93d 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -11,7 +11,8 @@ - + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index f40dd731cc78..3fa68e5ea1f6 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -30,6 +30,10 @@ contextConfigLocation classpath:spring/spring-mvc.xml + + throwExceptionIfNoHandlerFound + true + 1 diff --git a/src/main/webapp/test.html b/src/main/webapp/test.html deleted file mode 100644 index 62e151e5806f..000000000000 --- a/src/main/webapp/test.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - -
    - -
    - - \ No newline at end of file From 2a722cec4587923b818c7bb4ee0d0a687d226951 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 16:18:32 +0700 Subject: [PATCH 133/135] 11_09 auth user --- .../javawebinar/topjava/AuthorizedUser.java | 4 ++++ .../web/GlobalControllerExceptionHandler.java | 7 ------ .../topjava/web/RootController.java | 10 ++++---- .../web/interceptor/ModelInterceptor.java | 24 ------------------- .../web/user/ProfileRestController.java | 13 +++++----- src/main/resources/spring/spring-mvc.xml | 5 ++-- .../WEB-INF/jsp/fragments/bodyHeader.jsp | 2 +- 7 files changed, 21 insertions(+), 44 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java diff --git a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java index e90610cff1a4..92386610e704 100644 --- a/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java +++ b/src/main/java/ru/javawebinar/topjava/AuthorizedUser.java @@ -33,6 +33,10 @@ public static AuthorizedUser get() { return user; } + public int getId() { + return userTo.getId(); + } + public static int id() { return get().userTo.getId(); } diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index 4a17313f8b58..1abe4616785f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -6,7 +6,6 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; -import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.util.ValidationUtil; import ru.javawebinar.topjava.util.exception.ErrorType; @@ -27,12 +26,6 @@ public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) thr mav.addObject("typeMessage", messageUtil.getMessage(ErrorType.APP_ERROR.getErrorCode())); mav.addObject("exception", rootCause); mav.addObject("message", rootCause.toString()); - - // Interceptor is not invoked, put userTo - AuthorizedUser authorizedUser = AuthorizedUser.safeGet(); - if (authorizedUser != null) { - mav.addObject("userTo", authorizedUser.getUserTo()); - } return mav; } } diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index aedcdaa90e4e..58916965cab1 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -2,6 +2,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; @@ -43,18 +44,19 @@ public String meals() { } @GetMapping("/profile") - public String profile() { + public String profile(ModelMap model, @AuthenticationPrincipal AuthorizedUser authorizedUser) { + model.addAttribute("userTo", authorizedUser.getUserTo()); return "profile"; } @PostMapping("/profile") - public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status) { + public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status, @AuthenticationPrincipal AuthorizedUser authorizedUser) { if (result.hasErrors()) { return "profile"; } try { - super.update(userTo, AuthorizedUser.id()); - AuthorizedUser.get().update(userTo); + super.update(userTo, authorizedUser.getId()); + authorizedUser.update(userTo); status.setComplete(); return "redirect:meals"; } catch (DataIntegrityViolationException ex) { diff --git a/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java b/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java deleted file mode 100644 index d667454749b7..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.javawebinar.topjava.web.interceptor; - -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; -import ru.javawebinar.topjava.AuthorizedUser; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * This interceptor adds the user to the model of every requests managed - */ -public class ModelInterceptor extends HandlerInterceptorAdapter { - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { - if (modelAndView != null && !modelAndView.isEmpty()) { - AuthorizedUser authorizedUser = AuthorizedUser.safeGet(); - if (authorizedUser != null) { - modelAndView.getModelMap().addAttribute("userTo", authorizedUser.getUserTo()); - } - } - } -} 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 ef253bae38a1..7ce2fac559f2 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; @@ -15,19 +16,19 @@ public class ProfileRestController extends AbstractUserController { static final String REST_URL = "/rest/profile"; @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - public User get() { - return super.get(AuthorizedUser.id()); + public User get(@AuthenticationPrincipal AuthorizedUser authorizedUser) { + return super.get(authorizedUser.getId()); } @DeleteMapping @ResponseStatus(value = HttpStatus.NO_CONTENT) - public void delete() { - super.delete(AuthorizedUser.id()); + public void delete(@AuthenticationPrincipal AuthorizedUser authorizedUser) { + super.delete(authorizedUser.getId()); } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public void update(@Valid @RequestBody UserTo userTo) { - super.update(userTo, AuthorizedUser.id()); + public void update(@Valid @RequestBody UserTo userTo, @AuthenticationPrincipal AuthorizedUser authorizedUser) { + super.update(userTo, authorizedUser.getId()); } @GetMapping(value = "/text") diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 9fa5361ac93d..f76e9f94cedf 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -30,6 +30,9 @@
    + + + @@ -76,8 +79,6 @@ - - diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 95acfadc4033..b2aa86121cc3 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -14,7 +14,7 @@ - ${userTo.name} + From 1fa24ec439919ae1dd0fabfc2761f4a1bcf1b0c3 Mon Sep 17 00:00:00 2001 From: Fabus Date: Tue, 23 Jan 2018 16:26:30 +0700 Subject: [PATCH 134/135] 11_09 auth user --- config/messages/app.properties | 2 + config/messages/app_ru.properties | 2 + .../java/ru/javawebinar/topjava/Profiles.java | 3 +- .../topjava/util/ValidationUtil.java | 4 +- .../util/exception/ApplicationException.java | 42 ++++++++++++++++++ .../ModificationRestrictionException.java | 11 +++++ .../util/exception/NotFoundException.java | 11 +++-- .../topjava/web/ExceptionInfoHandler.java | 12 ++--- .../web/GlobalControllerExceptionHandler.java | 14 +++++- .../javawebinar/topjava/web/MessageUtil.java | 13 ++++-- .../web/user/AbstractUserController.java | 21 +++++++++ src/main/resources/spring/spring-db.xml | 2 +- .../service/AbstractMealServiceTest.java | 6 ++- .../web/user/HerokuRestControllerTest.java | 44 +++++++++++++++++++ 14 files changed, 167 insertions(+), 20 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/HerokuRestControllerTest.java diff --git a/config/messages/app.properties b/config/messages/app.properties index 5f8338ca970d..8636b04c6d8f 100644 --- a/config/messages/app.properties +++ b/config/messages/app.properties @@ -45,7 +45,9 @@ error.dataNotFound=Data not found error.dataError=Data error error.validationError=Validation error +exception.common.notFound=Not found entity with {0} exception.user.duplicateEmail=User with this email already exists +exception.user.modificationRestriction=Admin/User modification is forbidden exception.meal.duplicateDateTime=You already have meal with this date/time NotEmpty="{0}" must not be empty diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties index 39220d32a1a6..db901d8ba7f8 100644 --- a/config/messages/app_ru.properties +++ b/config/messages/app_ru.properties @@ -79,7 +79,9 @@ error.dataNotFound=Данные не найдены error.dataError=Ошибка в данных error.validationError=Ошибка формата данных +exception.common.notFound=Не найдена запись с {0} exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении +exception.user.modificationRestriction=Модификация Admin/User запрещена exception.meal.duplicateDateTime=У вас уже есть еда с такой датой/временем NotEmpty=Поле "{0}" не должно быть пустым diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index d6ef2e0379ed..91b983429553 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -10,7 +10,8 @@ public class Profiles { public static final String POSTGRES_DB = "postgres", - HSQL_DB = "hsqldb"; + HSQL_DB = "hsqldb", + HEROKU = "heroku"; // Get DB profile depending of DB driver in classpath public static String getActiveDbProfile() { diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 0e71260a5240..3899c2606954 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -21,9 +21,9 @@ public static T checkNotFound(T object, String msg) { return object; } - public static void checkNotFound(boolean found, String msg) { + public static void checkNotFound(boolean found, String arg) { if (!found) { - throw new NotFoundException("Not found entity with " + msg); + throw new NotFoundException(arg); } } diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java b/src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java new file mode 100644 index 000000000000..31fb5f20c576 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.util.exception; + +import org.springframework.http.HttpStatus; + +import java.util.Arrays; + +public class ApplicationException extends RuntimeException { + + private final ErrorType type; + private final String msgCode; + private final HttpStatus httpStatus; + private final String[] args; + + public ApplicationException(String msgCode, HttpStatus httpStatus) { + this(ErrorType.APP_ERROR, msgCode, httpStatus); + } + + public ApplicationException(ErrorType type, String msgCode, HttpStatus httpStatus, String... args) { + super(String.format("type=%s, msgCode=%s, args=%s", type, msgCode, Arrays.toString(args))); + this.type = type; + this.msgCode = msgCode; + this.httpStatus = httpStatus; + this.args = args; + } + + public ErrorType getType() { + return type; + } + + public String getMsgCode() { + return msgCode; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public String[] getArgs() { + return args; + } + +} diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java b/src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java new file mode 100644 index 000000000000..2f46bf152dea --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java @@ -0,0 +1,11 @@ +package ru.javawebinar.topjava.util.exception; + +import org.springframework.http.HttpStatus; + +public class ModificationRestrictionException extends ApplicationException { + public static final String EXCEPTION_MODIFICATION_RESTRICTION = "exception.user.modificationRestriction"; + + public ModificationRestrictionException() { + super(EXCEPTION_MODIFICATION_RESTRICTION, HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS); + } +} \ 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 index f1e9b0e46376..2e41450ded44 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -1,7 +1,12 @@ package ru.javawebinar.topjava.util.exception; -public class NotFoundException extends RuntimeException { - public NotFoundException(String message) { - super(message); +import org.springframework.http.HttpStatus; + +public class NotFoundException extends ApplicationException { + public static final String NOT_FOUND_EXCEPTION = "exception.common.notFound"; + + // http://stackoverflow.com/a/22358422/548473 + public NotFoundException(String arg) { + super(ErrorType.DATA_NOT_FOUND, NOT_FOUND_EXCEPTION, HttpStatus.UNPROCESSABLE_ENTITY, arg); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 4bbc90dc1c0f..c60c9b41a15d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -7,6 +7,7 @@ import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -15,9 +16,9 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestControllerAdvice; import ru.javawebinar.topjava.util.ValidationUtil; +import ru.javawebinar.topjava.util.exception.ApplicationException; import ru.javawebinar.topjava.util.exception.ErrorInfo; import ru.javawebinar.topjava.util.exception.ErrorType; -import ru.javawebinar.topjava.util.exception.NotFoundException; import javax.servlet.http.HttpServletRequest; import java.util.Collections; @@ -46,11 +47,10 @@ public class ExceptionInfoHandler { @Autowired private MessageUtil messageUtil; - // http://stackoverflow.com/a/22358422/548473 - @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) - @ExceptionHandler(NotFoundException.class) - public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) { - return logAndGetErrorInfo(req, e, false, DATA_NOT_FOUND); + @ExceptionHandler(ApplicationException.class) + public ResponseEntity applicationError(HttpServletRequest req, ApplicationException appEx) { + ErrorInfo errorInfo = logAndGetErrorInfo(req, appEx, false, appEx.getType(), messageUtil.getMessage(appEx)); + return new ResponseEntity<>(errorInfo, appEx.getHttpStatus()); } @ResponseStatus(value = HttpStatus.CONFLICT) // 409 diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index 1abe4616785f..cc6d3f59c8a8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; import ru.javawebinar.topjava.util.ValidationUtil; +import ru.javawebinar.topjava.util.exception.ApplicationException; import ru.javawebinar.topjava.util.exception.ErrorType; import javax.servlet.http.HttpServletRequest; @@ -18,14 +19,23 @@ public class GlobalControllerExceptionHandler { @Autowired private MessageUtil messageUtil; + @ExceptionHandler(ApplicationException.class) + public ModelAndView applicationErrorHandler(HttpServletRequest req, ApplicationException appEx) throws Exception { + return getView(req, appEx, appEx.getType(), messageUtil.getMessage(appEx)); + } + @ExceptionHandler(Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { + return getView(req, e, ErrorType.APP_ERROR, null); + } + + public ModelAndView getView(HttpServletRequest req, Exception e, ErrorType type, String msg) throws Exception { Throwable rootCause = ValidationUtil.getRootCause(e); log.error("Exception at request " + req.getRequestURL(), rootCause); ModelAndView mav = new ModelAndView("exception/exception"); - mav.addObject("typeMessage", messageUtil.getMessage(ErrorType.APP_ERROR.getErrorCode())); + mav.addObject("typeMessage", messageUtil.getMessage(type.getErrorCode())); mav.addObject("exception", rootCause); - mav.addObject("message", rootCause.toString()); + mav.addObject("message", msg != null ? msg : rootCause.toString()); return mav; } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java index 414ca3e6f626..2891428680a7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java @@ -5,6 +5,7 @@ import org.springframework.context.MessageSourceResolvable; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; +import ru.javawebinar.topjava.util.exception.ApplicationException; import java.util.Locale; @@ -18,12 +19,16 @@ public MessageUtil(MessageSource messageSource) { this.messageSource = messageSource; } - public String getMessage(String code, Locale locale) { - return messageSource.getMessage(code, null, locale); + public String getMessage(String code, Locale locale, String... args) { + return messageSource.getMessage(code, args, locale); } - public String getMessage(String code) { - return getMessage(code, LocaleContextHolder.getLocale()); + public String getMessage(String code, String... args) { + return getMessage(code, LocaleContextHolder.getLocale(), args); + } + + public String getMessage(ApplicationException appEx) { + return getMessage(appEx.getMsgCode(), appEx.getArgs()); } public String getMessage(MessageSourceResolvable resolvable) { 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 f6bd95465fe2..090dc5a920c6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -3,9 +3,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.model.AbstractBaseEntity; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; import ru.javawebinar.topjava.to.UserTo; +import ru.javawebinar.topjava.util.exception.ModificationRestrictionException; import java.util.List; @@ -18,6 +22,13 @@ public abstract class AbstractUserController { @Autowired private UserService service; + private boolean modificationRestriction; + + @Autowired + public void setEnvironment(Environment environment) { + modificationRestriction = environment.acceptsProfiles(Profiles.HEROKU); + } + public List getAll() { log.info("getAll"); return service.getAll(); @@ -36,18 +47,21 @@ public User create(User user) { public void delete(int id) { log.info("delete {}", id); + checkModificationAllowed(id); service.delete(id); } public void update(User user, int id) { log.info("update {} with id={}", user, id); assureIdConsistent(user, id); + checkModificationAllowed(id); service.update(user); } public void update(UserTo userTo, int id) { log.info("update {} with id={}", userTo, id); assureIdConsistent(userTo, id); + checkModificationAllowed(id); service.update(userTo); } @@ -58,6 +72,13 @@ public User getByMail(String email) { public void enable(int id, boolean enabled) { log.info((enabled ? "enable " : "disable ") + id); + checkModificationAllowed(id); service.enable(id, enabled); } + + private void checkModificationAllowed(int id) { + if (modificationRestriction && id < AbstractBaseEntity.START_SEQ + 2) { + throw new ModificationRestrictionException(); + } + } } \ 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 0d0ee5071d6a..1946ca8f8f47 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -34,7 +34,7 @@ p:password="${database.password}"/> - + Date: Wed, 24 Jan 2018 08:50:05 +0700 Subject: [PATCH 135/135] 11_11 heroku --- Procfile | 1 + hr.bat | 2 + pom.xml | 42 +++++++++++++++++++ settings.xml | 10 +++++ src/main/resources/db/heroku.properties | 5 +++ src/main/resources/spring/spring-db.xml | 26 +++++++++++- .../web/user/HerokuRestControllerTest.java | 23 ++++++++++ system.properties | 1 + 8 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 Procfile create mode 100644 hr.bat create mode 100644 settings.xml create mode 100644 src/main/resources/db/heroku.properties create mode 100644 system.properties diff --git a/Procfile b/Procfile new file mode 100644 index 000000000000..4afeb7900918 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: java $JAVA_OPTS -Dspring.profiles.active="datajpa,heroku" -DTOPJAVA_ROOT="." -jar target/dependency/webapp-runner.jar --port $PORT target/*.war \ No newline at end of file diff --git a/hr.bat b/hr.bat new file mode 100644 index 000000000000..1ea30584df86 --- /dev/null +++ b/hr.bat @@ -0,0 +1,2 @@ +call mvn -B -s settings.xml -DskipTests=true clean package +call java -Dspring.profiles.active="datajpa,heroku" -DDATABASE_URL="postgres://user:password@localhost:5432/topjava" -jar target/dependency/webapp-runner.jar target/*.war \ No newline at end of file diff --git a/pom.xml b/pom.xml index b99127527782..ea5679d13bbd 100644 --- a/pom.xml +++ b/pom.xml @@ -402,6 +402,48 @@ true + + heroku + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.2 + + + package + + copy + + + + + com.github.jsimone + webapp-runner + 8.5.11.2 + webapp-runner.jar + + + + + + + + + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + + + diff --git a/settings.xml b/settings.xml new file mode 100644 index 000000000000..e0a6d49fb0d0 --- /dev/null +++ b/settings.xml @@ -0,0 +1,10 @@ + + + + + + heroku + + diff --git a/src/main/resources/db/heroku.properties b/src/main/resources/db/heroku.properties new file mode 100644 index 000000000000..c8146ba6f139 --- /dev/null +++ b/src/main/resources/db/heroku.properties @@ -0,0 +1,5 @@ +jpa.showSql=false +hibernate.format_sql=false +hibernate.use_sql_comments=false +database.init=false +jdbc.initLocation=initDB.sql \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 1946ca8f8f47..303ffeb0fb7c 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -34,7 +34,7 @@ p:password="${database.password}"/> - + + + + + + + + + +