diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 000000000000..a7672ebf36af --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,4 @@ +--- +exclude_paths: + - 'src/main/webapp/**' + - 'README.md' \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..ad4c8b9aada9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +# https://docs.travis-ci.com/user/languages/java/ +language: java +jdk: openjdk11 + +#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/README.md b/README.md new file mode 100644 index 000000000000..de83d87b4911 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +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) 1. Осваиваем 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 +- Presentation + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Системы управления версиями. Git. +- **Wiki по ведению проекта в Git** +- Система управления версиями. VCS/DVSC. +- Ресурсы: + - Интерактивная Git обучалка + - Еще одна интерактивная обучалка, по-русски + - Книга Git + - Working with remote repositories + - Видео по обучению Git + - Git Overview + - [Основы Git за 20 минут](https://www.youtube.com/watch?v=TMeZGvtQnT8) + - [Git - для новичков](https://www.youtube.com/watch?list=PLY4rE9dstrJyTdVJpv7FibSaXB4BHPInb&v=PEKN8NtBDQ0) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Работа с проектом (выполнять инструкции) +**ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** +> Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео: в `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 через циклы (`forEach`): +- должны возвращаться только записи между startTime и endTime +- поле UserMealWithExceed.exceed должно показывать, + превышает ли сумма калорий за весь день параметра метода caloriesPerDay + +Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. + +- Проверьте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) +- Оцените Time complexity алгоритма. Если она больше O(N), например O(N*N) или N*log(N), сделайте O(N). +``` +- Java 8 Date and Time API +- Алгоритмы и структуры данных для начинающих: сложность алгоритмов +- Time complexity +- Временная сложность алгоритма +- Вычислительная сложность + +#### ВНИМАНИЕ: варианты Optional делайте в `UserMealsUtil` в одной ветке в разных методах. Проще делать, проще проверять + +#### Optional (Java 8 Stream API) +``` +Сделать реализацию через Java 8 Stream API. +``` +- Видео: Доступно о Java 8 Lambda +- Java 8: Lambda выражения +- Java 8: Потоки +- Pуководство по Java 8 Stream +- Java 8 Stream API в картинках и примерах +- [7 способов использовать groupingBy в Stream API](https://habrahabr.ru/post/348536) +- Лямбда-выражения в 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) + +#### Optional 2 (+5 бонусов) +``` +Сделать реализацию со сложностью O(N): +- циклом за 1 проход по List. Обратите внимание на п.13 замечаний +- через Stream API за 1 проход по исходному списку Stream meals + - возможно дополнительные проходы по частям списка + - нельзя использовать внешние коллекции, не являющиеся частью коллектора или результатами работы stream +``` +#### Замечания по использованию 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м уроке. +- 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: Не полагайтесь в решении на то, что список будет подаваться отсортированным. Такого условия нет. +----- +## [Пример 7-го занятия онлайн стажировки, несколько видео открыто](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md) + +### Полезные ресурсы +> ВНИМАНИЕ: +> - **ДЗ первого урока будет связано с [созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах](https://danielniko.wordpress.com/2012/04/17/simple-crud-using-jsp-servlet-and-mysql/)**. Введение будет, но предварительное знакомство не помешает. +> - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! + +Все остальное - опционально. + +#### HTML, JavaScript, CSS +- [Basic HTML and HTML5](https://learn.freecodecamp.org/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements/) +- [Справочник по 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? Полное руководство. + +#### 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..ae142c42357e --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,185 @@ +# TopJava Release Notes +### Topjava 16 +- Выделил общий код реализации хранения в памяти в `InMemoryBaseRepositoryImpl` +- Сделал подтверждение для удаления записей +- Обновились видео 7-го занятия. [Выложил его как пример занятия, некоторые видео открыты](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md) +- Сделали валидации дублирования email через `WebDataBinder` и `Validator` + +### Topjava 15 +- Миграция на Servlet API 4.0 / Tomcat 9.x +- [Миграция на JDK11](http://javaops.ru/view/resources/jdk8_11) +- JUnit5 fix: junit-platform-surefire-provider не нужен +- Рефакторинг тестов: + - в `RootControllerTest.testUsers` для проверки используем `AssertionMatcher` адаптер + - вместо `content().json()` от `jsonassert` десериализуем json и используем сравнения через `AssertJ` +- В javascript место глабальных переменных и одинаковой функции обновления таблицы задаю их в объекте контекст, который передаю в `makeEditable()` как параметр +- Починил `back` в браузере после логина. Кнопки входа и регистрации отображаю только для `isAnonymous()` + +### Topjava 14 +- [Миграция на JUnit 5](http://javaops.ru/view/resources/junit5) +- Для измерения времени в тестах использую [Spring StopWatch](https://www.logicbig.com/how-to/code-snippets/jcode-spring-framework-stopwatch.html) +- `SimpleJdbcInsert` и `NamedParameterJdbcTemplate` конструируются (и берут настройки) из `jdbcTemplate` +- `AuthorizedUser` зарефакторился в `SecurityUtil` +- В javascript [заменил `var` на `let/const`](https://learn.javascript.ru/let-const). [Поддержка 95% браузеров](https://caniuse.com/#feat=const) +- Подправил UI фильтрации и заголовка страниц, добавилась кнопка `Cancel` в профиль +- Починил [баг в FireFox](https://bugzilla.mozilla.org/show_bug.cgi?id=884693): пустой ответ по ajax +- Сделал вход в приложение при нажании кнопок `Зайти как ...` +- Добавил регистрацию пользователя по REST +- Преименовал js файлы согласно [javascript filename naming convention](https://stackoverflow.com/questions/7273316/what-is-the-javascript-filename-naming-convention) +- Сделал проверку startTime/endTime на фильтре времени (после обновления datetimepicker до 2.5.20) + +### Topjava 13 +- [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) +- Добавил [Responsive behaviors](https://getbootstrap.com/docs/4.1/components/navbar/#responsive-behaviors) - при уменшении ширины экрана навигация сворачивается в кнопку +- Для отображения цвета еды и выключенного юзера использую [data-* атрибуты](https://developer.mozilla.org/ru/docs/Web/Guide/HTML/Using_data_attributes) +- В `inputField.tag` передаю как параметр код для локализации label, а в `i18n.jsp` передаю как параметр `page`. См. [JSP include action with parameter example](https://beginnersbook.com/2013/12/jsp-include-with-parameter-example) + +### Topjava 12 +- [Миграция на Spring 5](http://javaops.ru/view/resources/spring5) +- обновил версии: Ehcache 3.x, datatables, datetimepicker +- добавил видео решений HW0 с одним проходом +- поправил видео [Обзор Spring Framework. Spring Context](https://drive.google.com/file/d/1fBSLGEbc7YXBbmr_EwEHltCWNW_pUmIH). Дописал про Constructor injection. +- заменил видео про тетсирование сервисов. Вместо самодельных матчеров стали использовать [AssertJ](http://joel-costigliola.github.io/assertj/index.html). Видео [Тестирование UserService через AssertJ](https://drive.google.com/open?id=1SPMkWMYPvpk9i0TA7ioa-9Sn1EGBtClD), время 1:53 +- сделал [видео с jQuery конвертерами и дефолтными группами валидации при сохранении в базу](https://drive.google.com/open?id=1tOMOdmaP5OQ7iynwC77bdXSs-13Ommax) +- сделал [видео с новым `DelegatingPasswordEncoder` и Json READ/WRITE access](https://drive.google.com/file/d/1XZXvOThinzPw4EhigAUdo8-MWT_g8wOt/view?usp=sharing) +- убрал `AccessType.PROPERTY` для `AbstractBaseEntity.id` (см. [fixed HHH-3718](https://hibernate.atlassian.net/browse/HHH-3718)) +- удалил `PasswordUtil`, возвращаю статус `NO_CONTENT` для REST delete, убрал группы валидации в `UserTo` +- заменил в jQuery [success на done](https://stackoverflow.com/a/22213543/548473) +- вместо `lang.jsp` сделал общий `bodyHeader.jsp` + +### 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..2571ae7a0605 --- /dev/null +++ b/cv.md @@ -0,0 +1,109 @@ +## Составление резюме, подготовка к интервью, поиск работы + +![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/) +- Выбрать шаблон для резюме +- [GitHub Pages](https://pages.github.com/), Resume template +- Как продать свое резюме в 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 +- [Вопросы по классам коллекциям от JavaRush-1](http://info.javarush.ru/translation/2013/10/08/Часто-задаваемые-на-собеседованиях-вопросы-по-классам-коллекциям-в-Java-Часть-1-.html) +- [Вопросы по классам коллекциям от JavaRush-2](http://info.javarush.ru/translation/2013/10/08/Часто-задаваемые-на-собеседованиях-вопросы-по-классам-коллекциям-в-Java-Часть-2-.html) +- Тест на знание 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/view/topjava (в 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 (более актуально для Украины) + +## Как выжить на испытательном сроке +- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В + процессе формулирования очень часто приходит ее решение. +- Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки + разработчика. В логах надо читать верх самого нижнего эксепшена - там причина всей портянки. +- Грамотно уделяй время каждой проблеме. Две крайности - сразу бросаться за помощью и + бится нам ней часами. + Пробуй решить ее сам и в зависимости от проблемы выделяй на это разумное время. +- Если тебе что-то объясняют по проекту - обязательно записывай. +- Когда получаешь задачу - уточни все очень подробно. +- Получай в процессе решения обратную связь - в том ли направлении ты идешь. +- Не игнорируй совместные ланчи (курилки) +- Готовься к стендапам/летучкам. Задавай на них вменяемые вопросы. Выказывай заинтересованность +- Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи. +- [**5 вещей, которые разработчик должен сделать прежде чем попросить о помощи**](https://techrocks.ru/2018/07/16/5-things-a-developer-should-do-before-asking-for-help/) +- [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) +- [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) + +## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246) diff --git a/description.md b/description.md new file mode 100644 index 000000000000..d2448ca99e57 --- /dev/null +++ b/description.md @@ -0,0 +1,77 @@ +#### Разработка полнофункционального 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). + Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять 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. +- [Тестирование JPA сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw) +- Поддержка 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 +- Собеседование. Разработка ПО diff --git a/doc/lesson07.md b/doc/lesson07.md new file mode 100644 index 000000000000..9a59e0fda3d4 --- /dev/null +++ b/doc/lesson07.md @@ -0,0 +1,181 @@ +# [Онлайн стажировка Spring 5/JPA Enterprise (Topjava)](http://javaops.ru/view/topjava) +## REST, REST контроллеры, тестирование Spring MVC контроллеров (несколько видео открыто) +## [Формат обучения на стажировке](http://javaops.ru/#format) +- Не стоит стремиться прочитать все ссылки урока, их можно использовать как справочник. Гораздо важнее пройти основной материал урока и сделать Домашнее Задание +- Обязательно посмотри правила работы с патчами на проекте +- Делать Apply Patch лучше по одному, непосредственно перед видео на эту тему, а при просмотре видео сразу отслеживать все изменения кода проекта по изменению в патче (`Version Control->Local Changes-> Ctrl+D`) +- При первом Apply удобнее выбрать имя локального ченджлиста Name: Default. Далее все остальные патчи также будут в него попадать. +- Код проекта обновляется и не всегда совпадает с видео (можно увидеть как развивался проект). Изменения в проекте указываю после соответствующего патча. + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Разбор домашнего задания HW6 + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW6 +#### Apply 7_01_HW6_fix_tests.patch +> - Добавил `AbstractServiceTest.isJpaBased()` и `Assume.assumeTrue(isJpaBased())` в `AbstractMealServiceTest.testValidation()`. +> - Как вариант можно было вместо наследования от `AbstractJpaUserServiceTest` сделать `@Autowired(required = false) JpaUtil` (в этом случае, если в профиле jpa/datajpa не объявить `JpaUtil`, в тестах будет NPE) +> - В новой версии Spring классы `spring-mvc` требуют `WebApplicationContext`, поэтому поправил `inmemory.xml` + + +#### Apply 7_02_HW6_meals.patch +При переходе на AJAX `JspMealController` удалим за ненадобностью, возвращение всей еды `meals()` останется в `RootController`. + +#### Apply 7_03_HW6_fix_relative_url_utf8.patch +- Relative paths in JSP +- Spring redirect: prefix + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW6 Optional +#### Apply 7_04_HW6_optional_add_role.patch +`JdbcUserServiceTest` отвалились. Будем чинить в `7_06_HW6_optional_jdbc.patch` + +#### Apply 7_05_fix_hint_graph.patch +- В `JpaUserRepositoryImpl.getByEmail` DISTINCT попадает в запрос, хотя он там не нужен. Это просто указание Hibernate не дублировать данные. +Для оптимизации можно указать Hibernate делать запрос без distinct: [15.16.2. Using DISTINCT with entity queries](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct) +- Тест `DataJpaUserServiceTest.testGetWithMeals()` не работает для admin (у админа 2 роли и еда при JOIN дублируется). `DISTINCT` при несколький JOIN не помогает. +Оставил в графе только `meals`. Корректно поставить тип `LOAD`, чтобы остальные ассоциации доставались по стратегии модели. Однако [с типом по умолчанию `FETCH` роли также достаются](https://stackoverflow.com/a/46013654/548473) (похоже что бага). + +#### Apply 7_06_HW6_optional_jdbc.patch +> - реализовал в `JdbcUserRepositoryImpl.getAll()` доставание ролей через лямбду +> - в `insertRoles` поменял метод `batchUpdate` и сделал проверку на empty + +Еще интересные JDBC реализации: + - в `getAll()/ get()/ getByEmail()` делать запросы с `LEFT JOIN` и сделать реализацию `ResultSetExtractor` + - подключить зависимость `spring-data-jdbc-core`. Там есть готовый `OneToManyResultSetExtractor`. Можно посмотреть, как он реализован. + - реализация, зависимая от БД (для postgres): доставать агрегированные роли и делать им `split(",")`: +``` +... +``` + +## Занятие 7: +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Тестирование Spring MVC +#### Apply 7_07_controller_test.patch +> - в `MockMvc` добавился `CharacterEncodingFilter` +> - добавил `AllActiveProfileResolver` +> - реализация Ehcache нетранзакционная, после отката транзакции в тестах по `@Transactional` Hibernate кэш не восстанавливал роль для USER. Добавил очистку кэша Hibernate в `AbstractControllerTest.setUp` (с учетом того, что `Profiles.REPOSITORY_IMPLEMENTATION` можно переключить на `JDBC`). + +- Hamcrest +- Unit Testing of Spring MVC Controllers + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Миграция на JUnit 5](https://www.youtube.com/watch?v=YmLzT-j1hU4) +#### Apply 7_08_JUnit5.patch +> [No need `junit-platform-surefire-provider` dependency in `maven-surefire-plugin`](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven) + +- [JUnit 5 homepage](https://junit.org/junit5) +- [Overview](https://junit.org/junit5/docs/snapshot/user-guide/#overview) +- [10 интересных нововведений](https://habr.com/post/337700) +- Дополнительно: + - [Extension Model](https://junit.org/junit5/docs/current/user-guide/#extensions) + - [A Guide to JUnit 5](http://www.baeldung.com/junit-5) + - [Migrating from JUnit 4](http://www.baeldung.com/junit-5-migration) + - [Before and After Test Execution Callbacks](https://junit.org/junit5/docs/snapshot/user-guide/#extensions-lifecycle-callbacks-before-after-execution) + - [Conditional Test Execution](https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-conditional-execution) + - [Third party Extensions](https://github.com/junit-team/junit5/wiki/Third-party-Extensions) + - [Реализация assertThat](https://stackoverflow.com/questions/43280250) + + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. [Принципы REST. REST контроллеры](https://youtu.be/33_2yOck4ak) +#### Apply 7_09_rest_controller.patch + +- Понимание REST +- JSON (JavaScript Object Notation) +- [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/) +- [10 Best Practices for Better RESTful](http://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/) +- [Best practices for rest nested resources](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) +- Request mapping +- Дополнительно: + - JAX-RS vs Spring MVC + - RESTful API для сервера – делаем правильно (Часть 1) + - RESTful API для сервера – делаем правильно (Часть 2) + - И. Головач. RestAPI + - [value/name в аннотациях @PathVariable и @RequestParam](https://habr.com/ru/post/440214/) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. [Тестирование REST контроллеров. Jackson](https://drive.google.com/file/d/1aZm2qoMh4yL_-i3HhRoyZFjRAQx-15lO) +#### Apply 7_10_rest_test_jackson.patch +- [Jackson databind github](https://github.com/FasterXML/jackson-databind) +- [Jackson Annotation Examples](https://www.baeldung.com/jackson-annotations) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. [Кастомизация Jackson Object Mapper](https://drive.google.com/file/d/1CM6y1JhKG_yeLQE_iCDONnI7Agi4pBks) + +#### Apply 7_11_jackson_object_mapper.patch +- Сериализация hibernate lazy-loading с помощью jackson-datatype-hibernate +- Handle Java 8 dates with Jackson +- Дополнительно: + - Jackson JSON Serializer & Deserializer + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 8. [Тестирование REST контроллеров через JSONassert](https://drive.google.com/file/d/1oa3e0_tG57E71g6PW7_tfb3B61Qldctl) +#### Apply 7_12_json_assert_tests.patch +- [JSONassert](https://github.com/skyscreamer/JSONassert) +- [Java Code Examples for ObjectMapper](https://www.programcreek.com/java-api-examples/index.php?api=com.fasterxml.jackson.databind.ObjectMapper) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 9. Тестирование через SoapUi. UTF-8 +#### Apply 7_13_soapui_utf8_converter.patch +- Инструменты тестирования REST: + - SoapUi + - Написание HTTP-запросов с помощью Curl +(для Windows можно использовать Git Bash). Для работы с UTF-8 в Windows 10 нужны пляски с бубном: ["Язык и региональные стандарты" -> "Сопутствующие параметры" -> "Административные языковые параметры" -> "Изменить язык системы" -> галка "Бета-версия:Использовать Юникод (UTF-8) для поддержки языка во всем мире"](https://drive.google.com/open?id=1J1WquTv9wenJQ9ptMymXPYGnrvFzAV-L), перезагрузка. + - Postman + - IDEA: Tools->HTTP Client->... + - [Insomnia REST client](https://insomnia.rest/) + +**Импортировать проект в SoapUi из config\Topjava-soapui-project.xml. Response смотреть в формате JSON.** + +> Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text + +ResponseBody and UTF-8 + +## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы +> При выполнении тестов через MockMvc никаких изменений на базе не видно, почему оно не сохраняет? + +`AbstractControllerTest` аннотируется `@Transactional` - это означает, что тесты идут в транзакции и после каждого теста JUnit делает rollback базы. + +> Что получается в результате выполнения запроса `SELECT DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email`? В чем разница в SQL без `DISTINCT`. + +Запросы SQL можно посмотреть в логах. Те `DISTINCT` в `JPQL` влияет на то, как Hibernate обрабатывает дублирующие записи (с `DISTINCT` их исключает). Результат можно посмотреть в тестах или приложении, поставив брекпойнт. +По поводу `SQL DISTINCT` не стесняйтесь пользоваться google, например [оператор SQL DISTINCT](http://2sql.ru/novosti/sql-distinct/) + +> В чем заключается расширение функциональности hamcrest в нашем тесте, что нам пришлось его отдельно от JUnit прописывать? + +hamcrest-all используется в проверках `RootControllerTest`: `org.hamcrest.Matchers.*` + +> Jackson мы просто подключаем в помнике и спринг будет с ним работать без любых других настроек? + +Да, Spring смотрит в classpath и, если видит там Jackson, то подключает интеграцию с ним + +> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url, без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ? + +Так делают, только при отношении агрегация. В случае критериев, поиска или страничных данных они передаются как параметр. Смотри также: +- [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/) +- 10 Best Practices for Better RESTful + +> Что означает конструкция в `JsonUtil`: `reader.readValues(json)`; + +См. Generic Methods. Когда компилятор не может вывести тип, можно его уточнить при вызове generic метода. Не важно static или нет. + +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW07 + +- 1: Добавить тесты контроллеров: + - 1.1 `RootControllerTest.testMeals` для `meals.jsp` + - 1.2 `ResourceControllerTest` для `style.css` (проверить status и ContentType) +- 2: Реализовать `MealRestController` и протестировать его через `MealRestControllerTest` + - 2.1 cледите чтобы url в тестах совпадал с параметрами в методе контроллера. Можно добавить логирование `` для проверки маршрутизации. + - 2.2 в параметрах `getBetween` принимать `LocalDateTime` (конвертировать через @DATETIMEFORMAT WITH JAVA 8 DATE-TIME API), а передавать в тестах в формате `ISO_LOCAL_DATE_TIME` (например `'2011-12-03T10:15:30'`). Вызывать `super.getBetween()` пока без проверки на `null`, используя `toLocalDate()/toLocalTime()` (см. Optional п.3) + +#### Optional +- 3: Переделать `MealRestController.getBetween` на параметры `LocalDate/LocalTime` c раздельной фильтрацией по времени/дате, работающий при `null` значениях (см. демо и `JspMealController.getBetween`). Заменить `@DateTimeFormat` на свои LocalDate/LocalTime конверторы или форматтеры. + - Spring Type Conversion + - Spring Field Formatting + - Difference between Spring MVC formatters and converters +- 4: Протестировать `MealRestController` (SoapUi, curl, IDEA Test RESTful Web Service, Postman). Запросы `curl` занести в отдельный `md` файл (либо `README.md`) + +**На следующем занятии используется JavaScript/jQuery. Если у вас там пробелы, пройдите его основы** + +--------------------- +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW07 +- 1: Ошибка в тесте _Invalid read array from JSON_ обычно расшифровывается немного ниже: читайте внимательно. +- 2: Jackson и неизменяемые объекты +- 3: Jackson JSON Tutorial +- 4: Если у meal, приходящий в контроллер, поля null, проверьте `@RequestBody` перед параметром (данные приходят в формате JSON) +- 5: При проблемах с собственным форматтером убедитесь, что в конфигурации ` Совершенство достигнуто не тогда, когда нечего добавить, а тогда, когда нечего отнять + +_Антуан де Сент-Экзюпери_ + +- 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** +- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу** +- 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** +- 4: **учитывайте, что пользователей может быть ооочень много, а админов- мало** +- 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: + - 5.1 НЕ надо делать разные профили базы и работы с ней. + - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. + - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования. + - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. +- 6: базу лучше взять без установки (H2 или HSQLDB). Ваше приложение должно сразу запуститься **ОПТИМАЛЬНО- без всяких настроек** +- 7: по возможности сделать JUnit тесты +- 8: уделяйте внимание обработке ошибок +- 9: далаем REST API в соответствии с концепцией REST, **с учетом иерархии принадлежности объектов** + - [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/) + - 10 Best Practices for Better RESTful API +- 10: не смешивайте TO и Entity вместе. Лучше всего, если они будут независимыми друг от друга. +- 11: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера) +- 12: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) +- 13: **Историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** +- 14: Еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по всем полям! +- 15: Название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил. +- 16: **Используйте DATA-JPA** (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository). +- 17: В DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями. +- 18: На topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. +- 19: Проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. +- 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! +- 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). +- 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB +- 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! + +## Попробуйте подергать свое API по всем типичным сценариям ТЗ! +- Удобно использовать? Можно сделать проще? *Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`.* +- Сколько раз пришлось его вызвать API для типичного сценария (нарпимер посмотреть рестораны с едой)? +- Сколько запросов к базе было сделано? Можно ли сократить (например с FETCH/Graph или через кэширование)? +- **API ДОЛЖНО соответствовать принципам REST (см. ссылки выше)** +- **ОБЯЗАТЕЛЬНО: запустите `mvn test`- ошибок быть не должно** +- **ОБЯЗАТЕЛЬНО: запустите приложение без всяких предварительных настроек (базы, переменных окружения, ..), лучше на другом компьютере. Должно запускаться!** diff --git a/patches/1_0_fix.patch b/patches/1_0_fix.patch new file mode 100644 index 000000000000..f5e205e55771 --- /dev/null +++ b/patches/1_0_fix.patch @@ -0,0 +1,141 @@ +Index: src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (revision d41c2fa123fec99e1de1d43287e78d864ac08663) ++++ src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (revision d41c2fa123fec99e1de1d43287e78d864ac08663) +@@ -1,31 +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; +- +-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; +- } +-} +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1559792031321) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1559792031321) +@@ -0,0 +1,31 @@ ++package ru.javawebinar.topjava.util; ++ ++import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.model.MealTo; ++ ++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) ++ ); ++ getFilteredWithExcess(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); ++// .toLocalDate(); ++// .toLocalTime(); ++ } ++ ++ public static List getFilteredWithExcess(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ // TODO return filtered list with correctly excess field ++ return null; ++ } ++} +\ No newline at end of file +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (revision d41c2fa123fec99e1de1d43287e78d864ac08663) ++++ pom.xml (date 1559792096977) +@@ -24,7 +24,7 @@ + + org.apache.maven.plugins + maven-compiler-plugin +- 3.7.0 ++ 3.8.1 + + ${java.version} + ${java.version} +Index: src/main/java/ru/javawebinar/topjava/model/UserMeal.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMeal.java (revision d41c2fa123fec99e1de1d43287e78d864ac08663) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1559792031300) +@@ -2,14 +2,14 @@ + + import java.time.LocalDateTime; + +-public class UserMeal { ++public class Meal { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + +- public UserMeal(LocalDateTime dateTime, String description, int calories) { ++ public Meal(LocalDateTime dateTime, String description, int calories) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; +Index: src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java (revision d41c2fa123fec99e1de1d43287e78d864ac08663) ++++ src/main/java/ru/javawebinar/topjava/model/MealTo.java (date 1559792031313) +@@ -2,19 +2,19 @@ + + import java.time.LocalDateTime; + +-public class UserMealWithExceed { ++public class MealTo { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + +- private final boolean exceed; ++ private final boolean excess; + +- public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { ++ public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; +- this.exceed = exceed; ++ this.excess = excess; + } + } diff --git a/patches/1_1_HW0_stream.patch b/patches/1_1_HW0_stream.patch new file mode 100644 index 000000000000..09553d454d89 --- /dev/null +++ b/patches/1_1_HW0_stream.patch @@ -0,0 +1,116 @@ +Index: src/main/java/ru/javawebinar/topjava/model/Meal.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1549485461000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1549485942159) +@@ -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 int getCalories() { + return calories; + } ++ ++ public LocalDate getDate() { ++ return dateTime.toLocalDate(); ++ } ++ ++ public LocalTime getTime() { ++ return dateTime.toLocalTime(); ++ } + } +Index: src/main/java/ru/javawebinar/topjava/model/MealTo.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealTo.java (date 1549485461000) ++++ src/main/java/ru/javawebinar/topjava/model/MealTo.java (date 1549486218447) +@@ -17,4 +17,14 @@ + this.calories = calories; + this.excess = excess; + } +-} ++ ++ @Override ++ public String toString() { ++ return "MealTo{" + ++ "dateTime=" + dateTime + ++ ", description='" + description + '\'' + ++ ", calories=" + calories + ++ ", excess=" + excess + ++ '}'; ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1549485461000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1549486218458) +@@ -3,29 +3,41 @@ + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.model.MealTo; + ++import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.LocalTime; + import java.time.Month; + import java.util.Arrays; + import java.util.List; ++import java.util.Map; ++import java.util.stream.Collectors; + + public class MealsUtil { + public static void main(String[] args) { +- List 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) + ); +- getFilteredWithExcess(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +-// .toLocalDate(); +-// .toLocalTime(); ++ List mealsWithExcess = getFilteredWithExcess(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); ++ mealsWithExcess.forEach(System.out::println); + } + +- public static List getFilteredWithExcess(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- // TODO return filtered list with correctly excess field +- return null; ++ public static List getFilteredWithExcess(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 MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), ++ caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) ++ .collect(Collectors.toList()); + } + } +\ No newline at end of file diff --git a/patches/1_2_HW0_cycle.patch b/patches/1_2_HW0_cycle.patch new file mode 100644 index 000000000000..001610d49ca1 --- /dev/null +++ b/patches/1_2_HW0_cycle.patch @@ -0,0 +1,57 @@ +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1559793508000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1559793560039) +@@ -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 @@ + ); + List mealsWithExcess = getFilteredWithExcess(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExcess.forEach(System.out::println); ++ ++ System.out.println(getFilteredWithExcessByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExcess(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -35,9 +35,25 @@ + + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) +- .map(meal -> +- new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), +- caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) ++ .map(meal -> createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } ++ ++ public static List getFilteredWithExcessByCycle(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 mealsWithExcess = new ArrayList<>(); ++ meals.forEach(meal -> { ++ if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { ++ mealsWithExcess.add(createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); ++ } ++ }); ++ return mealsWithExcess; ++ } ++ ++ private static MealTo createWithExcess(Meal meal, boolean excess) { ++ return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); ++ } + } +\ No newline at end of file diff --git a/patches/1_3_HW0_optional2.patch b/patches/1_3_HW0_optional2.patch new file mode 100644 index 000000000000..f18e87034bea --- /dev/null +++ b/patches/1_3_HW0_optional2.patch @@ -0,0 +1,81 @@ +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1559793619000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1559793675454) +@@ -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 @@ + mealsWithExcess.forEach(System.out::println); + + System.out.println(getFilteredWithExcessByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ System.out.println(getFilteredWithExcessInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ System.out.println(getFilteredWithExcessInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExcess(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -53,6 +60,51 @@ + return mealsWithExcess; + } + ++ public static List getFilteredWithExcessInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ Collection> list = meals.stream() ++ .collect(Collectors.groupingBy(Meal::getDate)).values(); ++ ++ return list.stream().flatMap(dayMeals -> { ++ boolean excess = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; ++ return dayMeals.stream().filter(meal -> ++ TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .map(meal -> createWithExcess(meal, excess)); ++ }).collect(toList()); ++ } ++ ++ public static List getFilteredWithExcessInOnePass2(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 excess = dailySumOfCalories > caloriesPerDay; ++ return dailyMeals.stream().map(meal -> createWithExcess(meal, excess)); ++ } ++ } ++ ++ Collection> values = meals.stream() ++ .collect(Collectors.groupingBy(Meal::getDate, ++ Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) ++ ).values(); ++ ++ return values.stream().flatMap(identity()).collect(toList()); ++ } ++ + private static MealTo createWithExcess(Meal meal, boolean excess) { + return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + } diff --git a/patches/1_4_switch_to_war.patch b/patches/1_4_switch_to_war.patch new file mode 100644 index 000000000000..11d6c95fb523 --- /dev/null +++ b/patches/1_4_switch_to_war.patch @@ -0,0 +1,84 @@ +Index: src/main/webapp/WEB-INF/web.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/WEB-INF/web.xml (date 1559793192979) ++++ src/main/webapp/WEB-INF/web.xml (date 1559793192979) +@@ -0,0 +1,19 @@ ++ ++ ++ Topjava ++ ++ ++ userServlet ++ ru.javawebinar.topjava.web.UserServlet ++ 0 ++ ++ ++ userServlet ++ /users ++ ++ ++ +Index: src/main/webapp/users.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/users.jsp (date 1559793298022) ++++ src/main/webapp/users.jsp (date 1559793298022) +@@ -0,0 +1,11 @@ ++<%@ page contentType="text/html;charset=UTF-8" language="java" %> ++ ++ ++ Users ++ ++ ++

Home

++
++

Users

++ ++ +\ No newline at end of file +Index: src/main/webapp/index.html +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/index.html (date 1559793192985) ++++ src/main/webapp/index.html (date 1559793192985) +@@ -0,0 +1,13 @@ ++ ++ ++ ++ Java Enterprise (Topjava) ++ ++ ++

Проект Java Enterprise (Topjava)

++
++ ++ ++ +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1559793186000) ++++ pom.xml (date 1559793278070) +@@ -4,7 +4,7 @@ + + ru.javawebinar + topjava +- jar ++ war + + 1.0-SNAPSHOT + diff --git a/patches/1_5_add_servlet_api.patch b/patches/1_5_add_servlet_api.patch new file mode 100644 index 000000000000..614b5dcfc33c --- /dev/null +++ b/patches/1_5_add_servlet_api.patch @@ -0,0 +1,45 @@ +Index: src/main/java/ru/javawebinar/topjava/web/UserServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1559793331117) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1559793331117) +@@ -0,0 +1,15 @@ ++package ru.javawebinar.topjava.web; ++ ++import javax.servlet.ServletException; ++import javax.servlet.http.HttpServlet; ++import javax.servlet.http.HttpServletRequest; ++import javax.servlet.http.HttpServletResponse; ++import java.io.IOException; ++ ++public class UserServlet extends HttpServlet { ++ ++ @Override ++ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ++ request.getRequestDispatcher("/users.jsp").forward(request, response); ++ } ++} +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1559793326000) ++++ pom.xml (date 1559793331149) +@@ -34,6 +34,14 @@ + + + ++ ++ ++ ++ javax.servlet ++ javax.servlet-api ++ 4.0.1 ++ provided ++ + + + diff --git a/patches/1_6_forward_to_redirect.patch b/patches/1_6_forward_to_redirect.patch new file mode 100644 index 000000000000..35fc459218a3 --- /dev/null +++ b/patches/1_6_forward_to_redirect.patch @@ -0,0 +1,32 @@ +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1481146514000) ++++ pom.xml (revision ) +@@ -19,7 +19,7 @@ + + + topjava +- install ++ package + + + org.apache.maven.plugins +Index: src/main/java/ru/javawebinar/topjava/web/UserServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1481146514000) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (revision ) +@@ -13,6 +13,7 @@ + public class UserServlet extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { +- request.getRequestDispatcher("/users.jsp").forward(request, response); ++// request.getRequestDispatcher("/users.jsp").forward(request, response); ++ response.sendRedirect("users.jsp"); + } + } diff --git a/patches/1_7_logging.patch b/patches/1_7_logging.patch new file mode 100644 index 000000000000..72fad180b032 --- /dev/null +++ b/patches/1_7_logging.patch @@ -0,0 +1,113 @@ +Index: src/main/resources/logback.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/logback.xml (revision ) ++++ src/main/resources/logback.xml (revision ) +@@ -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 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1499899119000) ++++ pom.xml (revision ) +@@ -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 ++ + + + +Index: src/main/java/ru/javawebinar/topjava/web/UserServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1499899119000) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (revision ) +@@ -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/patches/2_1_HW1.patch b/patches/2_1_HW1.patch new file mode 100644 index 000000000000..094b2ff3bb93 --- /dev/null +++ b/patches/2_1_HW1.patch @@ -0,0 +1,405 @@ +Index: src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) +@@ -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; +- } +-} +Index: src/main/java/ru/javawebinar/topjava/Main.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/Main.java (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ src/main/java/ru/javawebinar/topjava/Main.java (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) +@@ -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!"); +- } +-} +Index: src/main/java/ru/javawebinar/topjava/model/MealTo.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealTo.java (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ src/main/java/ru/javawebinar/topjava/model/MealTo.java (date 1560373099252) +@@ -18,6 +18,22 @@ + this.excess = excess; + } + ++ public LocalDateTime getDateTime() { ++ return dateTime; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ ++ public int getCalories() { ++ return calories; ++ } ++ ++ public boolean isExcess() { ++ return excess; ++ } ++ + @Override + public String toString() { + return "MealTo{" + +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560373099285) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560373099285) +@@ -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.getWithExcess(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ } ++} +Index: src/main/webapp/WEB-INF/web.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/WEB-INF/web.xml (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ src/main/webapp/WEB-INF/web.xml (date 1560373099310) +@@ -16,4 +16,14 @@ + /users + + ++ ++ mealServlet ++ ru.javawebinar.topjava.web.MealServlet ++ 0 ++ ++ ++ mealServlet ++ /meals ++ ++ + +Index: src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560373099258) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560373099258) +@@ -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); ++ } ++} +Index: src/main/webapp/index.html +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/index.html (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ src/main/webapp/index.html (date 1560373099315) +@@ -8,6 +8,7 @@ +
+ + + +Index: src/main/webapp/meals.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/meals.jsp (date 1560373493992) ++++ src/main/webapp/meals.jsp (date 1560373493992) +@@ -0,0 +1,48 @@ ++<%@ page contentType="text/html;charset=UTF-8" language="java" %> ++<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> ++<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> ++<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> ++<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> ++ ++ ++ Meal list ++ ++ ++ ++
++

Home

++
++

Meals

++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
DateDescriptionCalories
++ <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> ++ <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> ++ <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> ++ ${fn:formatDateTime(meal.dateTime)} ++ ${meal.description}${meal.calories}
++
++ ++ +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/web/UserServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/UserServlet.java (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1560373099291) +@@ -15,9 +15,7 @@ + + @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); + } + } +Index: src/main/webapp/WEB-INF/tld/functions.tld +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/WEB-INF/tld/functions.tld (date 1560373099304) ++++ src/main/webapp/WEB-INF/tld/functions.tld (date 1560373099304) +@@ -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) ++ ++ +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ pom.xml (date 1560373099335) +@@ -67,6 +67,12 @@ + 4.0.1 + provided +
++ ++ ++ javax.servlet ++ jstl ++ 1.2 ++ +
+ + +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (revision 85736501bbe92fdff688eceeb7cdc63dacb77961) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1560373099276) +@@ -7,33 +7,35 @@ + 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.function.Predicate; + 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 mealsWithExcess = getFilteredWithExcess(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); +- mealsWithExcess.forEach(System.out::println); ++ 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; + +- System.out.println(getFilteredWithExcessByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); +- System.out.println(getFilteredWithExcessInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); +- System.out.println(getFilteredWithExcessInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ public static List getWithExcess(List meals, int caloriesPerDay) { ++ return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); + } + +- public static List getFilteredWithExcess(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ public static List getFilteredWithExcess(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { ++ return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); ++ } ++ ++ private static List getFilteredWithExcess(List meals, int caloriesPerDay, Predicate filter) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +@@ -41,68 +43,9 @@ + ); + + return meals.stream() +- .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .filter(filter) + .map(meal -> createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) +- .collect(Collectors.toList()); +- } +- +- public static List getFilteredWithExcessByCycle(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 mealsWithExcess = new ArrayList<>(); +- meals.forEach(meal -> { +- if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { +- mealsWithExcess.add(createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); +- } +- }); +- return mealsWithExcess; +- } +- +- public static List getFilteredWithExcessInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- Collection> list = meals.stream() +- .collect(Collectors.groupingBy(Meal::getDate)).values(); +- +- return list.stream().flatMap(dayMeals -> { +- boolean excess = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; +- return dayMeals.stream().filter(meal -> +- TimeUtil.isBetween(meal.getTime(), startTime, endTime)) +- .map(meal -> createWithExcess(meal, excess)); +- }).collect(toList()); +- } +- +- public static List getFilteredWithExcessInOnePass2(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 excess = dailySumOfCalories > caloriesPerDay; +- return dailyMeals.stream().map(meal -> createWithExcess(meal, excess)); +- } +- } +- +- Collection> values = meals.stream() +- .collect(Collectors.groupingBy(Meal::getDate, +- Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) +- ).values(); +- +- return values.stream().flatMap(identity()).collect(toList()); ++ .collect(toList()); + } + + private static MealTo createWithExcess(Meal meal, boolean excess) { diff --git a/patches/2_2_HW1_optional.patch b/patches/2_2_HW1_optional.patch new file mode 100644 index 000000000000..01637eb9d871 --- /dev/null +++ b/patches/2_2_HW1_optional.patch @@ -0,0 +1,408 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1560374116352) ++++ src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1560374116352) +@@ -0,0 +1,17 @@ ++package ru.javawebinar.topjava.repository; ++ ++import ru.javawebinar.topjava.model.Meal; ++ ++import java.util.Collection; ++ ++public interface MealRepository { ++ Meal save(Meal meal); ++ ++ // false if not found ++ boolean delete(int id); ++ ++ // null if not found ++ Meal get(int id); ++ ++ Collection getAll(); ++} +Index: src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java (date 1560374116362) ++++ src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java (date 1560374116362) +@@ -0,0 +1,45 @@ ++package ru.javawebinar.topjava.repository; ++ ++import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.util.MealsUtil; ++ ++import java.util.Collection; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++public class 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; ++ } ++ // treat case: update, but absent in storage ++ return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); ++ } ++ ++ @Override ++ public boolean delete(int id) { ++ return repository.remove(id) != null; ++ } ++ ++ @Override ++ public Meal get(int id) { ++ return repository.get(id); ++ } ++ ++ @Override ++ public Collection getAll() { ++ return repository.values(); ++ } ++} ++ +Index: src/main/webapp/mealForm.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/mealForm.jsp (date 1560374291277) ++++ src/main/webapp/mealForm.jsp (date 1560374291277) +@@ -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:
++
++
++ ++ ++
++
++ ++ +Index: src/main/java/ru/javawebinar/topjava/model/MealTo.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealTo.java (date 1560373522000) ++++ src/main/java/ru/javawebinar/topjava/model/MealTo.java (date 1560373540380) +@@ -3,6 +3,8 @@ + import java.time.LocalDateTime; + + public class MealTo { ++ private final Integer id; ++ + private final LocalDateTime dateTime; + + private final String description; +@@ -11,13 +13,18 @@ + + private final boolean excess; + +- public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) { ++ public MealTo(Integer id, LocalDateTime dateTime, String description, int calories, boolean excess) { ++ this.id = id; + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.excess = excess; + } + ++ public Integer getId() { ++ return id; ++ } ++ + public LocalDateTime getDateTime() { + return dateTime; + } +@@ -37,7 +44,8 @@ + @Override + public String toString() { + return "MealTo{" + +- "dateTime=" + dateTime + ++ "id=" + id + ++ ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", excess=" + excess + +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560373522000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560373540398) +@@ -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.parseInt(request.getParameter("calories"))); ++ ++ log.info(meal.isNew() ? "Create {}" : "Update {}", meal); ++ repository.save(meal); ++ response.sendRedirect("meals"); ++ } ++ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { +- log.info("getAll"); +- request.setAttribute("meals", MealsUtil.getWithExcess(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.getWithExcess(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ break; ++ } ++ } ++ ++ private int getId(HttpServletRequest request) { ++ String paramId = Objects.requireNonNull(request.getParameter("id")); ++ return Integer.parseInt(paramId); + } + } +Index: src/main/java/ru/javawebinar/topjava/model/Meal.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1560373522000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1560373540375) +@@ -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 @@ + 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 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 + ++ '}'; ++ } + } +Index: src/main/webapp/meals.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/meals.jsp (date 1560373522000) ++++ src/main/webapp/meals.jsp (date 1560374168935) +@@ -21,12 +21,16 @@ +

Home

+
+

Meals

++ Add Meal ++

+ + + + + + ++ ++ + + + +@@ -40,6 +44,8 @@ + + + ++ ++ + + +
DateDescriptionCalories
${meal.description}${meal.calories}UpdateDelete
+Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1560373522000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1560373540395) +@@ -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.function.Predicate; +@@ -27,15 +28,15 @@ + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + +- public static List getWithExcess(List meals, int caloriesPerDay) { ++ public static List getWithExcess(Collection meals, int caloriesPerDay) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); + } + +- public static List getFilteredWithExcess(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { ++ public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); + } + +- private static List getFilteredWithExcess(List meals, int caloriesPerDay, Predicate filter) { ++ private static List getFilteredWithExcess(Collection meals, int caloriesPerDay, Predicate filter) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +@@ -49,6 +50,6 @@ + } + + private static MealTo createWithExcess(Meal meal, boolean excess) { +- return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); ++ return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + } + } +\ No newline at end of file diff --git a/patches/2_3_app_layers.patch b/patches/2_3_app_layers.patch new file mode 100644 index 000000000000..384aabbf905d --- /dev/null +++ b/patches/2_3_app_layers.patch @@ -0,0 +1,569 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (date 1560374363612) ++++ src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (date 1560374363612) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/model/User.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/User.java (date 1560374363608) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1560374363608) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/model/Role.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/Role.java (date 1560374363603) ++++ src/main/java/ru/javawebinar/topjava/model/Role.java (date 1560374363603) +@@ -0,0 +1,6 @@ ++package ru.javawebinar.topjava.model; ++ ++public enum Role { ++ ROLE_USER, ++ ROLE_ADMIN ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1560374533075) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1560374533075) +@@ -0,0 +1,24 @@ ++package ru.javawebinar.topjava.model; ++ ++public abstract class AbstractNamedEntity extends AbstractBaseEntity { ++ ++ protected String name; ++ ++ protected AbstractNamedEntity(Integer id, String name) { ++ super(id); ++ this.name = name; ++ } ++ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ public String getName() { ++ return this.name; ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString() + '(' + name + ')'; ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/service/UserService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1560374363626) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1560374363626) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java (date 1560374363621) ++++ src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java (date 1560374363621) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/service/MealService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1560374363617) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1560374363617) +@@ -0,0 +1,4 @@ ++package ru.javawebinar.topjava.service; ++ ++public interface MealService { ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java (date 1560374363641) ++++ src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java (date 1560374363641) +@@ -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 T checkNotFoundWithId(T object, int id) { ++ return checkNotFound(object, "id=" + id); ++ } ++ ++ public static void checkNotFoundWithId(boolean found, int id) { ++ checkNotFound(found, "id=" + id); ++ } ++ ++ public static T checkNotFound(T object, String msg) { ++ checkNotFound(object != null, msg); ++ return object; ++ } ++ ++ public static void checkNotFound(boolean found, String msg) { ++ if (!found) { ++ throw new NotFoundException("Not found entity with " + msg); ++ } ++ } ++ ++ public static void checkNew(AbstractBaseEntity entity) { ++ if (!entity.isNew()) { ++ throw new IllegalArgumentException(entity + " must be new (id=null)"); ++ } ++ } ++ ++ public static void assureIdConsistent(AbstractBaseEntity entity, int id) { ++// conservative when you reply, but accept liberally (http://stackoverflow.com/a/32728226/548473) ++ if (entity.isNew()) { ++ entity.setId(id); ++ } else if (entity.getId() != id) { ++ throw new IllegalArgumentException(entity + " must be with id=" + id); ++ } ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java (date 1560374363636) ++++ src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java (date 1560374363636) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560375116421) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560375116421) +@@ -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) throws NotFoundException { ++ checkNotFoundWithId(repository.save(user), user.getId()); ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1560374363654) ++++ src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1560374363654) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java (date 1560374363646) ++++ src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java (date 1560374363646) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1560374363692) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1560374363692) +@@ -0,0 +1,14 @@ ++package ru.javawebinar.topjava.web; ++ ++import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; ++ ++public class SecurityUtil { ++ ++ public static int authUserId() { ++ return 1; ++ } ++ ++ public static int authUserCaloriesPerDay() { ++ return DEFAULT_CALORIES_PER_DAY; ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1560374363664) ++++ src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1560374363664) +@@ -0,0 +1,20 @@ ++package ru.javawebinar.topjava.web.user; ++ ++import ru.javawebinar.topjava.model.User; ++ ++import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; ++ ++public class ProfileRestController extends AbstractUserController { ++ ++ public User get() { ++ return super.get(authUserId()); ++ } ++ ++ public void delete() { ++ super.delete(authUserId()); ++ } ++ ++ public void update(User user) { ++ super.update(user, authUserId()); ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1560374363659) ++++ src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1560374363659) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1560374445187) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1560374445187) +@@ -0,0 +1,26 @@ ++package ru.javawebinar.topjava.model; ++ ++public abstract class AbstractBaseEntity { ++ protected Integer id; ++ ++ protected AbstractBaseEntity(Integer id) { ++ this.id = id; ++ } ++ ++ public void setId(Integer id) { ++ this.id = id; ++ } ++ ++ public Integer getId() { ++ return id; ++ } ++ ++ public boolean isNew() { ++ return this.id == null; ++ } ++ ++ @Override ++ public String toString() { ++ return getClass().getSimpleName() + ":" + id; ++ } ++} +\ No newline at end of file diff --git a/patches/2_4_add_spring_context.patch b/patches/2_4_add_spring_context.patch new file mode 100644 index 000000000000..e4a9217061c4 --- /dev/null +++ b/patches/2_4_add_spring_context.patch @@ -0,0 +1,167 @@ +Index: src/main/java/ru/javawebinar/topjava/SpringMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560376843279) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560376843279) +@@ -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("inmemoryUserRepository"); ++ UserRepository userRepository = appCtx.getBean(UserRepository.class); ++ userRepository.getAll(); ++ appCtx.close(); ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java (date 1560376843267) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java (date 1560376843267) +@@ -0,0 +1,43 @@ ++package ru.javawebinar.topjava.repository.inmemory; ++ ++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 InMemoryUserRepositoryImpl implements UserRepository { ++ private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.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; ++ } ++} +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (date 1560376843285) ++++ src/main/resources/spring/spring-app.xml (date 1560376843285) +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java (date 1560376834000) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java (date 1560376843207) +@@ -1,6 +1,7 @@ +-package ru.javawebinar.topjava.repository; ++package ru.javawebinar.topjava.repository.inmemory; + + import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.repository.MealRepository; + import ru.javawebinar.topjava.util.MealsUtil; + + import java.util.Collection; +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560376834000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560376843274) +@@ -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.inmemory.InMemoryMealRepositoryImpl; + import ru.javawebinar.topjava.util.MealsUtil; + + import javax.servlet.ServletConfig; +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1560376834000) ++++ pom.xml (date 1560376880020) +@@ -16,6 +16,8 @@ + UTF-8 + UTF-8 + ++ 5.1.7.RELEASE ++ + + 1.2.3 + 1.7.25 +@@ -46,19 +48,19 @@ + compile + + +- +- org.slf4j +- jcl-over-slf4j +- ${slf4j.version} +- runtime +- +- + + ch.qos.logback + logback-classic + ${logback.version} + runtime + ++ ++ ++ ++ org.springframework ++ spring-context ++ ${spring.version} ++ + + + diff --git a/patches/2_5_dependency_injection.patch b/patches/2_5_dependency_injection.patch new file mode 100644 index 000000000000..4ae04fc9241f --- /dev/null +++ b/patches/2_5_dependency_injection.patch @@ -0,0 +1,63 @@ +Index: src/main/java/ru/javawebinar/topjava/SpringMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560377076000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560377086177) +@@ -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 @@ + // UserRepository userRepository = (UserRepository) appCtx.getBean("inmemoryUserRepository"); + UserRepository userRepository = appCtx.getBean(UserRepository.class); + userRepository.getAll(); ++ ++ UserService userService = appCtx.getBean(UserService.class); ++ userService.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); ++ + appCtx.close(); + } + } +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (date 1560377076000) ++++ src/main/resources/spring/spring-app.xml (date 1560377086182) +@@ -4,4 +4,7 @@ + + + ++ ++ ++ + +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560377076000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560377086171) +@@ -13,6 +13,10 @@ + + private UserRepository repository; + ++ public void setRepository(UserRepository repository) { ++ this.repository = repository; ++ } ++ + @Override + public User create(User user) { + return repository.save(user); diff --git a/patches/2_6_annotation_processing.patch b/patches/2_6_annotation_processing.patch new file mode 100644 index 000000000000..4619733f8825 --- /dev/null +++ b/patches/2_6_annotation_processing.patch @@ -0,0 +1,187 @@ +Index: src/main/java/ru/javawebinar/topjava/SpringMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560377185000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560377199354) +@@ -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("inmemoryUserRepository"); +- UserRepository userRepository = appCtx.getBean(UserRepository.class); +- userRepository.getAll(); +- +- UserService userService = appCtx.getBean(UserService.class); +- userService.create(new User(null, "userName", "email@mail.ru", "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@mail.ru", "password", Role.ROLE_ADMIN)); ++ } + } + } +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java (date 1560377185000) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java (date 1560377199314) +@@ -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 InMemoryUserRepositoryImpl implements UserRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); + +Index: src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1560377185000) ++++ src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1560377199347) +@@ -1,9 +1,11 @@ + package ru.javawebinar.topjava.web.user; + ++import org.springframework.stereotype.Controller; + import ru.javawebinar.topjava.model.User; + + import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; + ++@Controller + public class ProfileRestController extends AbstractUserController { + + public User get() { +Index: src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1560377185000) ++++ src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1560377199340) +@@ -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 +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (date 1560377185000) ++++ src/main/resources/spring/spring-app.xml (date 1560377199361) +@@ -1,10 +1,24 @@ + ++ 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 +Index: src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560377185000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560377199326) +@@ -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,14 +11,12 @@ + import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; + import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + ++@Service + public class UserServiceImpl implements UserService { + ++ @Autowired + private UserRepository repository; + +- public void setRepository(UserRepository repository) { +- this.repository = repository; +- } +- + @Override + public User create(User user) { + return repository.save(user); +Index: src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1560377185000) ++++ src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1560377199334) +@@ -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/patches/2_7_constructor_injection.patch b/patches/2_7_constructor_injection.patch new file mode 100644 index 000000000000..20c935d75f04 --- /dev/null +++ b/patches/2_7_constructor_injection.patch @@ -0,0 +1,37 @@ +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (date 1560377508000) ++++ src/main/resources/spring/spring-app.xml (date 1560377519364) +@@ -8,7 +8,7 @@ + + + +- ++ + + --> + +Index: src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560377508000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1560377519358) +@@ -14,8 +14,12 @@ + @Service + public class UserServiceImpl implements UserService { + ++ private final UserRepository repository; ++ + @Autowired +- private UserRepository repository; ++ public UserServiceImpl(UserRepository repository) { ++ this.repository = repository; ++ } + + @Override + public User create(User user) { diff --git a/patches/3_01_HW2_repository.patch b/patches/3_01_HW2_repository.patch new file mode 100644 index 000000000000..3585b0bfac19 --- /dev/null +++ b/patches/3_01_HW2_repository.patch @@ -0,0 +1,322 @@ +Index: src/main/java/ru/javawebinar/topjava/util/Util.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/Util.java (date 1560984144262) ++++ src/main/java/ru/javawebinar/topjava/util/Util.java (date 1560984144262) +@@ -0,0 +1,7 @@ ++package ru.javawebinar.topjava.util; ++ ++public class Util { ++ public static > boolean isBetween(T value, T start, T end) { ++ return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560984027000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560984144269) +@@ -39,7 +39,7 @@ + Integer.parseInt(request.getParameter("calories"))); + + log.info(meal.isNew() ? "Create {}" : "Update {}", meal); +- repository.save(meal); ++ repository.save(meal, SecurityUtil.authUserId()); + response.sendRedirect("meals"); + } + +@@ -50,15 +50,15 @@ + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); +- log.info("Delete {}", id); +- repository.delete(id); ++ log.info("delete {}", id); ++ repository.delete(id, SecurityUtil.authUserId()); + response.sendRedirect("meals"); + break; + case "create": + case "update": + final Meal meal = "create".equals(action) ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : +- repository.get(getId(request)); ++ repository.get(getId(request), SecurityUtil.authUserId()); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); + break; +@@ -66,7 +66,7 @@ + default: + log.info("getAll"); + request.setAttribute("meals", +- MealsUtil.getWithExcess(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ MealsUtil.getWithExcess(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1560984027000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1560984144255) +@@ -33,7 +33,7 @@ + } + + public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { +- return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); ++ return getFilteredWithExcess(meals, caloriesPerDay, meal -> Util.isBetween(meal.getTime(), startTime, endTime)); + } + + private static List getFilteredWithExcess(Collection meals, int caloriesPerDay, Predicate filter) { +Index: src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560984027000) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560984144246) +@@ -1,16 +1,11 @@ + package ru.javawebinar.topjava.util; + + import java.time.LocalDateTime; +-import java.time.LocalTime; + import java.time.format.DateTimeFormatter; + + public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + +- public static boolean 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); + } +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560984027000) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560985035872) +@@ -1,46 +1,78 @@ + package ru.javawebinar.topjava.repository.inmemory; + ++import org.springframework.util.CollectionUtils; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; + import ru.javawebinar.topjava.util.MealsUtil; ++import ru.javawebinar.topjava.util.Util; + +-import java.util.Collection; ++import java.time.LocalDateTime; ++import java.time.Month; ++import java.util.Collections; ++import java.util.Comparator; ++import java.util.List; + import java.util.Map; + import java.util.concurrent.ConcurrentHashMap; + import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++ ++import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.ADMIN_ID; ++import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.USER_ID; + + public class InMemoryMealRepository implements MealRepository { +- private Map repository = new ConcurrentHashMap<>(); ++ ++ // Map userId -> (mealId-> meal) ++ private Map> usersMealsMap = 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 = usersMealsMap.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 boolean delete(int id) { +- return repository.remove(id) != null; ++ public boolean delete(int id, int userId) { ++ Map meals = usersMealsMap.get(userId); ++ return meals != null && meals.remove(id) != null; + } + + @Override +- public Meal get(int id) { +- return repository.get(id); ++ public Meal get(int id, int userId) { ++ Map meals = usersMealsMap.get(userId); ++ return meals == null ? null : meals.get(id); + } + + @Override +- public Collection getAll() { +- return repository.values(); ++ public List getAll(int userId) { ++ return getAllFiltered(userId, meal -> true); + } +-} ++ ++ @Override ++ public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { ++ return getAllFiltered(userId, meal -> Util.isBetween(meal.getDateTime(), startDateTime, endDateTime)); ++ } + ++ private List getAllFiltered(int userId, Predicate filter) { ++ Map meals = usersMealsMap.get(userId); ++ return CollectionUtils.isEmpty(meals) ? Collections.emptyList() : ++ meals.values().stream() ++ .filter(filter) ++ .sorted(Comparator.comparing(Meal::getDateTime).reversed()) ++ .collect(Collectors.toList()); ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1560984027000) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1560984882455) +@@ -1,45 +1,57 @@ + package ru.javawebinar.topjava.repository.inmemory; + +-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.Comparator; + import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.stream.Collectors; + + @Repository + public class InMemoryUserRepository implements UserRepository { +- private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepository.class); + +- @Override +- public boolean delete(int id) { +- log.info("delete {}", id); +- return true; +- } ++ public static final int USER_ID = 1; ++ public static final int ADMIN_ID = 2; ++ ++ private Map usersMap = new ConcurrentHashMap<>(); ++ private AtomicInteger counter = new AtomicInteger(0); + + @Override + public User save(User user) { +- log.info("save {}", user); +- return user; ++ if (user.isNew()) { ++ user.setId(counter.incrementAndGet()); ++ usersMap.put(user.getId(), user); ++ return user; ++ } ++ return usersMap.computeIfPresent(user.getId(), (id, oldUser) -> user); + } ++ ++ @Override ++ public boolean delete(int id) { ++ return usersMap.remove(id) != null; ++ } + + @Override + public User get(int id) { +- log.info("get {}", id); +- return null; ++ return usersMap.get(id); + } + + @Override + public List getAll() { +- log.info("getAll"); +- return Collections.emptyList(); ++ return usersMap.values().stream() ++ .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) ++ .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { +- log.info("getByEmail {}", email); +- return null; ++ return usersMap.values().stream() ++ .filter(u -> email.equals(u.getEmail())) ++ .findFirst() ++ .orElse(null); + } +-} ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1560984027000) ++++ src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1560984867247) +@@ -2,17 +2,22 @@ + + import ru.javawebinar.topjava.model.Meal; + +-import java.util.Collection; ++import java.time.LocalDateTime; ++import java.util.List; + + public interface MealRepository { +- // null if not found, when updated +- Meal save(Meal meal); ++ // null if updated meal do not belong to userId ++ Meal save(Meal meal, int userId); + +- // false if not found +- boolean delete(int id); ++ // false if meal do not belong to userId ++ boolean delete(int id, int userId); + +- // null if not found +- 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/patches/3_02_HW2_meal_layers.patch b/patches/3_02_HW2_meal_layers.patch new file mode 100644 index 000000000000..85c94beece95 --- /dev/null +++ b/patches/3_02_HW2_meal_layers.patch @@ -0,0 +1,536 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java (date 1560986059413) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java (date 1560986059413) +@@ -0,0 +1,38 @@ ++package ru.javawebinar.topjava.repository.inmemory; ++ ++import org.springframework.stereotype.Repository; ++import ru.javawebinar.topjava.model.AbstractBaseEntity; ++ ++import java.util.Collection; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++@Repository ++public class InMemoryBaseRepository { ++ ++ private static AtomicInteger counter = new AtomicInteger(0); ++ ++ private Map entryMap = new ConcurrentHashMap<>(); ++ ++ public T save(T entry) { ++ if (entry.isNew()) { ++ entry.setId(counter.incrementAndGet()); ++ entryMap.put(entry.getId(), entry); ++ return entry; ++ } ++ return entryMap.computeIfPresent(entry.getId(), (id, oldT) -> entry); ++ } ++ ++ public boolean delete(int id) { ++ return entryMap.remove(id) != null; ++ } ++ ++ public T get(int id) { ++ return entryMap.get(id); ++ } ++ ++ Collection getCollection() { ++ return entryMap.values(); ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java (date 1560986059482) +@@ -1,8 +1,76 @@ + 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.model.Meal; + import ru.javawebinar.topjava.service.MealService; ++import ru.javawebinar.topjava.to.MealTo; ++import ru.javawebinar.topjava.util.MealsUtil; ++import ru.javawebinar.topjava.web.SecurityUtil; ++ ++import java.time.LocalDate; ++import java.time.LocalTime; ++import java.util.List; + ++import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; ++import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; ++ ++@Controller + public class MealRestController { +- private MealService service; ++ private static final Logger log = LoggerFactory.getLogger(MealRestController.class); ++ ++ private final MealService service; ++ ++ @Autowired ++ public MealRestController(MealService service) { ++ this.service = service; ++ } ++ ++ public Meal get(int id) { ++ int userId = SecurityUtil.authUserId(); ++ log.info("get meal {} for user {}", id, userId); ++ return service.get(id, userId); ++ } ++ ++ public void delete(int id) { ++ int userId = SecurityUtil.authUserId(); ++ log.info("delete meal {} for user {}", id, userId); ++ service.delete(id, userId); ++ } ++ ++ public List getAll() { ++ int userId = SecurityUtil.authUserId(); ++ log.info("getAll for user {}", userId); ++ return MealsUtil.getWithExcess(service.getAll(userId), SecurityUtil.authUserCaloriesPerDay()); ++ } ++ ++ public Meal create(Meal meal) { ++ int userId = SecurityUtil.authUserId(); ++ checkNew(meal); ++ log.info("create {} for user {}", meal, userId); ++ return service.create(meal, userId); ++ } ++ ++ public void update(Meal meal, int id) { ++ int userId = SecurityUtil.authUserId(); ++ 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 = SecurityUtil.authUserId(); ++ log.info("getBetween dates({} - {}) time({} - {}) for user {}", startDate, endDate, startTime, endTime, userId); ++ ++ List mealsDateFiltered = service.getBetweenDates(startDate, endDate, userId); ++ return MealsUtil.getFilteredWithExcess(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), startTime, endTime); ++ } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1560986059472) +@@ -1,7 +1,8 @@ + package ru.javawebinar.topjava.util; + ++import org.springframework.lang.Nullable; + import ru.javawebinar.topjava.model.Meal; +-import ru.javawebinar.topjava.model.MealTo; ++import ru.javawebinar.topjava.to.MealTo; + + import java.time.LocalDate; + import java.time.LocalDateTime; +@@ -32,7 +33,7 @@ + return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); + } + +- public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { ++ public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, @Nullable LocalTime startTime, @Nullable LocalTime endTime) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> Util.isBetween(meal.getTime(), startTime, endTime)); + } + +Index: src/main/webapp/meals.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/meals.jsp (date 1560985251000) ++++ src/main/webapp/meals.jsp (date 1560986059501) +@@ -34,7 +34,7 @@ + + + +- ++ + + + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> +Index: src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560986059468) +@@ -1,11 +1,29 @@ + package ru.javawebinar.topjava.util; + ++import java.time.LocalDate; + 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"); + ++ // DataBase doesn't support LocalDate.MIN/MAX ++ private static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); ++ private static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); ++ ++ public static LocalDateTime adjustStartDateTime(LocalDate localDate) { ++ return adjustDateTime(localDate, MIN_DATE, LocalTime.MIN); ++ } ++ ++ public static LocalDateTime adjustEndDateTime(LocalDate localDate) { ++ return adjustDateTime(localDate, MAX_DATE, LocalTime.MAX); ++ } ++ ++ private static LocalDateTime adjustDateTime(LocalDate localDate, LocalDate defaultDate, LocalTime adjustTime) { ++ return LocalDateTime.of(localDate != null ? localDate : defaultDate, adjustTime); ++ } ++ + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } +Index: src/main/java/ru/javawebinar/topjava/util/Util.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/Util.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/util/Util.java (date 1560986059477) +@@ -1,7 +1,9 @@ + package ru.javawebinar.topjava.util; + ++import org.springframework.lang.Nullable; ++ + public class Util { +- public static > boolean isBetween(T value, T start, T end) { +- return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; ++ public static > boolean isBetween(T value, @Nullable T start, @Nullable T end) { ++ return (start == null || value.compareTo(start) >= 0) && (end == null || value.compareTo(end) <= 0); + } + } +Index: src/main/java/ru/javawebinar/topjava/model/Meal.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1560986059405) +@@ -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(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 @@ + return dateTime.toLocalTime(); + } + +- public boolean isNew() { +- return id == null; +- } +- + @Override + public String toString() { + return "Meal{" + +Index: src/main/java/ru/javawebinar/topjava/model/MealTo.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealTo.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/to/MealTo.java (date 1560986059444) +@@ -1,4 +1,4 @@ +-package ru.javawebinar.topjava.model; ++package ru.javawebinar.topjava.to; + + import java.time.LocalDateTime; + +Index: src/main/java/ru/javawebinar/topjava/service/MealService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1560986059438) +@@ -1,9 +1,54 @@ + package ru.javawebinar.topjava.service; + ++import org.springframework.beans.factory.annotation.Autowired; ++import org.springframework.lang.Nullable; ++import org.springframework.stereotype.Service; ++import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; + ++import java.time.LocalDate; ++import java.time.LocalDateTime; ++import java.util.List; ++ ++import static ru.javawebinar.topjava.util.DateTimeUtil.adjustEndDateTime; ++import static ru.javawebinar.topjava.util.DateTimeUtil.adjustStartDateTime; ++import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; ++ ++@Service + public class MealService { + +- private MealRepository repository; ++ private final MealRepository repository; ++ ++ @Autowired ++ public MealService(MealRepository repository) { ++ this.repository = repository; ++ } ++ ++ public Meal get(int id, int userId) { ++ return checkNotFoundWithId(repository.get(id, userId), id); ++ } ++ ++ public void delete(int id, int userId) { ++ checkNotFoundWithId(repository.delete(id, userId), id); ++ } + ++ public List getBetweenDates(@Nullable LocalDate startDate, @Nullable LocalDate endDate, int userId) { ++ return getBetweenDateTimes(adjustStartDateTime(startDate), adjustEndDateTime(endDate), userId); ++ } ++ ++ public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { ++ return repository.getBetween(startDateTime, endDateTime, userId); ++ } ++ ++ public List getAll(int userId) { ++ return repository.getAll(userId); ++ } ++ ++ public void update(Meal meal, int userId) { ++ checkNotFoundWithId(repository.save(meal, userId), meal.getId()); ++ } ++ ++ public Meal create(Meal meal, int userId) { ++ return repository.save(meal, userId); ++ } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/SpringMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560986059485) +@@ -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.MealTo; ++import ru.javawebinar.topjava.web.meal.MealRestController; + import ru.javawebinar.topjava.web.user.AdminRestController; + ++import java.time.LocalDate; ++import java.time.LocalTime; ++import java.time.Month; + import java.util.Arrays; ++import java.util.List; + + public class SpringMain { + public static void main(String[] args) { +@@ -15,6 +21,14 @@ + 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)); ++ System.out.println(); ++ ++ MealRestController mealController = appCtx.getBean(MealRestController.class); ++ List filteredMealsWithExcess = ++ mealController.getBetween( ++ LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), ++ LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); ++ filteredMealsWithExcess.forEach(System.out::println); + } + } + } +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560986059422) +@@ -1,6 +1,6 @@ + package ru.javawebinar.topjava.repository.inmemory; + +-import org.springframework.util.CollectionUtils; ++import org.springframework.stereotype.Repository; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; + import ru.javawebinar.topjava.util.MealsUtil; +@@ -13,18 +13,17 @@ + import java.util.List; + import java.util.Map; + import java.util.concurrent.ConcurrentHashMap; +-import java.util.concurrent.atomic.AtomicInteger; + import java.util.function.Predicate; + import java.util.stream.Collectors; + + import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.ADMIN_ID; + import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.USER_ID; + ++@Repository + public class InMemoryMealRepository implements MealRepository { + + // Map userId -> (mealId-> meal) +- private Map> usersMealsMap = new ConcurrentHashMap<>(); +- private AtomicInteger counter = new AtomicInteger(0); ++ private Map> usersMealsMap = new ConcurrentHashMap<>(); + + { + MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); +@@ -36,24 +35,19 @@ + + @Override + public Meal save(Meal meal, int userId) { +- Map meals = usersMealsMap.computeIfAbsent(userId, ConcurrentHashMap::new); +- if (meal.isNew()) { +- meal.setId(counter.incrementAndGet()); +- meals.put(meal.getId(), meal); +- return meal; +- } +- return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); ++ InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); ++ return meals.save(meal); + } + + @Override + public boolean delete(int id, int userId) { +- Map meals = usersMealsMap.get(userId); +- return meals != null && meals.remove(id) != null; ++ InMemoryBaseRepository meals = usersMealsMap.get(userId); ++ return meals != null && meals.delete(id); + } + + @Override + public Meal get(int id, int userId) { +- Map meals = usersMealsMap.get(userId); ++ InMemoryBaseRepository meals = usersMealsMap.get(userId); + return meals == null ? null : meals.get(id); + } + +@@ -68,9 +62,9 @@ + } + + private List getAllFiltered(int userId, Predicate filter) { +- Map meals = usersMealsMap.get(userId); +- return CollectionUtils.isEmpty(meals) ? Collections.emptyList() : +- meals.values().stream() ++ InMemoryBaseRepository meals = usersMealsMap.get(userId); ++ return meals == null ? Collections.emptyList() : ++ meals.getCollection().stream() + .filter(filter) + .sorted(Comparator.comparing(Meal::getDateTime).reversed()) + .collect(Collectors.toList()); +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1560985251000) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1560986059430) +@@ -6,50 +6,24 @@ + + import java.util.Comparator; + import java.util.List; +-import java.util.Map; +-import java.util.concurrent.ConcurrentHashMap; +-import java.util.concurrent.atomic.AtomicInteger; + import java.util.stream.Collectors; + + @Repository +-public class InMemoryUserRepository implements UserRepository { +- +- public static final int USER_ID = 1; +- public static final int ADMIN_ID = 2; ++public class InMemoryUserRepository extends InMemoryBaseRepository implements UserRepository { + +- private Map usersMap = new ConcurrentHashMap<>(); +- private AtomicInteger counter = new AtomicInteger(0); +- +- @Override +- public User save(User user) { +- if (user.isNew()) { +- user.setId(counter.incrementAndGet()); +- usersMap.put(user.getId(), user); +- return user; +- } +- return usersMap.computeIfPresent(user.getId(), (id, oldUser) -> user); +- } +- +- @Override +- public boolean delete(int id) { +- return usersMap.remove(id) != null; +- } +- +- @Override +- public User get(int id) { +- return usersMap.get(id); +- } ++ static final int USER_ID = 1; ++ static final int ADMIN_ID = 2; + + @Override + public List getAll() { +- return usersMap.values().stream() ++ return getCollection().stream() + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { +- return usersMap.values().stream() ++ return getCollection().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() + .orElse(null); diff --git a/patches/3_03_HW2_optional_MealServlet.patch b/patches/3_03_HW2_optional_MealServlet.patch new file mode 100644 index 000000000000..fdc26c572c09 --- /dev/null +++ b/patches/3_03_HW2_optional_MealServlet.patch @@ -0,0 +1,95 @@ +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560986576000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560986761749) +@@ -1,11 +1,10 @@ + package ru.javawebinar.topjava.web; + +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; ++import org.springframework.context.ConfigurableApplicationContext; ++import org.springframework.context.support.ClassPathXmlApplicationContext; ++import org.springframework.util.StringUtils; + import ru.javawebinar.topjava.model.Meal; +-import ru.javawebinar.topjava.repository.MealRepository; +-import ru.javawebinar.topjava.repository.inmemory.InMemoryMealRepository; +-import ru.javawebinar.topjava.util.MealsUtil; ++import ru.javawebinar.topjava.web.meal.MealRestController; + + import javax.servlet.ServletConfig; + import javax.servlet.ServletException; +@@ -18,28 +17,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 InMemoryMealRepository(); ++ 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, SecurityUtil.authUserId()); ++ if (StringUtils.isEmpty(request.getParameter("id"))) { ++ mealController.create(meal); ++ } else { ++ mealController.update(meal, getId(request)); ++ } + response.sendRedirect("meals"); + } + +@@ -50,23 +57,20 @@ + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); +- log.info("delete {}", id); +- repository.delete(id, SecurityUtil.authUserId()); ++ 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), SecurityUtil.authUserId()); ++ 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.getWithExcess(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ request.setAttribute("meals", mealController.getAll()); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } diff --git a/patches/3_04_HW2_optional_filter.patch b/patches/3_04_HW2_optional_filter.patch new file mode 100644 index 000000000000..53fe15bb19e5 --- /dev/null +++ b/patches/3_04_HW2_optional_filter.patch @@ -0,0 +1,197 @@ +Index: src/main/webapp/css/style.css +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/css/style.css (date 1560987157775) ++++ src/main/webapp/css/style.css (date 1560987157775) +@@ -0,0 +1,24 @@ ++dl { ++ background: none repeat scroll 0 0 #FAFAFA; ++ margin: 8px 0; ++ padding: 0; ++} ++ ++dt { ++ display: inline-block; ++ width: 170px; ++} ++ ++dd { ++ display: inline-block; ++ margin-left: 8px; ++ vertical-align: top; ++} ++ ++tr[data-mealExcess="false"] { ++ color: green; ++} ++ ++tr[data-mealExcess="true"] { ++ color: blue; ++} +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560987045000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560987359745) +@@ -12,10 +12,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; +@@ -67,6 +72,14 @@ + mealController.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); ++ break; ++ case "filter": ++ LocalDate startDate = parseLocalDate(request.getParameter("startDate")); ++ LocalDate endDate = parseLocalDate(request.getParameter("endDate")); ++ LocalTime startTime = parseLocalTime(request.getParameter("startTime")); ++ LocalTime endTime = parseLocalTime(request.getParameter("endTime")); ++ request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + case "all": + default: +Index: src/main/webapp/meals.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/meals.jsp (date 1560987045000) ++++ src/main/webapp/meals.jsp (date 1560987955587) +@@ -2,25 +2,37 @@ + <%@ 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 +- ++ Meal ++ + + +
+

Home

+
+

Meals

++
++ ++
++
From Date:
++
++
++
++
To Date:
++
++
++
++
From Time:
++
++
++
++
To Time:
++
++
++ ++
++
+ Add Meal +

+ +@@ -35,7 +47,7 @@ + + + +- ++ +
+ <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> +Index: src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560987045000) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1560987054244) +@@ -1,5 +1,8 @@ + package ru.javawebinar.topjava.util; + ++import org.springframework.lang.Nullable; ++import org.springframework.util.StringUtils; ++ + import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.LocalTime; +@@ -27,4 +30,12 @@ + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } ++ ++ public static LocalDate parseLocalDate(@Nullable String str) { ++ return StringUtils.isEmpty(str) ? null : LocalDate.parse(str); ++ } ++ ++ public static LocalTime parseLocalTime(@Nullable String str) { ++ return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); ++ } + } +Index: src/main/webapp/mealForm.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/mealForm.jsp (date 1560987045000) ++++ src/main/webapp/mealForm.jsp (date 1560987054296) +@@ -4,24 +4,7 @@ + + + Meal +- ++ + + +
diff --git a/patches/3_05_HW2_optional_select_user.patch b/patches/3_05_HW2_optional_select_user.patch new file mode 100644 index 000000000000..adf84cb044a5 --- /dev/null +++ b/patches/3_05_HW2_optional_select_user.patch @@ -0,0 +1,69 @@ +Index: src/main/java/ru/javawebinar/topjava/web/UserServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1550615665000) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1550615678046) +@@ -13,6 +13,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")); ++ SecurityUtil.setAuthUserId(userId); ++ response.sendRedirect("meals"); ++ } ++ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("forward to users"); +Index: src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1550615665000) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1550615678032) +@@ -4,8 +4,14 @@ + + public class SecurityUtil { + ++ private static int id = 1; ++ + public static int authUserId() { +- return 1; ++ return id; ++ } ++ ++ public static void setAuthUserId(int id) { ++ SecurityUtil.id = id; + } + + public static int authUserCaloriesPerDay() { +Index: src/main/webapp/index.html +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/index.html (date 1550615665000) ++++ src/main/webapp/index.html (date 1550615678059) +@@ -6,9 +6,13 @@ + +

Проект Java Enterprise (Topjava)

+
+- ++
++ Meals of  ++ ++ ++
+ + diff --git a/patches/3_06_bean_life_cycle.patch b/patches/3_06_bean_life_cycle.patch new file mode 100644 index 000000000000..fb6810d91c59 --- /dev/null +++ b/patches/3_06_bean_life_cycle.patch @@ -0,0 +1,48 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560988347000) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560988516000) +@@ -1,11 +1,15 @@ + package ru.javawebinar.topjava.repository.inmemory; + ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; + import org.springframework.stereotype.Repository; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; + import ru.javawebinar.topjava.util.MealsUtil; + import ru.javawebinar.topjava.util.Util; + ++import javax.annotation.PostConstruct; ++import javax.annotation.PreDestroy; + import java.time.LocalDateTime; + import java.time.Month; + import java.util.Collections; +@@ -21,6 +25,7 @@ + + @Repository + public class InMemoryMealRepository implements MealRepository { ++ private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepository.class); + + // Map userId -> (mealId-> meal) + private Map> usersMealsMap = new ConcurrentHashMap<>(); +@@ -39,6 +44,16 @@ + return meals.save(meal); + } + ++ @PostConstruct ++ public void postConstruct() { ++ log.info("+++ PostConstruct"); ++ } ++ ++ @PreDestroy ++ public void preDestroy() { ++ log.info("+++ PreDestroy"); ++ } ++ + @Override + public boolean delete(int id, int userId) { + InMemoryBaseRepository meals = usersMealsMap.get(userId); diff --git a/patches/3_07_add_junit.patch b/patches/3_07_add_junit.patch new file mode 100644 index 000000000000..cbdaa095da8d --- /dev/null +++ b/patches/3_07_add_junit.patch @@ -0,0 +1,195 @@ +Index: src/test/java/ru/javawebinar/topjava/UserTestData.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/UserTestData.java (revision 59fbea3837adfd48a684135260d89d2b3e894c6a) ++++ src/test/java/ru/javawebinar/topjava/UserTestData.java (revision 59fbea3837adfd48a684135260d89d2b3e894c6a) +@@ -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); ++} +Index: src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java (revision 59fbea3837adfd48a684135260d89d2b3e894c6a) ++++ src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java (revision 59fbea3837adfd48a684135260d89d2b3e894c6a) +@@ -0,0 +1,51 @@ ++package ru.javawebinar.topjava.web.user; ++ ++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.inmemory.InMemoryUserRepository; ++import ru.javawebinar.topjava.util.exception.NotFoundException; ++ ++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 ++ InMemoryUserRepository repository = appCtx.getBean(InMemoryUserRepository.class); ++ repository.init(); ++ } ++ ++ @Test ++ public void delete() 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 deleteNotFound() throws Exception { ++ controller.delete(10); ++ } ++} +\ No newline at end of file +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1560988558000) ++++ pom.xml (date 1560989123000) +@@ -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.22.1 ++ ++ -Dfile.encoding=UTF-8 ++ ++ + + + +@@ -75,6 +86,14 @@ + jstl + 1.2 + ++ ++ ++ ++ junit ++ junit ++ ${junit.version} ++ test ++ + + + +Index: src/main/java/ru/javawebinar/topjava/SpringMain.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1560988558000) ++++ src/test/java/ru/javawebinar/topjava/SpringMain.java (date 1560989123000) +@@ -1,0 +1,0 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java (date 1560988558000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java (date 1560989123000) +@@ -13,7 +13,7 @@ + + private static AtomicInteger counter = new AtomicInteger(0); + +- private Map entryMap = new ConcurrentHashMap<>(); ++ Map entryMap = new ConcurrentHashMap<>(); + + public T save(T entry) { + if (entry.isNew()) { +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560988558000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560989123000) +@@ -20,8 +20,8 @@ + import java.util.function.Predicate; + import java.util.stream.Collectors; + +-import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.ADMIN_ID; +-import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository.USER_ID; ++import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; ++import static ru.javawebinar.topjava.UserTestData.USER_ID; + + @Repository + public class InMemoryMealRepository implements MealRepository { +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1560988558000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1560989123000) +@@ -1,6 +1,7 @@ + package ru.javawebinar.topjava.repository.inmemory; + + import org.springframework.stereotype.Repository; ++import ru.javawebinar.topjava.UserTestData; + import ru.javawebinar.topjava.model.User; + import ru.javawebinar.topjava.repository.UserRepository; + +@@ -8,12 +9,22 @@ + import java.util.List; + import java.util.stream.Collectors; + ++import static ru.javawebinar.topjava.UserTestData.ADMIN; ++import static ru.javawebinar.topjava.UserTestData.USER; ++ ++ + @Repository + public class InMemoryUserRepository extends InMemoryBaseRepository implements UserRepository { + + static final int USER_ID = 1; + static final int ADMIN_ID = 2; + ++ public void init() { ++ entryMap.clear(); ++ entryMap.put(UserTestData.USER_ID, USER); ++ entryMap.put(UserTestData.ADMIN_ID, ADMIN); ++ } ++ + @Override + public List getAll() { + return getCollection().stream() diff --git a/patches/3_08_add_spring_test.patch b/patches/3_08_add_spring_test.patch new file mode 100644 index 000000000000..7743b9293994 --- /dev/null +++ b/patches/3_08_add_spring_test.patch @@ -0,0 +1,74 @@ +Index: src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java (date 1560989283174) ++++ src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java (date 1560989283174) +@@ -0,0 +1,46 @@ ++package ru.javawebinar.topjava.web.user; ++ ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.springframework.beans.factory.annotation.Autowired; ++import org.springframework.test.context.ContextConfiguration; ++import org.springframework.test.context.junit4.SpringRunner; ++import ru.javawebinar.topjava.UserTestData; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository; ++import ru.javawebinar.topjava.util.exception.NotFoundException; ++ ++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 InMemoryUserRepository repository; ++ ++ @Before ++ public void setUp() throws Exception { ++ repository.init(); ++ } ++ ++ @Test ++ public void delete() 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 deleteNotFound() throws Exception { ++ controller.delete(10); ++ } ++} +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1560989250000) ++++ pom.xml (date 1560989256696) +@@ -94,6 +94,12 @@ + ${junit.version} + test + ++ ++ org.springframework ++ spring-test ++ ${spring.version} ++ test ++ + + + diff --git a/patches/3_09_add_postgresql.patch b/patches/3_09_add_postgresql.patch new file mode 100644 index 000000000000..84ed32e53bfb --- /dev/null +++ b/patches/3_09_add_postgresql.patch @@ -0,0 +1,45 @@ +Index: src/main/resources/db/postgres.properties +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/postgres.properties (date 1560990583885) ++++ src/main/resources/db/postgres.properties (date 1560990583885) +@@ -0,0 +1,7 @@ ++#database.url=jdbc:postgresql://ec2-54-247-74-197.eu-west-1.compute.amazonaws.com:5432/de4fjsqhdvl7ld?ssl=true&sslmode=require&sslfactory=org.postgresql.ssl.NonValidatingFactory ++#database.username=anbxkjtzukqacj ++#database.password=da1f25b2a38784fb0d46858e5b8fc168e08c9e1e9c72faea5bbac9c0e1f9c24f ++ ++database.url=jdbc:postgresql://localhost:5432/topjava ++database.username=user ++database.password=password +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1560989326000) ++++ pom.xml (date 1560990545299) +@@ -22,6 +22,8 @@ + 1.2.3 + 1.7.25 + ++ ++ 42.2.6 + + 4.12 + +@@ -72,6 +74,13 @@ + spring-context + ${spring.version} + ++ ++ ++ ++ org.postgresql ++ postgresql ++ ${postgresql.version} ++ + + + diff --git a/patches/3_0_fix.patch b/patches/3_0_fix.patch new file mode 100644 index 000000000000..42cbe389d121 --- /dev/null +++ b/patches/3_0_fix.patch @@ -0,0 +1,276 @@ +Index: src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) +@@ -1,9 +0,0 @@ +-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 +Index: src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) +@@ -1,53 +0,0 @@ +-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; +- +-import java.util.List; +- +-import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; +-import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; +- +-@Service +-public class UserServiceImpl implements UserService { +- +- private final UserRepository repository; +- +- @Autowired +- public UserServiceImpl(UserRepository repository) { +- this.repository = 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) throws NotFoundException { +- checkNotFoundWithId(repository.save(user), user.getId()); +- } +-} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1560773291475) +@@ -4,7 +4,7 @@ + import org.slf4j.LoggerFactory; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; +-import ru.javawebinar.topjava.repository.inmemory.InMemoryMealRepositoryImpl; ++import ru.javawebinar.topjava.repository.inmemory.InMemoryMealRepository; + import ru.javawebinar.topjava.util.MealsUtil; + + import javax.servlet.ServletConfig; +@@ -25,7 +25,7 @@ + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); +- repository = new InMemoryMealRepositoryImpl(); ++ repository = new InMemoryMealRepository(); + } + + @Override +Index: src/main/webapp/meals.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/meals.jsp (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/webapp/meals.jsp (date 1560772828014) +@@ -12,7 +12,7 @@ + } + + .excess { +- color: red; ++ color: blue; + } + + +Index: src/main/java/ru/javawebinar/topjava/service/MealService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealService.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1560981738980) +@@ -1,4 +1,9 @@ + package ru.javawebinar.topjava.service; + +-public interface MealService { ++import ru.javawebinar.topjava.repository.MealRepository; ++ ++public class MealService { ++ ++ private MealRepository repository; ++ + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/service/UserService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserService.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1560981812616) +@@ -1,22 +1,47 @@ + 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; + + import java.util.List; + +-public interface UserService { ++import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; ++import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; ++ ++@Service ++public class UserService { + +- User create(User user); ++ private final UserRepository repository; + +- void delete(int id) throws NotFoundException; ++ @Autowired ++ public UserService(UserRepository repository) { ++ this.repository = repository; ++ } + +- User get(int id) throws NotFoundException; ++ public User create(User user) { ++ return repository.save(user); ++ } + +- User getByEmail(String email) throws NotFoundException; ++ public void delete(int id) throws NotFoundException { ++ checkNotFoundWithId(repository.delete(id), id); ++ } + +- void update(User user); ++ public User get(int id) throws NotFoundException { ++ return checkNotFoundWithId(repository.get(id), id); ++ } + +- List getAll(); ++ public User getByEmail(String email) throws NotFoundException { ++ return checkNotFound(repository.getByEmail(email), "email=" + email); ++ } ++ ++ public List getAll() { ++ return repository.getAll(); ++ } ++ ++ public void update(User user) throws NotFoundException { ++ checkNotFoundWithId(repository.save(user), user.getId()); ++ } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1560773291463) +@@ -9,7 +9,7 @@ + import java.util.concurrent.ConcurrentHashMap; + import java.util.concurrent.atomic.AtomicInteger; + +-public class InMemoryMealRepositoryImpl implements MealRepository { ++public class InMemoryMealRepository implements MealRepository { + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + +Index: src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1560773291468) +@@ -10,8 +10,8 @@ + import java.util.List; + + @Repository +-public class InMemoryUserRepositoryImpl implements UserRepository { +- private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); ++public class InMemoryUserRepository implements UserRepository { ++ private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepository.class); + + @Override + public boolean delete(int id) { +Index: src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (date 1560708875247) +@@ -5,6 +5,7 @@ + import java.util.List; + + public interface UserRepository { ++ // null if not found, when updated + User save(User user); + + // false if not found +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/resources/spring/spring-app.xml (date 1560982185437) +@@ -5,10 +5,10 @@ + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + + +Index: src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (revision c5f3b74ca99407c1ef5f81bc9fd524f30f96aa8e) ++++ src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1560708910502) +@@ -5,6 +5,7 @@ + import java.util.Collection; + + public interface MealRepository { ++ // null if not found, when updated + Meal save(Meal meal); + + // false if not found diff --git a/patches/3_10_db_implementation.patch b/patches/3_10_db_implementation.patch new file mode 100644 index 000000000000..92b6e7d177bd --- /dev/null +++ b/patches/3_10_db_implementation.patch @@ -0,0 +1,195 @@ +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1560990635676) ++++ src/main/resources/spring/spring-db.xml (date 1560990635676) +@@ -0,0 +1,25 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +Index: src/main/resources/db/populateDB.sql +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/populateDB.sql (date 1560990635668) ++++ src/main/resources/db/populateDB.sql (date 1560990635668) +@@ -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); +Index: src/main/resources/db/initDB.sql +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/initDB.sql (date 1560990635660) ++++ src/main/resources/db/initDB.sql (date 1560990635660) +@@ -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 WITH 100000; ++ ++CREATE TABLE users ++( ++ id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), ++ name VARCHAR NOT NULL, ++ email VARCHAR NOT NULL, ++ password VARCHAR NOT NULL, ++ registered TIMESTAMP DEFAULT now() NOT NULL, ++ enabled BOOL DEFAULT TRUE NOT NULL, ++ calories_per_day INTEGER DEFAULT 2000 NOT NULL ++); ++CREATE UNIQUE INDEX users_unique_email_idx ON users (email); ++ ++CREATE TABLE user_roles ++( ++ user_id INTEGER NOT NULL, ++ role VARCHAR, ++ CONSTRAINT user_roles_idx UNIQUE (user_id, role), ++ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ++); +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java (date 1560990635653) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java (date 1560990635653) +@@ -0,0 +1,81 @@ ++package ru.javawebinar.topjava.repository.jdbc; ++ ++import org.springframework.beans.factory.annotation.Autowired; ++import org.springframework.dao.support.DataAccessUtils; ++import org.springframework.jdbc.core.BeanPropertyRowMapper; ++import org.springframework.jdbc.core.JdbcTemplate; ++import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; ++import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; ++import org.springframework.jdbc.core.simple.SimpleJdbcInsert; ++import org.springframework.stereotype.Repository; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.UserRepository; ++ ++import java.util.List; ++ ++@Repository ++public class JdbcUserRepository implements UserRepository { ++ ++ private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); ++ ++ private final JdbcTemplate jdbcTemplate; ++ ++ private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; ++ ++ private final SimpleJdbcInsert insertUser; ++ ++ @Autowired ++ public JdbcUserRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { ++ this.insertUser = new SimpleJdbcInsert(jdbcTemplate) ++ .withTableName("users") ++ .usingGeneratedKeyColumns("id"); ++ ++ this.jdbcTemplate = jdbcTemplate; ++ this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; ++ } ++ ++ @Override ++ public User save(User user) { ++ MapSqlParameterSource map = new MapSqlParameterSource() ++ .addValue("id", user.getId()) ++ .addValue("name", user.getName()) ++ .addValue("email", user.getEmail()) ++ .addValue("password", user.getPassword()) ++ .addValue("registered", user.getRegistered()) ++ .addValue("enabled", user.isEnabled()) ++ .addValue("caloriesPerDay", user.getCaloriesPerDay()); ++ ++ if (user.isNew()) { ++ Number newKey = insertUser.executeAndReturnKey(map); ++ user.setId(newKey.intValue()); ++ } else if (namedParameterJdbcTemplate.update( ++ "UPDATE users SET name=:name, email=:email, password=:password, " + ++ "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map) == 0) { ++ return null; ++ } ++ return user; ++ } ++ ++ @Override ++ public boolean delete(int id) { ++ return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0; ++ } ++ ++ @Override ++ public User get(int id) { ++ List users = jdbcTemplate.query("SELECT * FROM users WHERE id=?", ROW_MAPPER, id); ++ return DataAccessUtils.singleResult(users); ++ } ++ ++ @Override ++ public User getByEmail(String email) { ++// return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); ++ List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); ++ return DataAccessUtils.singleResult(users); ++ } ++ ++ @Override ++ public List getAll() { ++ return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); ++ } ++} +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1560990625000) ++++ pom.xml (date 1560990635683) +@@ -74,6 +74,11 @@ + spring-context + ${spring.version} + ++ ++ org.springframework ++ spring-jdbc ++ ${spring.version} ++ + + + diff --git a/patches/3_11_test_UserService.patch b/patches/3_11_test_UserService.patch new file mode 100644 index 000000000000..6d454e259f01 --- /dev/null +++ b/patches/3_11_test_UserService.patch @@ -0,0 +1,343 @@ +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1560990827154) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1560990827154) +@@ -0,0 +1,87 @@ ++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.Date; ++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, new Date(), 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 deletedNotFound() 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 +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java (date 1560990827137) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java (date 1560990827137) +@@ -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; ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1560990786000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1560990827115) +@@ -4,6 +4,9 @@ + + protected String name; + ++ public AbstractNamedEntity() { ++ } ++ + protected AbstractNamedEntity(Integer id, String name) { + super(id); + this.name = name; +Index: src/main/java/ru/javawebinar/topjava/model/User.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/User.java (date 1560990786000) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1560990827127) +@@ -1,5 +1,8 @@ + package ru.javawebinar.topjava.model; + ++import org.springframework.util.CollectionUtils; ++ ++import java.util.Collection; + import java.util.Date; + import java.util.EnumSet; + import java.util.Set; +@@ -20,17 +23,25 @@ + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + ++ public User() { ++ } ++ ++ public User(User u) { ++ this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRegistered(), u.getRoles()); ++ } ++ + public User(Integer id, String name, String email, String password, Role role, Role... roles) { +- this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, 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() { +@@ -77,15 +88,19 @@ + return password; + } + ++ public void setRoles(Collection roles) { ++ this.roles = CollectionUtils.isEmpty(roles) ? EnumSet.noneOf(Role.class) : EnumSet.copyOf(roles); ++ } ++ + @Override + public String toString() { +- return "User (" + ++ return "User{" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + +- ')'; ++ '}'; + } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1560990786000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1560990827104) +@@ -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,21 @@ + public String toString() { + return getClass().getSimpleName() + ":" + id; + } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ AbstractBaseEntity that = (AbstractBaseEntity) o; ++ return id != null && id.equals(that.id); ++ } ++ ++ @Override ++ public int hashCode() { ++ return id == null ? 0 : id; ++ } + } +\ No newline at end of file +Index: src/test/java/ru/javawebinar/topjava/UserTestData.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1560990786000) ++++ src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1560990827161) +@@ -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); ++ } + } +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1560990786000) ++++ pom.xml (date 1560990860400) +@@ -114,6 +114,12 @@ + ${spring.version} + test + ++ ++ org.assertj ++ assertj-core ++ 3.12.2 ++ test ++ + + + +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (date 1560990786000) ++++ src/main/resources/spring/spring-app.xml (date 1560990827145) +@@ -15,7 +15,7 @@ + + + +- ++ + + + diff --git a/patches/3_12_test_logging.patch b/patches/3_12_test_logging.patch new file mode 100644 index 000000000000..0b1c0660feca --- /dev/null +++ b/patches/3_12_test_logging.patch @@ -0,0 +1,104 @@ +Index: src/test/resources/logback-test.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/resources/logback-test.xml (date 1560991085369) ++++ src/test/resources/logback-test.xml (date 1560991085369) +@@ -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 +Index: src/main/resources/logback.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/logback.xml (date 1560991063000) ++++ src/main/resources/logback.xml (date 1560991085357) +@@ -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 + + + +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1560991063000) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1560991085364) +@@ -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; +@@ -26,6 +27,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; + +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1560991063000) ++++ pom.xml (date 1560991085373) +@@ -61,6 +61,13 @@ + compile + + ++ ++ org.slf4j ++ jul-to-slf4j ++ ${slf4j.version} ++ runtime ++ ++ + + ch.qos.logback + logback-classic diff --git a/patches/3_13_fix_servlet.patch b/patches/3_13_fix_servlet.patch new file mode 100644 index 000000000000..5c43d373a389 --- /dev/null +++ b/patches/3_13_fix_servlet.patch @@ -0,0 +1,55 @@ +Index: src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1550661241000) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1550661251697) +@@ -1,10 +1,12 @@ + package ru.javawebinar.topjava.web; + ++import ru.javawebinar.topjava.model.AbstractBaseEntity; ++ + import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + + public class SecurityUtil { + +- private static int id = 1; ++ private static int id = AbstractBaseEntity.START_SEQ; + + public static int authUserId() { + return id; +Index: src/main/webapp/index.html +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/index.html (date 1550661241000) ++++ src/main/webapp/index.html (date 1550661251710) +@@ -9,8 +9,8 @@ +
+ Meals of  + + +
+Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1550661241000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1550661251688) +@@ -29,7 +29,7 @@ + @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/patches/4_0_fix.patch b/patches/4_0_fix.patch new file mode 100644 index 000000000000..e0e07c742fc1 --- /dev/null +++ b/patches/4_0_fix.patch @@ -0,0 +1,46 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java (revision 3c3223bd49e9b29f384bef068f3d5218659edede) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java (date 1561384866975) +@@ -8,7 +8,7 @@ + import java.util.List; + + @Repository +-public class JdbcMealRepositoryImpl implements MealRepository { ++public class JdbcMealRepository implements MealRepository { + + @Override + public Meal save(Meal meal, int userId) { +Index: src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (revision 3c3223bd49e9b29f384bef068f3d5218659edede) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1561384892353) +@@ -16,9 +16,6 @@ + @Repository + public class InMemoryUserRepository extends InMemoryBaseRepository implements UserRepository { + +- static final int USER_ID = 1; +- static final int ADMIN_ID = 2; +- + public void init() { + entryMap.clear(); + entryMap.put(UserTestData.USER_ID, USER); +Index: src/main/java/ru/javawebinar/topjava/service/MealService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealService.java (revision 3c3223bd49e9b29f384bef068f3d5218659edede) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1561384866997) +@@ -36,7 +36,7 @@ + return getBetweenDateTimes(adjustStartDateTime(startDate), adjustEndDateTime(endDate), userId); + } + +- public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { ++ private List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return repository.getBetween(startDateTime, endDateTime, userId); + } + diff --git a/patches/4_1_HW3.patch b/patches/4_1_HW3.patch new file mode 100644 index 000000000000..8e236bf5f8f6 --- /dev/null +++ b/patches/4_1_HW3.patch @@ -0,0 +1,241 @@ +Index: src/main/resources/db/initDB.sql +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/initDB.sql (date 1561376939000) ++++ src/main/resources/db/initDB.sql (date 1561376976761) +@@ -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,15 @@ + 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 +Index: src/main/resources/db/populateDB.sql +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/populateDB.sql (date 1561376939000) ++++ src/main/resources/db/populateDB.sql (date 1561376976769) +@@ -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 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); +Index: src/main/java/ru/javawebinar/topjava/model/Meal.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1561376939000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1561376976755) +@@ -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 @@ + 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{" + +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java (date 1561376939000) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java (date 1561377092655) +@@ -1,5 +1,13 @@ + 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; +@@ -10,28 +18,70 @@ + @Repository + public class JdbcMealRepository 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 JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { ++ this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) ++ .withTableName("meals") ++ .usingGeneratedKeyColumns("id"); ++ ++ this.jdbcTemplate = jdbcTemplate; ++ this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; ++ } ++ + @Override + public Meal save(Meal meal, int userId) { +- 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); + } + } +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java (date 1561376939000) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java (date 1561376976740) +@@ -4,7 +4,7 @@ + import org.springframework.dao.support.DataAccessUtils; + import org.springframework.jdbc.core.BeanPropertyRowMapper; + import org.springframework.jdbc.core.JdbcTemplate; +-import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; ++import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; + import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + import org.springframework.jdbc.core.simple.SimpleJdbcInsert; + import org.springframework.stereotype.Repository; +@@ -36,21 +36,14 @@ + + @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/patches/4_2_HW3_optional.patch b/patches/4_2_HW3_optional.patch new file mode 100644 index 000000000000..7a5398487f9c --- /dev/null +++ b/patches/4_2_HW3_optional.patch @@ -0,0 +1,243 @@ +Index: src/test/java/ru/javawebinar/topjava/MealTestData.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/MealTestData.java (date 1561381425440) ++++ src/test/java/ru/javawebinar/topjava/MealTestData.java (date 1561381425440) +@@ -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); ++ } ++} +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1561382754942) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1561382754942) +@@ -0,0 +1,100 @@ ++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 delete() throws Exception { ++ service.delete(MEAL1_ID, USER_ID); ++ assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void deleteNotFound() throws Exception { ++ service.delete(1, USER_ID); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void deleteNotOwn() throws Exception { ++ service.delete(MEAL1_ID, ADMIN_ID); ++ } ++ ++ @Test ++ public void create() throws Exception { ++ Meal newMeal = getCreated(); ++ Meal created = service.create(newMeal, USER_ID); ++ newMeal.setId(created.getId()); ++ assertMatch(newMeal, created); ++ assertMatch(service.getAll(USER_ID), newMeal, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); ++ } ++ ++ @Test ++ public void get() throws Exception { ++ Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); ++ assertMatch(actual, ADMIN_MEAL1); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void getNotFound() throws Exception { ++ service.get(1, USER_ID); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void getNotOwn() throws Exception { ++ service.get(MEAL1_ID, ADMIN_ID); ++ } ++ ++ @Test ++ public void update() throws Exception { ++ Meal updated = getUpdated(); ++ service.update(updated, USER_ID); ++ assertMatch(service.get(MEAL1_ID, USER_ID), updated); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void updateNotFound() throws Exception { ++ service.update(MEAL1, ADMIN_ID); ++ } ++ ++ @Test ++ public void getAll() throws Exception { ++ assertMatch(service.getAll(USER_ID), MEALS); ++ } ++ ++ @Test ++ 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); ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1561381544000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1561381425413) +@@ -5,10 +5,7 @@ + import ru.javawebinar.topjava.to.MealTo; + + import java.time.LocalDate; +-import java.time.LocalDateTime; + import java.time.LocalTime; +-import java.time.Month; +-import java.util.Arrays; + import java.util.Collection; + import java.util.List; + import java.util.Map; +@@ -18,14 +15,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; + +Index: src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1561381544000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1561381425455) +@@ -5,13 +5,11 @@ + import org.springframework.stereotype.Repository; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; +-import ru.javawebinar.topjava.util.MealsUtil; + import ru.javawebinar.topjava.util.Util; + + import javax.annotation.PostConstruct; + import javax.annotation.PreDestroy; + import java.time.LocalDateTime; +-import java.time.Month; + import java.util.Collections; + import java.util.Comparator; + import java.util.List; +@@ -20,9 +18,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 InMemoryMealRepository implements MealRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepository.class); +@@ -30,14 +25,6 @@ + // Map userId -> (mealId-> meal) + private Map> usersMealsMap = new ConcurrentHashMap<>(); + +- { +- MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); +- +- save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); +- save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); +- } +- +- + @Override + public Meal save(Meal meal, int userId) { + InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); diff --git a/patches/4_3_HW3_fix_logging.patch b/patches/4_3_HW3_fix_logging.patch new file mode 100644 index 000000000000..eacce73828d3 --- /dev/null +++ b/patches/4_3_HW3_fix_logging.patch @@ -0,0 +1,61 @@ +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1561587066000) ++++ src/main/resources/spring/spring-db.xml (date 1561587120012) +@@ -6,6 +6,15 @@ + + + ++ ++ ++ ++ ++ + + +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1561587066000) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1561587092961) +@@ -27,12 +27,6 @@ + @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) + public class UserServiceTest { + +- static { +- // Only for postgres driver logging +- // It uses java.util.logging and logged via jul-to-slf4j bridge +- SLF4JBridgeHandler.install(); +- } +- + @Autowired + private UserService service; + +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1561587066000) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1561587092954) +@@ -26,10 +26,6 @@ + @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) + public class MealServiceTest { + +- static { +- SLF4JBridgeHandler.install(); +- } +- + @Autowired + private MealService service; + diff --git a/patches/4_4_improve_code.patch b/patches/4_4_improve_code.patch new file mode 100644 index 000000000000..044fcd642bf2 --- /dev/null +++ b/patches/4_4_improve_code.patch @@ -0,0 +1,289 @@ +Index: .codacy.yml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- .codacy.yml (date 1561590920048) ++++ .codacy.yml (date 1561590920048) +@@ -0,0 +1,4 @@ ++--- ++exclude_paths: ++ - 'src/main/webapp/**' ++ - 'README.md' +\ No newline at end of file +Index: .travis.yml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- .travis.yml (date 1561590912921) ++++ .travis.yml (date 1561590912921) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1561590909000) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1561590912902) +@@ -8,6 +8,9 @@ + + private static int id = AbstractBaseEntity.START_SEQ; + ++ private SecurityUtil() { ++ } ++ + public static int authUserId() { + return id; + } +Index: src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java (date 1561590909000) ++++ src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java (date 1561590912896) +@@ -6,6 +6,9 @@ + + public class ValidationUtil { + ++ private ValidationUtil() { ++ } ++ + public static T checkNotFoundWithId(T object, int id) { + return checkNotFound(object, "id=" + id); + } +Index: src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1561590909000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1561590912885) +@@ -18,6 +18,9 @@ + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + ++ private MealsUtil() { ++ } ++ + public static List getWithExcess(Collection meals, int caloriesPerDay) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); + } +Index: src/main/java/ru/javawebinar/topjava/util/Util.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/Util.java (date 1561590909000) ++++ src/main/java/ru/javawebinar/topjava/util/Util.java (date 1561590912891) +@@ -3,6 +3,9 @@ + import org.springframework.lang.Nullable; + + public class Util { ++ private Util() { ++ } ++ + public static > boolean isBetween(T value, @Nullable T start, @Nullable T end) { + return (start == null || value.compareTo(start) >= 0) && (end == null || value.compareTo(end) <= 0); + } +Index: src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1561590909000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java (date 1561590912915) +@@ -7,6 +7,7 @@ + + import java.util.Comparator; + import java.util.List; ++import java.util.Objects; + import java.util.stream.Collectors; + + import static ru.javawebinar.topjava.UserTestData.ADMIN; +@@ -31,6 +32,7 @@ + + @Override + public User getByEmail(String email) { ++ Objects.requireNonNull(email, "email must not be null"); + return getCollection().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() +Index: src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1561590909000) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1561590912881) +@@ -15,6 +15,9 @@ + private static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); + private static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + ++ private DateTimeUtil() { ++ } ++ + public static LocalDateTime adjustStartDateTime(LocalDate localDate) { + return adjustDateTime(localDate, MIN_DATE, LocalTime.MIN); + } +Index: src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java (date 1561590909000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java (date 1561590912907) +@@ -5,6 +5,7 @@ + + import java.util.Collection; + import java.util.Map; ++import java.util.Objects; + import java.util.concurrent.ConcurrentHashMap; + import java.util.concurrent.atomic.AtomicInteger; + +@@ -16,6 +17,7 @@ + Map entryMap = new ConcurrentHashMap<>(); + + public T save(T entry) { ++ Objects.requireNonNull(entry, "Entry must not be null"); + if (entry.isNew()) { + entry.setId(counter.incrementAndGet()); + entryMap.put(entry.getId(), entry); +Index: src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1561590909000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1561590912911) +@@ -10,10 +10,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.function.Predicate; + import java.util.stream.Collectors; +@@ -27,6 +24,7 @@ + + @Override + public Meal save(Meal meal, int userId) { ++ Objects.requireNonNull(meal, "meal must not be null"); + InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); + return meals.save(meal); + } +@@ -60,6 +58,8 @@ + + @Override + public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { ++ Objects.requireNonNull(startDateTime, "startDateTime must not be null"); ++ Objects.requireNonNull(endDateTime, "endDateTime must not be null"); + return getAllFiltered(userId, meal -> Util.isBetween(meal.getDateTime(), startDateTime, endDateTime)); + } + +Index: src/main/java/ru/javawebinar/topjava/service/UserService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1561590909000) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1561590912876) +@@ -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; +@@ -22,6 +23,7 @@ + } + + public User create(User user) { ++ Assert.notNull(user, "user must not be null"); + return repository.save(user); + } + +@@ -34,6 +36,7 @@ + } + + public User getByEmail(String email) throws NotFoundException { ++ Assert.notNull(email, "email must not be null"); + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + +@@ -42,6 +45,7 @@ + } + + public void update(User user) throws NotFoundException { ++ Assert.notNull(user, "user must not be null"); + checkNotFoundWithId(repository.save(user), user.getId()); + } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/service/MealService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1561590909000) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1561590912868) +@@ -3,6 +3,7 @@ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.lang.Nullable; + import org.springframework.stereotype.Service; ++import org.springframework.util.Assert; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; + +@@ -37,6 +38,8 @@ + } + + private 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); + } + +@@ -45,10 +48,12 @@ + } + + public void update(Meal meal, int userId) { ++ Assert.notNull(meal, "meal must not be null"); + checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + + 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/patches/4_5_init_and_populate_db.patch b/patches/4_5_init_and_populate_db.patch new file mode 100644 index 000000000000..428c4e5207f9 --- /dev/null +++ b/patches/4_5_init_and_populate_db.patch @@ -0,0 +1,40 @@ +Index: src/main/resources/db/postgres.properties +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/postgres.properties (date 1561385000000) ++++ src/main/resources/db/postgres.properties (date 1561385008108) +@@ -5,3 +5,4 @@ + database.url=jdbc:postgresql://localhost:5432/topjava + database.username=user + database.password=password ++database.init=true +\ No newline at end of file +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1561385000000) ++++ src/main/resources/spring/spring-db.xml (date 1561385008120) +@@ -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"> + + + ++ ++ ++ ++ ++ + + diff --git a/patches/4_6_create_inmemory_test_ctx.patch b/patches/4_6_create_inmemory_test_ctx.patch new file mode 100644 index 000000000000..777f1aa20c90 --- /dev/null +++ b/patches/4_6_create_inmemory_test_ctx.patch @@ -0,0 +1,96 @@ +Index: src/test/resources/spring/inmemory.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/resources/spring/inmemory.xml (date 1561387304916) ++++ src/test/resources/spring/inmemory.xml (date 1561387304916) +@@ -0,0 +1,7 @@ ++ ++ ++ ++ +\ No newline at end of file +Index: src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java (date 1561387244000) ++++ src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java (date 1561387304900) +@@ -19,7 +19,7 @@ + + @BeforeClass + public static void beforeClass() { +- appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); ++ appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml"); + System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); + controller = appCtx.getBean(AdminRestController.class); + } +Index: src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java (date 1561387244000) ++++ src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java (date 1561387304891) +@@ -16,7 +16,7 @@ + + import static ru.javawebinar.topjava.UserTestData.ADMIN; + +-@ContextConfiguration("classpath:spring/spring-app.xml") ++@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/inmemory.xml"}) + @RunWith(SpringRunner.class) + public class InMemoryAdminRestControllerSpringTest { + +Index: src/test/java/ru/javawebinar/topjava/SpringMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/SpringMain.java (date 1561387244000) ++++ src/test/java/ru/javawebinar/topjava/SpringMain.java (date 1561387304909) +@@ -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/inmemory.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (date 1561387244000) ++++ src/main/resources/spring/spring-app.xml (date 1561387304873) +@@ -15,8 +15,6 @@ + + + +- +- + + + +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1561387244000) ++++ src/main/resources/spring/spring-db.xml (date 1561387304884) +@@ -8,6 +8,8 @@ + + + ++ ++ + + + diff --git a/patches/4_7_add_jpa.patch b/patches/4_7_add_jpa.patch new file mode 100644 index 000000000000..c37891e58ea7 --- /dev/null +++ b/patches/4_7_add_jpa.patch @@ -0,0 +1,409 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java (date 1561591023593) ++++ src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java (date 1561591023593) +@@ -0,0 +1,37 @@ ++package ru.javawebinar.topjava.repository.jpa; ++ ++import org.springframework.stereotype.Repository; ++import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.repository.MealRepository; ++ ++import java.time.LocalDateTime; ++import java.util.List; ++ ++@Repository ++public class JpaMealRepository implements MealRepository { ++ ++ @Override ++ public Meal save(Meal meal, int userId) { ++ return null; ++ } ++ ++ @Override ++ public boolean delete(int id, int userId) { ++ return false; ++ } ++ ++ @Override ++ public Meal get(int id, int userId) { ++ return null; ++ } ++ ++ @Override ++ public List getAll(int userId) { ++ return null; ++ } ++ ++ @Override ++ public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { ++ return null; ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java (date 1561591329107) ++++ src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java (date 1561591329107) +@@ -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 JpaUserRepository 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; ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/model/User.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/User.java (date 1561591018000) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1561591416280) +@@ -1,7 +1,13 @@ + package ru.javawebinar.topjava.model; + ++import org.hibernate.validator.constraints.Range; + import org.springframework.util.CollectionUtils; + ++import javax.persistence.*; ++import javax.validation.constraints.Email; ++import javax.validation.constraints.NotBlank; ++import javax.validation.constraints.NotNull; ++import javax.validation.constraints.Size; + import java.util.Collection; + import java.util.Date; + import java.util.EnumSet; +@@ -9,18 +15,36 @@ + + 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 = 100) + private String password; + ++ @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") + private boolean enabled = true; + ++ @Column(name = "registered", nullable = false, columnDefinition = "timestamp default now()") ++ @NotNull + private Date registered = new Date(); + ++ @Enumerated(EnumType.STRING) ++ @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) ++ @Column(name = "role") ++ @ElementCollection(fetch = FetchType.EAGER) + private Set roles; + ++ @Column(name = "calories_per_day", nullable = false, columnDefinition = "int default 2000") ++ @Range(min = 10, max = 10000) + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public User() { +Index: src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1561591018000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1561591023569) +@@ -1,10 +1,20 @@ + package ru.javawebinar.topjava.model; + ++import javax.persistence.Column; ++import javax.persistence.MappedSuperclass; ++import javax.validation.constraints.NotBlank; ++import javax.validation.constraints.Size; ++ ++ ++@MappedSuperclass + public abstract class AbstractNamedEntity extends AbstractBaseEntity { + ++ @NotBlank ++ @Size(min = 2, max = 100) ++ @Column(name = "name", nullable = false) + protected String name; + +- public AbstractNamedEntity() { ++ protected AbstractNamedEntity() { + } + + protected AbstractNamedEntity(Integer id, String name) { +Index: src/main/resources/db/postgres.properties +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/postgres.properties (date 1561591018000) ++++ src/main/resources/db/postgres.properties (date 1561591023602) +@@ -5,4 +5,8 @@ + database.url=jdbc:postgresql://localhost:5432/topjava + database.username=user + database.password=password +-database.init=true +\ No newline at end of file ++ ++database.init=true ++jpa.showSql=true ++hibernate.format_sql=true ++hibernate.use_sql_comments=true +\ No newline at end of file +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1561591018000) ++++ src/main/resources/spring/spring-db.xml (date 1561591329108) +@@ -1,5 +1,6 @@ + + + +- ++ + + + +@@ -33,11 +34,34 @@ + + + +- +- +- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1561591018000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1561591023559) +@@ -1,11 +1,19 @@ + 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) { +Index: src/main/java/ru/javawebinar/topjava/model/Meal.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1561591018000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1561591023577) +@@ -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 @@ + + private int calories; + ++ @ManyToOne(fetch = FetchType.LAZY) ++ private User user; ++ + public Meal() { + } + +@@ -57,6 +62,14 @@ + this.calories = calories; + } + ++ public User getUser() { ++ return user; ++ } ++ ++ public void setUser(User user) { ++ this.user = user; ++ } ++ + @Override + public String toString() { + return "Meal{" + +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1561591018000) ++++ pom.xml (date 1561591023616) +@@ -26,6 +26,11 @@ + 42.2.6 + + 4.12 ++ ++ ++ 5.4.3.Final ++ 6.0.17.Final ++ 3.0.1-b11 + + + +@@ -83,7 +88,7 @@ +
+ + org.springframework +- spring-jdbc ++ spring-orm + ${spring.version} + + +@@ -93,6 +98,26 @@ + postgresql + ${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/patches/4_8_add_named_query_and_transaction.patch b/patches/4_8_add_named_query_and_transaction.patch new file mode 100644 index 000000000000..0649475c0b8e --- /dev/null +++ b/patches/4_8_add_named_query_and_transaction.patch @@ -0,0 +1,128 @@ +Index: src/main/java/ru/javawebinar/topjava/model/User.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/User.java (date 1561403251000) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1561403424011) +@@ -15,10 +15,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 +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1561403251000) ++++ src/main/resources/spring/spring-db.xml (date 1561403424020) +@@ -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 @@ + + + ++ ++ ++ ++ ++ + ++ + ++ + + + +- ++ + + + +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1561591500000) ++++ pom.xml (date 1561591522166) +@@ -98,6 +98,12 @@ + postgresql + ${postgresql.version} + ++ ++ org.hsqldb ++ hsqldb ++ 2.3.4 ++ ++ + + + diff --git a/patches/5_0_fix.patch b/patches/5_0_fix.patch new file mode 100644 index 000000000000..76a524ad70a5 --- /dev/null +++ b/patches/5_0_fix.patch @@ -0,0 +1,190 @@ +Index: src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java (revision 789e353867bc548046d013e8690465d8763d4098) ++++ src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java (date 1562187220612) +@@ -40,8 +40,8 @@ + public void delete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); +- Assert.assertEquals(users.size(), 1); +- Assert.assertEquals(users.iterator().next(), ADMIN); ++ Assert.assertEquals(1, users.size()); ++ Assert.assertEquals(ADMIN, users.iterator().next()); + } + + @Test(expected = NotFoundException.class) +Index: src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java (revision 789e353867bc548046d013e8690465d8763d4098) ++++ src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java (date 1562187220606) +@@ -35,8 +35,8 @@ + public void delete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); +- Assert.assertEquals(users.size(), 1); +- Assert.assertEquals(users.iterator().next(), ADMIN); ++ Assert.assertEquals(1, users.size()); ++ Assert.assertEquals(ADMIN, users.iterator().next()); + } + + @Test(expected = NotFoundException.class) +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (revision 789e353867bc548046d013e8690465d8763d4098) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562191629792) +@@ -2,7 +2,6 @@ + + import org.junit.Test; + import org.junit.runner.RunWith; +-import org.slf4j.bridge.SLF4JBridgeHandler; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.dao.DataAccessException; + import org.springframework.test.context.ContextConfiguration; +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (revision 789e353867bc548046d013e8690465d8763d4098) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562191629777) +@@ -2,7 +2,6 @@ + + import org.junit.Test; + import org.junit.runner.RunWith; +-import org.slf4j.bridge.SLF4JBridgeHandler; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.test.context.ContextConfiguration; + import org.springframework.test.context.jdbc.Sql; +Index: src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (revision 789e353867bc548046d013e8690465d8763d4098) ++++ src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1562187029470) +@@ -15,24 +15,13 @@ + protected final Logger log = LoggerFactory.getLogger(getClass()); + + @Autowired +- private UserService service; +- +- public List getAll() { +- log.info("getAll"); +- return service.getAll(); +- } ++ protected UserService service; + + 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); +@@ -43,9 +32,4 @@ + 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 +Index: .travis.yml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- .travis.yml (revision 789e353867bc548046d013e8690465d8763d4098) ++++ .travis.yml (date 1562191592852) +@@ -1,6 +1,6 @@ + # https://docs.travis-ci.com/user/languages/java/ + language: java +-jdk: oraclejdk8 ++jdk: openjdk8 + + #https://dzone.com/articles/travis-ci-tutorial-java-projects + cache: +Index: src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (revision 789e353867bc548046d013e8690465d8763d4098) ++++ src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1562187029464) +@@ -5,12 +5,14 @@ + + import java.util.List; + ++import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; ++ + @Controller + public class AdminRestController extends AbstractUserController { + +- @Override + public List getAll() { +- return super.getAll(); ++ log.info("getAll"); ++ return service.getAll(); + } + + @Override +@@ -18,9 +20,10 @@ + return super.get(id); + } + +- @Override + public User create(User user) { +- return super.create(user); ++ log.info("create {}", user); ++ checkNew(user); ++ return service.create(user); + } + + @Override +@@ -33,8 +36,8 @@ + super.update(user, id); + } + +- @Override + public User getByMail(String email) { +- return super.getByMail(email); ++ log.info("getByEmail {}", email); ++ return service.getByEmail(email); + } + } +\ No newline at end of file +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (revision 789e353867bc548046d013e8690465d8763d4098) ++++ src/main/resources/spring/spring-app.xml (date 1562187220600) +@@ -7,7 +7,7 @@ + diff --git a/patches/5_0_jdk_11.patch b/patches/5_0_jdk_11.patch new file mode 100644 index 000000000000..5b42ae02d362 --- /dev/null +++ b/patches/5_0_jdk_11.patch @@ -0,0 +1,143 @@ +Index: src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1562188282000) ++++ src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java (date 1562188286886) +@@ -25,7 +25,7 @@ + @Override + public Meal save(Meal meal, int userId) { + Objects.requireNonNull(meal, "meal must not be null"); +- InMemoryBaseRepository meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); ++ var meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); + return meals.save(meal); + } + +@@ -41,13 +41,13 @@ + + @Override + public boolean delete(int id, int userId) { +- InMemoryBaseRepository meals = usersMealsMap.get(userId); ++ var meals = usersMealsMap.get(userId); + return meals != null && meals.delete(id); + } + + @Override + public Meal get(int id, int userId) { +- InMemoryBaseRepository meals = usersMealsMap.get(userId); ++ var meals = usersMealsMap.get(userId); + return meals == null ? null : meals.get(id); + } + +@@ -64,7 +64,7 @@ + } + + private List getAllFiltered(int userId, Predicate filter) { +- InMemoryBaseRepository meals = usersMealsMap.get(userId); ++ var meals = usersMealsMap.get(userId); + return meals == null ? Collections.emptyList() : + meals.getCollection().stream() + .filter(filter) +Index: .travis.yml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- .travis.yml (date 1562188282000) ++++ .travis.yml (date 1562189130130) +@@ -1,6 +1,6 @@ + # https://docs.travis-ci.com/user/languages/java/ + language: java +-jdk: openjdk8 ++jdk: openjdk11 + + #https://dzone.com/articles/travis-ci-tutorial-java-projects + cache: +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1562188282000) ++++ pom.xml (date 1562189130075) +@@ -12,7 +12,7 @@ + http://topjava.herokuapp.com/ + + +- 1.8 ++ 11 + UTF-8 + UTF-8 + +@@ -79,6 +79,12 @@ + ${logback.version} + runtime + ++ ++ ++ javax.annotation ++ javax.annotation-api ++ 1.3.2 ++ + + + +Index: src/test/java/ru/javawebinar/topjava/MealTestData.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/MealTestData.java (date 1562188282000) ++++ src/test/java/ru/javawebinar/topjava/MealTestData.java (date 1562188286893) +@@ -3,7 +3,6 @@ + import ru.javawebinar.topjava.model.Meal; + + import java.time.Month; +-import java.util.Arrays; + import java.util.List; + + import static java.time.LocalDateTime.of; +@@ -23,7 +22,7 @@ + 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 final List MEALS = List.of(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + + public static Meal getCreated() { + return new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Созданный ужин", 300); +@@ -38,7 +37,7 @@ + } + + public static void assertMatch(Iterable actual, Meal... expected) { +- assertMatch(actual, Arrays.asList(expected)); ++ assertMatch(actual, List.of(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { +Index: src/test/java/ru/javawebinar/topjava/UserTestData.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1562188282000) ++++ src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1562188286900) +@@ -3,7 +3,7 @@ + import ru.javawebinar.topjava.model.Role; + import ru.javawebinar.topjava.model.User; + +-import java.util.Arrays; ++import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; +@@ -20,7 +20,7 @@ + } + + public static void assertMatch(Iterable actual, User... expected) { +- assertMatch(actual, Arrays.asList(expected)); ++ assertMatch(actual, List.of(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { diff --git a/patches/5_1_HW4.patch b/patches/5_1_HW4.patch new file mode 100644 index 000000000000..34283cdea95b --- /dev/null +++ b/patches/5_1_HW4.patch @@ -0,0 +1,160 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java (date 1562189671000) ++++ src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java (date 1562189750826) +@@ -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 JpaMealRepository 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 +Index: src/main/java/ru/javawebinar/topjava/model/Meal.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1562189671000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1562189686538) +@@ -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() { +Index: src/test/java/ru/javawebinar/topjava/MealTestData.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/MealTestData.java (date 1562189671000) ++++ src/test/java/ru/javawebinar/topjava/MealTestData.java (date 1562189686546) +@@ -33,7 +33,7 @@ + } + + 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) { +@@ -41,6 +41,6 @@ + } + + public static void assertMatch(Iterable actual, Iterable expected) { +- assertThat(actual).usingFieldByFieldElementComparator().isEqualTo(expected); ++ assertThat(actual).usingElementComparatorIgnoringFields("user").isEqualTo(expected); + } + } diff --git a/patches/5_2_fix_hibernate_issue.patch b/patches/5_2_fix_hibernate_issue.patch new file mode 100644 index 000000000000..582ec66d88e8 --- /dev/null +++ b/patches/5_2_fix_hibernate_issue.patch @@ -0,0 +1,49 @@ +Index: src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1562189816000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1562189830020) +@@ -1,5 +1,7 @@ + package ru.javawebinar.topjava.model; + ++import org.hibernate.Hibernate; ++ + import javax.persistence.*; + + @MappedSuperclass +@@ -11,6 +13,9 @@ + @Id + @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") ++ ++// See https://hibernate.atlassian.net/browse/HHH-3718 and https://hibernate.atlassian.net/browse/HHH-12034 ++// Proxy initialization when accessing its identifier managed now by JPA_PROXY_COMPLIANCE setting + protected Integer id; + + protected AbstractBaseEntity() { +@@ -42,7 +47,7 @@ + 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; +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1562189816000) ++++ src/main/resources/spring/spring-db.xml (date 1562189858065) +@@ -48,6 +48,7 @@ + + + ++ + + + diff --git a/patches/5_3_HW4_optional.patch b/patches/5_3_HW4_optional.patch new file mode 100644 index 000000000000..f6552ce13bd0 --- /dev/null +++ b/patches/5_3_HW4_optional.patch @@ -0,0 +1,121 @@ +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562191744000) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562191785519) +@@ -1,18 +1,26 @@ + package ru.javawebinar.topjava.service; + ++import org.junit.AfterClass; ++import org.junit.Rule; + import org.junit.Test; ++import org.junit.rules.ExpectedException; ++import org.junit.rules.Stopwatch; ++import org.junit.runner.Description; + import org.junit.runner.RunWith; ++import org.slf4j.Logger; + import org.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; +@@ -21,9 +29,35 @@ + "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"); ++ } ++ }; ++ ++ @AfterClass ++ public static void printResult() { ++ log.info("\n---------------------------------" + ++ "\nTest Duration, ms" + ++ "\n---------------------------------" + ++ results + ++ "\n---------------------------------"); ++ } + + @Autowired + private MealService service; +@@ -34,13 +68,15 @@ + assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); + } + +- @Test(expected = NotFoundException.class) ++ @Test + public void deleteNotFound() throws Exception { ++ thrown.expect(NotFoundException.class); + service.delete(1, USER_ID); + } + +- @Test(expected = NotFoundException.class) ++ @Test + public void deleteNotOwn() throws Exception { ++ thrown.expect(NotFoundException.class); + service.delete(MEAL1_ID, ADMIN_ID); + } + +@@ -59,13 +95,15 @@ + assertMatch(actual, ADMIN_MEAL1); + } + +- @Test(expected = NotFoundException.class) ++ @Test + public void getNotFound() throws Exception { +- service.get(1, USER_ID); ++ thrown.expect(NotFoundException.class); ++ service.get(MEAL1_ID, ADMIN_ID); + } + +- @Test(expected = NotFoundException.class) ++ @Test + public void getNotOwn() throws Exception { ++ thrown.expect(NotFoundException.class); + service.get(MEAL1_ID, ADMIN_ID); + } + +@@ -76,8 +114,10 @@ + assertMatch(service.get(MEAL1_ID, USER_ID), updated); + } + +- @Test(expected = NotFoundException.class) ++ @Test + public void updateNotFound() throws Exception { ++ thrown.expect(NotFoundException.class); ++ thrown.expectMessage("Not found entity with id=" + MEAL1_ID); + service.update(MEAL1, ADMIN_ID); + } + diff --git a/patches/5_4_log_colored.patch b/patches/5_4_log_colored.patch new file mode 100644 index 000000000000..2d219921d36e --- /dev/null +++ b/patches/5_4_log_colored.patch @@ -0,0 +1,66 @@ +Index: src/main/resources/logback.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/logback.xml (date 1562190388000) ++++ src/main/resources/logback.xml (date 1562190538282) +@@ -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 + + + +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562190388000) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562190538298) +@@ -32,7 +32,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(); + +Index: src/test/resources/logback-test.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/resources/logback-test.xml (date 1562190388000) ++++ src/test/resources/logback-test.xml (date 1562190538307) +@@ -7,12 +7,23 @@ + + + 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) + + ++ ++ ++ + +- +- ++ ++ ++ + + + diff --git a/patches/5_5_profiles_connection_pool.patch b/patches/5_5_profiles_connection_pool.patch new file mode 100644 index 000000000000..64e562bd68ad --- /dev/null +++ b/patches/5_5_profiles_connection_pool.patch @@ -0,0 +1,281 @@ +Index: src/main/java/ru/javawebinar/topjava/Profiles.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/Profiles.java (date 1562192182410) ++++ src/main/java/ru/javawebinar/topjava/Profiles.java (date 1562192182410) +@@ -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 = POSTGRES_DB; ++} +Index: src/main/resources/db/hsqldb.properties +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/hsqldb.properties (date 1562191872000) ++++ src/main/resources/db/hsqldb.properties (date 1562191887654) +@@ -3,7 +3,6 @@ + database.url=jdbc:hsqldb:mem:topjava + database.username=sa + database.password= +-database.driverClassName=org.hsqldb.jdbcDriver + + database.init=true + jdbc.initLocation=initDB_hsql.sql +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562191872000) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562191887684) +@@ -4,10 +4,12 @@ + import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.dao.DataAccessException; ++import org.springframework.test.context.ActiveProfiles; + import org.springframework.test.context.ContextConfiguration; + import org.springframework.test.context.jdbc.Sql; + import org.springframework.test.context.jdbc.SqlConfig; + import org.springframework.test.context.junit4.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 { + + @Autowired +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562191872000) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562191887677) +@@ -9,10 +9,12 @@ + import org.junit.runner.RunWith; + import org.slf4j.Logger; + 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; + +@@ -31,6 +33,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"); + +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1562191872000) ++++ src/main/resources/spring/spring-db.xml (date 1562192168524) +@@ -12,17 +12,8 @@ + + + +- ++ + +- +- +- +- +- + + + +@@ -30,15 +21,6 @@ + + + +- +- +- +- +- +- +- +- + +@@ -76,4 +58,36 @@ + + + --> ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + +\ No newline at end of file +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1562191872000) ++++ pom.xml (date 1562191957695) +@@ -17,6 +17,7 @@ + UTF-8 + + 5.1.7.RELEASE ++ 9.0.21 + + + 1.2.3 +@@ -65,13 +66,6 @@ + ${slf4j.version} + compile + +- +- +- org.slf4j +- jul-to-slf4j +- ${slf4j.version} +- runtime +- + + + ch.qos.logback +@@ -98,19 +92,6 @@ + ${spring.version} + + +- +- +- org.postgresql +- postgresql +- ${postgresql.version} +- +- +- org.hsqldb +- hsqldb +- 2.3.4 +- +- +- + + + org.hibernate +@@ -167,6 +148,42 @@ + + + ++ ++ hsqldb ++ ++ ++ org.hsqldb ++ hsqldb ++ 2.3.4 ++ ++ ++ ++ ++ ++ postgres ++ ++ ++ org.postgresql ++ postgresql ++ ${postgresql.version} ++ ++ ++ org.apache.tomcat ++ tomcat-jdbc ++ ${tomcat.version} ++ provided ++ ++ ++ org.slf4j ++ jul-to-slf4j ++ ${slf4j.version} ++ runtime ++ ++ ++ ++ true ++ ++ + + + +Index: src/main/resources/db/postgres.properties +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/db/postgres.properties (date 1562191872000) ++++ src/main/resources/db/postgres.properties (date 1562191887662) +@@ -5,7 +5,6 @@ + database.url=jdbc:postgresql://localhost:5432/topjava + database.username=user + database.password=password +-database.driverClassName=org.postgresql.Driver + + database.init=true + jdbc.initLocation=initDB.sql diff --git a/patches/5_6_profile_resolver.patch b/patches/5_6_profile_resolver.patch new file mode 100644 index 000000000000..c233317f417f --- /dev/null +++ b/patches/5_6_profile_resolver.patch @@ -0,0 +1,98 @@ +Index: src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java (date 1562192280028) ++++ src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java (date 1562192280028) +@@ -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 +Index: src/main/java/ru/javawebinar/topjava/Profiles.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/Profiles.java (date 1562192265000) ++++ src/main/java/ru/javawebinar/topjava/Profiles.java (date 1562192280000) +@@ -11,5 +11,18 @@ + POSTGRES_DB = "postgres", + HSQL_DB = "hsqldb"; + +- public static final String ACTIVE_DB = POSTGRES_DB; ++ // Get DB profile depending of DB driver in classpath ++ public static String getActiveDbProfile() { ++ 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"); ++ } ++ } ++ } + } +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562192265000) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562192280019) +@@ -9,7 +9,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 { + + @Autowired +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562192265000) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562192280010) +@@ -14,7 +14,7 @@ + import org.springframework.test.context.jdbc.Sql; + import org.springframework.test.context.jdbc.SqlConfig; + import org.springframework.test.context.junit4.SpringRunner; +-import ru.javawebinar.topjava.Profiles; ++import ru.javawebinar.topjava.ActiveDbProfileResolver; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.util.exception.NotFoundException; + +@@ -33,7 +33,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/patches/5_7_spring_data_jpa.patch b/patches/5_7_spring_data_jpa.patch new file mode 100644 index 000000000000..bf3a40c23b0a --- /dev/null +++ b/patches/5_7_spring_data_jpa.patch @@ -0,0 +1,296 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java (date 1562192678202) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java (date 1562192678202) +@@ -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 DataJpaUserRepository 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); ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java (date 1562192397438) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java (date 1562192397438) +@@ -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 { ++} +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java (date 1562192678198) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java (date 1562192678198) +@@ -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 DataJpaMealRepository 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; ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java (date 1562192710910) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java (date 1562192710910) +@@ -0,0 +1,19 @@ ++package ru.javawebinar.topjava.repository.datajpa; ++ ++import org.springframework.data.jpa.repository.JpaRepository; ++import org.springframework.data.jpa.repository.Modifying; ++import org.springframework.data.jpa.repository.Query; ++import org.springframework.data.repository.query.Param; ++import org.springframework.transaction.annotation.Transactional; ++import ru.javawebinar.topjava.model.User; ++ ++@Transactional(readOnly = true) ++public interface CrudUserRepository extends JpaRepository { ++ @Transactional ++ @Modifying ++// @Query(name = User.DELETE) ++ @Query("DELETE FROM User u WHERE u.id=:id") ++ int delete(@Param("id") int id); ++ ++ User getByEmail(String email); ++} +Index: src/main/java/ru/javawebinar/topjava/Profiles.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/Profiles.java (date 1562192377000) ++++ src/main/java/ru/javawebinar/topjava/Profiles.java (date 1562192397466) +@@ -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", +Index: src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1562192377000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1562192397432) +@@ -1,13 +1,14 @@ + package ru.javawebinar.topjava.model; + + import org.hibernate.Hibernate; ++import org.springframework.data.domain.Persistable; + + 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 abstract class AbstractBaseEntity implements Persistable { + public static final int START_SEQ = 100000; + + @Id +@@ -29,10 +30,12 @@ + this.id = id; + } + ++ @Override + public Integer getId() { + return id; + } + ++ @Override + public boolean isNew() { + return this.id == null; + } +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1562192377000) ++++ src/main/resources/spring/spring-db.xml (date 1562192397476) +@@ -4,17 +4,22 @@ + xmlns:context="http://www.springframework.org/schema/context" + xmlns:jdbc="http://www.springframework.org/schema/jdbc" + xmlns:tx="http://www.springframework.org/schema/tx" ++ xmlns:jpa="http://www.springframework.org/schema/data/jpa" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd +- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> ++ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd ++ http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> + + + + + + +- ++ ++ ++ ++ + + + +@@ -90,4 +95,11 @@ + p:username="${database.username}" + p:password="${database.password}"/> + ++ ++ ++ ++ ++ ++ ++ + +\ No newline at end of file +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1562192377000) ++++ pom.xml (date 1562192533919) +@@ -17,6 +17,7 @@ + UTF-8 + + 5.1.7.RELEASE ++ 2.1.9.RELEASE + 9.0.21 + + +@@ -84,12 +85,11 @@ + + org.springframework + spring-context +- ${spring.version} + + +- org.springframework +- spring-orm +- ${spring.version} ++ org.springframework.data ++ spring-data-jpa ++ ${spring-data-jpa.version} + + + +@@ -136,7 +136,6 @@ + + org.springframework + spring-test +- ${spring.version} + test + + +@@ -187,5 +186,14 @@ +
+ + ++ ++ ++ org.springframework ++ spring-framework-bom ++ ${spring.version} ++ pom ++ import ++ ++ + + diff --git a/patches/5_8_spring_cache.patch b/patches/5_8_spring_cache.patch new file mode 100644 index 000000000000..0be6babcf71e --- /dev/null +++ b/patches/5_8_spring_cache.patch @@ -0,0 +1,208 @@ +Index: src/main/resources/spring/spring-tools.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-tools.xml (date 1562193006477) ++++ src/main/resources/spring/spring-tools.xml (date 1562193006477) +@@ -0,0 +1,22 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +Index: src/main/resources/cache/ehcache.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/cache/ehcache.xml (date 1562193006460) ++++ src/main/resources/cache/ehcache.xml (date 1562193006460) +@@ -0,0 +1,25 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 5 ++ ++ 5000 ++ ++ ++ ++ ++ ++ ++ 1 ++ ++ ++ ++ +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562192901000) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562193762890) +@@ -1,8 +1,10 @@ + package ru.javawebinar.topjava.service; + ++import org.junit.Before; + import org.junit.Test; + import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; ++import org.springframework.cache.CacheManager; + import org.springframework.dao.DataAccessException; + import org.springframework.test.context.ActiveProfiles; + import org.springframework.test.context.ContextConfiguration; +@@ -32,6 +34,14 @@ + @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, new Date(), Collections.singleton(Role.ROLE_USER)); +Index: src/main/java/ru/javawebinar/topjava/service/UserService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1562192901000) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1562193006427) +@@ -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,11 +24,13 @@ + this.repository = repository; + } + ++ @CacheEvict(value = "users", allEntries = true) + public User create(User user) { + Assert.notNull(user, "user must not be null"); + return repository.save(user); + } + ++ @CacheEvict(value = "users", allEntries = true) + public void delete(int id) throws NotFoundException { + checkNotFoundWithId(repository.delete(id), id); + } +@@ -40,10 +44,12 @@ + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + ++ @Cacheable("users") + public List getAll() { + return repository.getAll(); + } + ++ @CacheEvict(value = "users", allEntries = true) + public void update(User user) throws NotFoundException { + Assert.notNull(user, "user must not be null"); + checkNotFoundWithId(repository.save(user), user.getId()); +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1562192901000) ++++ pom.xml (date 1562193684750) +@@ -33,6 +33,9 @@ + 5.4.3.Final + 6.0.17.Final + 3.0.1-b11 ++ ++ ++ 3.7.1 + + + +@@ -84,7 +87,7 @@ + + + org.springframework +- spring-context ++ spring-context-support + + + org.springframework.data +@@ -111,6 +114,26 @@ + ${javax-el.version} + provided + ++ ++ ++ ++ javax.cache ++ cache-api ++ 1.1.0 ++ ++ ++ org.ehcache ++ ehcache ++ ++ ${ehcache.version} ++ ++ ++ ++ ++ org.glassfish.jaxb ++ jaxb-runtime ++ 2.4.0-b180830.0438 ++ + + + +Index: src/main/resources/spring/spring-app.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-app.xml (date 1562192901000) ++++ src/main/resources/spring/spring-app.xml (date 1562193006468) +@@ -11,6 +11,7 @@ + + + --> ++ + + + diff --git a/patches/6_01_HW5_data_jpa.patch b/patches/6_01_HW5_data_jpa.patch new file mode 100644 index 000000000000..ba5f0cdf26a1 --- /dev/null +++ b/patches/6_01_HW5_data_jpa.patch @@ -0,0 +1,101 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java (date 1562790189000) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java (date 1562790227594) +@@ -1,7 +1,31 @@ + 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 ++ @Transactional ++ 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 +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java (date 1562790189000) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java (date 1562790227606) +@@ -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 DataJpaMealRepository implements MealRepository { + + @Autowired +- private CrudMealRepository crudRepository; ++ private CrudMealRepository crudMealRepository; ++ ++ @Autowired ++ private CrudUserRepository crudUserRepository; + + @Override ++ @Transactional + public Meal save(Meal meal, int userId) { +- return null; ++ if (!meal.isNew() && get(meal.getId(), userId) == null) { ++ return null; ++ } ++ meal.setUser(crudUserRepository.getOne(userId)); ++ return crudMealRepository.save(meal); + } + + @Override + public boolean delete(int id, int userId) { +- return false; ++ return crudMealRepository.delete(id, userId) != 0; + } + + @Override + public Meal get(int id, int userId) { +- return null; ++ return crudMealRepository.findById(id).filter(meal -> meal.getUser().getId() == userId).orElse(null); + } + + @Override + public List getAll(int userId) { +- return null; ++ return crudMealRepository.getAll(userId); + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { +- return null; ++ return crudMealRepository.getBetween(startDate, endDate, userId); + } + } diff --git a/patches/6_02_HW5_profile_test.patch b/patches/6_02_HW5_profile_test.patch new file mode 100644 index 000000000000..52f53dc4d381 --- /dev/null +++ b/patches/6_02_HW5_profile_test.patch @@ -0,0 +1,408 @@ +Index: src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java (date 1562791373719) ++++ src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java (date 1562791373719) +@@ -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 +Index: src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java (date 1562791373724) ++++ src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java (date 1562791373724) +@@ -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 +Index: src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java (date 1562791373709) ++++ src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java (date 1562791373709) +@@ -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 +Index: src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java (date 1562791373714) ++++ src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java (date 1562791373714) +@@ -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 +Index: src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java (date 1562792211275) ++++ src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java (date 1562792211275) +@@ -0,0 +1,58 @@ ++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.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 java.util.concurrent.TimeUnit; ++ ++import static org.slf4j.LoggerFactory.getLogger; ++ ++@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) ++abstract public class AbstractServiceTest { ++ private static final Logger log = getLogger("result"); ++ ++ private static StringBuilder results = new StringBuilder(); ++ ++ // https://dzone.com/articles/applying-new-jdk-11-string-methods ++ private static String DELIM = "-".repeat(103); ++ ++ @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"); ++ } ++ }; ++ ++ ++ @AfterClass ++ public static void printResult() { ++ log.info("\n" + DELIM + ++ "\nTest Duration, ms" + ++ "\n" + DELIM + "\n" + results + DELIM + "\n"); ++ results.setLength(0); ++ } ++} +\ No newline at end of file +Index: src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java (date 1562791373698) ++++ src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java (date 1562791373698) +@@ -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 { ++} +Index: src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java (date 1562791373703) ++++ src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java (date 1562791373703) +@@ -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 +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562791364000) ++++ src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java (date 1562791373730) +@@ -1,66 +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.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"); +- } +- }; +- +- @AfterClass +- public static void printResult() { +- log.info("\n---------------------------------" + +- "\nTest Duration, ms" + +- "\n---------------------------------" + +- results + +- "\n---------------------------------"); +- } ++public abstract class AbstractMealServiceTest extends AbstractServiceTest { + + @Autowired + private MealService service; +Index: src/main/resources/spring/spring-db.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/spring/spring-db.xml (date 1562791364000) ++++ src/main/resources/spring/spring-db.xml (date 1562791373692) +@@ -11,58 +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"> + +- +- +- +- +- +- +- +- +- +- + + + + + +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- ++ + + + +@@ -97,9 +51,50 @@ + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + ++ + ++ + ++ + ++ ++ + + +\ No newline at end of file +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562791364000) ++++ src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java (date 1562791373743) +@@ -2,16 +2,9 @@ + + import org.junit.Before; + import org.junit.Test; +-import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.cache.CacheManager; + import org.springframework.dao.DataAccessException; +-import org.springframework.test.context.ActiveProfiles; +-import org.springframework.test.context.ContextConfiguration; +-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; +@@ -22,14 +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 { ++public abstract class AbstractUserServiceTest extends AbstractServiceTest { + + @Autowired + private UserService service; diff --git a/patches/6_03_extract_rules.patch b/patches/6_03_extract_rules.patch new file mode 100644 index 000000000000..3fef596e18dd --- /dev/null +++ b/patches/6_03_extract_rules.patch @@ -0,0 +1,122 @@ +Index: src/test/java/ru/javawebinar/topjava/TimingRules.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/TimingRules.java (date 1562792620869) ++++ src/test/java/ru/javawebinar/topjava/TimingRules.java (date 1562792620869) +@@ -0,0 +1,42 @@ ++package ru.javawebinar.topjava; ++ ++import org.junit.rules.ExternalResource; ++import org.junit.rules.Stopwatch; ++import org.junit.runner.Description; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++ ++import java.util.concurrent.TimeUnit; ++ ++public class TimingRules { ++ private static final Logger log = LoggerFactory.getLogger("result"); ++ ++ private static StringBuilder results = new StringBuilder(); ++ ++ // https://dzone.com/articles/applying-new-jdk-11-string-methods ++ private static String DELIM = "-".repeat(103); ++ ++ // 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" + DELIM + ++ "\nTest Duration, ms" + ++ "\n" + DELIM + "\n" + results + DELIM + "\n"); ++ } ++ }; ++} +Index: src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java (date 1562792319000) ++++ src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java (date 1562792438449) +@@ -1,22 +1,18 @@ + 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.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 java.util.concurrent.TimeUnit; +- +-import static org.slf4j.LoggerFactory.getLogger; ++import ru.javawebinar.topjava.TimingRules; + + @ContextConfiguration({ + "classpath:spring/spring-app.xml", +@@ -26,33 +22,12 @@ + @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) + @ActiveProfiles(resolver = ActiveDbProfileResolver.class) + abstract public class AbstractServiceTest { +- private static final Logger log = getLogger("result"); +- +- private static StringBuilder results = new StringBuilder(); +- +- // https://dzone.com/articles/applying-new-jdk-11-string-methods +- private static String DELIM = "-".repeat(103); ++ @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"); +- } +- }; +- +- +- @AfterClass +- public static void printResult() { +- log.info("\n" + DELIM + +- "\nTest Duration, ms" + +- "\n" + DELIM + "\n" + results + DELIM + "\n"); +- results.setLength(0); +- } ++ public ExpectedException thrown = ExpectedException.none(); + } +\ No newline at end of file diff --git a/patches/6_04_HW5_optional_fix_jdbc_profiles.patch b/patches/6_04_HW5_optional_fix_jdbc_profiles.patch new file mode 100644 index 000000000000..bdc72139ec97 --- /dev/null +++ b/patches/6_04_HW5_optional_fix_jdbc_profiles.patch @@ -0,0 +1,159 @@ +Index: src/test/java/ru/javawebinar/topjava/SpringMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/SpringMain.java (date 1562802201000) ++++ src/test/java/ru/javawebinar/topjava/SpringMain.java (date 1562802894407) +@@ -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.MealTo; +@@ -17,7 +16,11 @@ + public class SpringMain { + public static void main(String[] args) { + // java 7 automatic resource management +- try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.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@mail.ru", "password", Role.ROLE_ADMIN)); +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java (date 1562802201000) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java (date 1562802980111) +@@ -1,6 +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; +@@ -9,14 +9,16 @@ + 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 java.sql.Timestamp; + import java.time.LocalDateTime; + import java.util.List; + + @Repository +-public class JdbcMealRepository implements MealRepository { ++public abstract class JdbcMealRepository implements MealRepository { + + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); + +@@ -26,8 +28,7 @@ + + private final SimpleJdbcInsert insertMeal; + +- @Autowired +- public JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { ++ JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); +@@ -36,13 +37,42 @@ + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + ++ ++ protected abstract T toDbDateTime(LocalDateTime ldt); ++ ++ @Repository ++ @Profile(Profiles.POSTGRES_DB) ++ public static class Java8JdbcMealRepository extends JdbcMealRepository { ++ public Java8JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { ++ super(jdbcTemplate, namedParameterJdbcTemplate); ++ } ++ ++ @Override ++ protected LocalDateTime toDbDateTime(LocalDateTime ldt) { ++ return ldt; ++ } ++ } ++ ++ @Repository ++ @Profile(Profiles.HSQL_DB) ++ public static class TimestampJdbcMealRepository extends JdbcMealRepository { ++ public TimestampJdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { ++ super(jdbcTemplate, namedParameterJdbcTemplate); ++ } ++ ++ @Override ++ protected Timestamp toDbDateTime(LocalDateTime ldt) { ++ return Timestamp.valueOf(ldt); ++ } ++ } ++ + @Override + public Meal save(Meal meal, int userId) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", meal.getId()) + .addValue("description", meal.getDescription()) + .addValue("calories", meal.getCalories()) +- .addValue("date_time", meal.getDateTime()) ++ .addValue("date_time", toDbDateTime(meal.getDateTime())) + .addValue("user_id", userId); + + if (meal.isNew()) { +@@ -82,6 +112,6 @@ + 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)); + } + } +Index: src/main/java/ru/javawebinar/topjava/web/MealServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1562802201000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1562802951888) +@@ -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 org.springframework.util.StringUtils; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.web.meal.MealRestController; +@@ -23,13 +23,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/patches/6_05_update_hsqldb.patch b/patches/6_05_update_hsqldb.patch new file mode 100644 index 000000000000..3e81f301c373 --- /dev/null +++ b/patches/6_05_update_hsqldb.patch @@ -0,0 +1,111 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java (date 1562803031000) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java (date 1562803144726) +@@ -1,6 +1,6 @@ + package ru.javawebinar.topjava.repository.jdbc; + +-import org.springframework.context.annotation.Profile; ++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; +@@ -9,16 +9,14 @@ + 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 java.sql.Timestamp; + import java.time.LocalDateTime; + import java.util.List; + + @Repository +-public abstract class JdbcMealRepository implements MealRepository { ++public class JdbcMealRepository implements MealRepository { + + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); + +@@ -28,7 +26,8 @@ + + private final SimpleJdbcInsert insertMeal; + +- JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { ++ @Autowired ++ public JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); +@@ -37,42 +36,13 @@ + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + +- +- protected abstract T toDbDateTime(LocalDateTime ldt); +- +- @Repository +- @Profile(Profiles.POSTGRES_DB) +- public static class Java8JdbcMealRepository extends JdbcMealRepository { +- public Java8JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { +- super(jdbcTemplate, namedParameterJdbcTemplate); +- } +- +- @Override +- protected LocalDateTime toDbDateTime(LocalDateTime ldt) { +- return ldt; +- } +- } +- +- @Repository +- @Profile(Profiles.HSQL_DB) +- public static class TimestampJdbcMealRepository extends JdbcMealRepository { +- public TimestampJdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { +- super(jdbcTemplate, namedParameterJdbcTemplate); +- } +- +- @Override +- protected Timestamp toDbDateTime(LocalDateTime ldt) { +- return Timestamp.valueOf(ldt); +- } +- } +- + @Override + public Meal save(Meal meal, int userId) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", meal.getId()) + .addValue("description", meal.getDescription()) + .addValue("calories", meal.getCalories()) +- .addValue("date_time", toDbDateTime(meal.getDateTime())) ++ .addValue("date_time", meal.getDateTime()) + .addValue("user_id", userId); + + if (meal.isNew()) { +@@ -112,6 +82,6 @@ + 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); + } + } +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1562803031000) ++++ pom.xml (date 1562803093173) +@@ -176,7 +176,7 @@ + + org.hsqldb + hsqldb +- 2.3.4 ++ 2.5.0 + + + diff --git a/patches/6_06_HW5_optional_fetch_join.patch b/patches/6_06_HW5_optional_fetch_join.patch new file mode 100644 index 000000000000..3a4d5aacc6b3 --- /dev/null +++ b/patches/6_06_HW5_optional_fetch_join.patch @@ -0,0 +1,299 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java (date 1562793782534) +@@ -16,4 +16,7 @@ + int delete(@Param("id") int id); + + User getByEmail(String email); ++ ++ @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") ++ User getWithMeals(int id); + } +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java (date 1562793757571) +@@ -28,4 +28,7 @@ + @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 +Index: src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java (date 1562793707000) ++++ src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java (date 1562793812213) +@@ -15,7 +15,7 @@ + public abstract class AbstractMealServiceTest extends AbstractServiceTest { + + @Autowired +- private MealService service; ++ protected MealService service; + + @Test + public void delete() throws Exception { +Index: src/main/java/ru/javawebinar/topjava/model/User.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/User.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1562793757559) +@@ -8,10 +8,7 @@ + import javax.validation.constraints.NotBlank; + import javax.validation.constraints.NotNull; + import javax.validation.constraints.Size; +-import java.util.Collection; +-import java.util.Date; +-import java.util.EnumSet; +-import java.util.Set; ++import java.util.*; + + import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +@@ -56,6 +53,10 @@ + @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() { + } + +@@ -125,6 +126,10 @@ + this.roles = CollectionUtils.isEmpty(roles) ? EnumSet.noneOf(Role.class) : EnumSet.copyOf(roles); + } + ++ public List getMeals() { ++ return meals; ++ } ++ + @Override + public String toString() { + return "User{" + +Index: src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java (date 1562793707000) ++++ src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java (date 1562793812220) +@@ -18,7 +18,7 @@ + public abstract class AbstractUserServiceTest extends AbstractServiceTest { + + @Autowired +- private UserService service; ++ protected UserService service; + + @Autowired + private CacheManager cacheManager; +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java (date 1562793812238) +@@ -39,4 +39,9 @@ + public List getAll() { + return crudRepository.findAll(SORT_NAME_EMAIL); + } ++ ++ @Override ++ public User getWithMeals(int id) { ++ return crudRepository.getWithMeals(id); ++ } + } +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java (date 1562793812231) +@@ -47,4 +47,9 @@ + 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); ++ } + } +Index: src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (date 1562793782589) +@@ -18,4 +18,8 @@ + User getByEmail(String email); + + List getAll(); ++ ++ default User getWithMeals(int id) { ++ throw new UnsupportedOperationException(); ++ } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1562793782573) +@@ -20,4 +20,8 @@ + + // ORDERED dateTime desc + List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); ++ ++ default Meal getWithUser(int id, int userId) { ++ throw new UnsupportedOperationException(); ++ } + } +Index: src/main/java/ru/javawebinar/topjava/service/UserService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1562793987068) +@@ -54,4 +54,8 @@ + Assert.notNull(user, "user must not be null"); + checkNotFoundWithId(repository.save(user), user.getId()); + } ++ ++ public User getWithMeals(int id) throws NotFoundException { ++ return checkNotFoundWithId(repository.getWithMeals(id), id); ++ } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/service/MealService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1562793707000) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1562793961646) +@@ -57,4 +57,8 @@ + Assert.notNull(meal, "meal must not be null"); + return repository.save(meal, userId); + } ++ ++ public Meal getWithUser(int id, int userId) throws NotFoundException { ++ return checkNotFoundWithId(repository.getWithUser(id, userId), id); ++ } + } +\ No newline at end of file +Index: src/test/java/ru/javawebinar/topjava/UserTestData.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1562793707000) ++++ src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1562793812226) +@@ -16,7 +16,7 @@ + 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, Iterable expected) { +- assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); ++ assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles", "meals").isEqualTo(expected); + } + } +Index: src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java (date 1562793707000) ++++ src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java (date 1562793812198) +@@ -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); ++ } + } +Index: src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java (date 1562793707000) ++++ src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java (date 1562793812205) +@@ -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 diff --git a/patches/6_06_fix.patch b/patches/6_06_fix.patch new file mode 100644 index 000000000000..f460e271c30d --- /dev/null +++ b/patches/6_06_fix.patch @@ -0,0 +1,34 @@ +Index: src/main/java/ru/javawebinar/topjava/service/UserService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserService.java (revision a7c401bcbf60c055d0618f6de5379760fa80aa07) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1563015104836) +@@ -54,7 +54,7 @@ + checkNotFoundWithId(repository.save(user), user.getId()); + } + +- public User getWithMeals(int id) throws NotFoundException { ++ public User getWithMeals(int id) { + return checkNotFoundWithId(repository.getWithMeals(id), id); + } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/service/MealService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/MealService.java (revision a7c401bcbf60c055d0618f6de5379760fa80aa07) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1563015104828) +@@ -57,7 +57,7 @@ + return repository.save(meal, userId); + } + +- public Meal getWithUser(int id, int userId) throws NotFoundException { ++ public Meal getWithUser(int id, int userId) { + return checkNotFoundWithId(repository.getWithUser(id, userId), id); + } + } +\ No newline at end of file diff --git a/patches/6_07_HW5_graph_batch_size.patch b/patches/6_07_HW5_graph_batch_size.patch new file mode 100644 index 000000000000..4bf379e9066b --- /dev/null +++ b/patches/6_07_HW5_graph_batch_size.patch @@ -0,0 +1,61 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java (date 1562794248000) ++++ src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java (date 1562794455641) +@@ -1,5 +1,6 @@ + package ru.javawebinar.topjava.repository.datajpa; + ++import org.springframework.data.jpa.repository.EntityGraph; + import org.springframework.data.jpa.repository.JpaRepository; + import org.springframework.data.jpa.repository.Modifying; + import org.springframework.data.jpa.repository.Query; +@@ -11,12 +12,12 @@ + public interface CrudUserRepository extends JpaRepository { + @Transactional + @Modifying +-// @Query(name = User.DELETE) + @Query("DELETE FROM User u WHERE u.id=:id") + int delete(@Param("id") int id); + + User getByEmail(String email); + +- @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") ++ @EntityGraph(attributePaths = {"meals", "roles"}) ++ @Query("SELECT u FROM User u WHERE u.id=?1") + User getWithMeals(int id); + } +Index: src/main/java/ru/javawebinar/topjava/model/User.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/User.java (date 1562794248000) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1562794272051) +@@ -1,5 +1,6 @@ + package ru.javawebinar.topjava.model; + ++import org.hibernate.annotations.BatchSize; + import org.hibernate.validator.constraints.Range; + import org.springframework.util.CollectionUtils; + +@@ -15,7 +16,7 @@ + @NamedQueries({ + @NamedQuery(name = User.DELETE, query = "DELETE FROM User u WHERE u.id=:id"), + @NamedQuery(name = User.BY_EMAIL, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email=?1"), +- @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"), ++ @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u ORDER BY u.name, u.email"), + }) + @Entity + @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) +@@ -47,6 +48,8 @@ + @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", nullable = false, columnDefinition = "int default 2000") diff --git a/patches/6_0_fix.patch b/patches/6_0_fix.patch new file mode 100644 index 000000000000..65a4b2f67de8 --- /dev/null +++ b/patches/6_0_fix.patch @@ -0,0 +1,74 @@ +Index: src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (revision 90db1242ea07de0bd40ea28798de53f37c3d0474) ++++ src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java (date 1562801819154) +@@ -101,7 +101,7 @@ + @Test + public void getNotFound() throws Exception { + thrown.expect(NotFoundException.class); +- service.get(MEAL1_ID, ADMIN_ID); ++ service.get(1, ADMIN_ID); + } + + @Test +Index: src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (revision 90db1242ea07de0bd40ea28798de53f37c3d0474) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1562801819155) +@@ -47,6 +47,7 @@ + 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(created, newUser); + assertMatch(service.getAll(), ADMIN, newUser, USER); + } + +Index: src/main/java/ru/javawebinar/topjava/service/UserService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/service/UserService.java (revision 90db1242ea07de0bd40ea28798de53f37c3d0474) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1562801881725) +@@ -7,7 +7,6 @@ + import org.springframework.util.Assert; + import ru.javawebinar.topjava.model.User; + import ru.javawebinar.topjava.repository.UserRepository; +-import ru.javawebinar.topjava.util.exception.NotFoundException; + + import java.util.List; + +@@ -31,15 +30,15 @@ + } + + @CacheEvict(value = "users", allEntries = true) +- public void delete(int id) throws NotFoundException { ++ public void delete(int id) { + checkNotFoundWithId(repository.delete(id), id); + } + +- public User get(int id) throws NotFoundException { ++ public User get(int id) { + return checkNotFoundWithId(repository.get(id), id); + } + +- public User getByEmail(String email) throws NotFoundException { ++ public User getByEmail(String email) { + Assert.notNull(email, "email must not be null"); + return checkNotFound(repository.getByEmail(email), "email=" + email); + } +@@ -50,7 +49,7 @@ + } + + @CacheEvict(value = "users", allEntries = true) +- public void update(User user) throws NotFoundException { ++ public void update(User user) { + Assert.notNull(user, "user must not be null"); + checkNotFoundWithId(repository.save(user), user.getId()); + } diff --git a/patches/Prepare_to_HW0.patch b/patches/Prepare_to_HW0.patch new file mode 100644 index 000000000000..c62d68e2e01d --- /dev/null +++ b/patches/Prepare_to_HW0.patch @@ -0,0 +1,158 @@ +Index: src/main/java/ru/javawebinar/topjava/Main.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/Main.java (date 1518688470000) ++++ src/main/java/ru/javawebinar/topjava/Main.java (date 1524952636638) +@@ -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) { +Index: src/main/java/ru/javawebinar/topjava/model/UserMeal.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMeal.java (date 1524952566910) ++++ src/main/java/ru/javawebinar/topjava/model/UserMeal.java (date 1524952566910) +@@ -0,0 +1,29 @@ ++package ru.javawebinar.topjava.model; ++ ++import java.time.LocalDateTime; ++ ++public class UserMeal { ++ private final LocalDateTime dateTime; ++ ++ private final String description; ++ ++ private final int calories; ++ ++ public UserMeal(LocalDateTime dateTime, String description, int calories) { ++ this.dateTime = dateTime; ++ this.description = description; ++ this.calories = calories; ++ } ++ ++ public LocalDateTime getDateTime() { ++ return dateTime; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ ++ public int getCalories() { ++ return calories; ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (date 1524952581843) ++++ src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (date 1524952581843) +@@ -0,0 +1,9 @@ ++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; ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (date 1524952589076) ++++ src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (date 1524952589076) +@@ -0,0 +1,31 @@ ++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; ++ ++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; ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java (date 1524952574889) ++++ src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java (date 1524952574889) +@@ -0,0 +1,20 @@ ++package ru.javawebinar.topjava.model; ++ ++import java.time.LocalDateTime; ++ ++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; ++ } ++} +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1518688470000) ++++ pom.xml (date 1524952636647) +@@ -24,7 +24,7 @@ + + org.apache.maven.plugins + maven-compiler-plugin +- 3.1 ++ 3.7.0 + + ${java.version} + ${java.version} diff --git a/pom.xml b/pom.xml index c8a1c78f3b29..05a7101173eb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT @@ -12,33 +12,211 @@ http://topjava.herokuapp.com/ - 1.8 + 11 UTF-8 UTF-8 + + 5.1.7.RELEASE + 2.1.9.RELEASE + 9.0.21 + + + 1.2.3 + 1.7.25 + + + 42.2.6 + + 4.12 + + + 5.4.3.Final + 6.0.17.Final + 3.0.1-b11 + + + 3.7.1 topjava - install + package org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.8.1 ${java.version} ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + -Dfile.encoding=UTF-8 + + + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + + + + javax.annotation + javax.annotation-api + 1.3.2 + + + + + org.springframework + spring-context-support + + + org.springframework.data + spring-data-jpa + ${spring-data-jpa.version} + + + + + org.hibernate + hibernate-core + ${hibernate.version} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + + + org.glassfish + javax.el + ${javax-el.version} + provided + + + + + javax.cache + cache-api + 1.1.0 + + + org.ehcache + ehcache + + ${ehcache.version} + + + + + org.glassfish.jaxb + jaxb-runtime + 2.4.0-b180830.0438 + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + + javax.servlet + jstl + 1.2 + + + + + junit + junit + ${junit.version} + test + + + org.springframework + spring-test + test + + + org.assertj + assertj-core + 3.12.2 + test + + + hsqldb + + + org.hsqldb + hsqldb + 2.5.0 + + + + + + postgres + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + provided + + + org.slf4j + jul-to-slf4j + ${slf4j.version} + runtime + + + + true + + + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + 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 b23a2f0961fc..000000000000 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.javawebinar.topjava; - -/** - * User: gkislin - * Date: 05.08.2015 - * - * @link http://caloriesmng.herokuapp.com/ - * @link https://github.com/JavaOPs/topjava - */ -public class Main { - public static void main(String[] args) { - System.out.format("Hello Topjava Enterprise!"); - } -} 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..d6ef2e0379ed --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava; + +public class Profiles { + public static final String + JDBC = "jdbc", + JPA = "jpa", + DATAJPA = "datajpa"; + + public static final String REPOSITORY_IMPLEMENTATION = DATAJPA; + + public static final String + POSTGRES_DB = "postgres", + HSQL_DB = "hsqldb"; + + // 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/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java new file mode 100644 index 000000000000..fefa8d7e6acf --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -0,0 +1,64 @@ +package ru.javawebinar.topjava.model; + +import org.hibernate.Hibernate; +import org.springframework.data.domain.Persistable; + +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 implements Persistable { + 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") + +// See https://hibernate.atlassian.net/browse/HHH-3718 and https://hibernate.atlassian.net/browse/HHH-12034 +// Proxy initialization when accessing its identifier managed now by JPA_PROXY_COMPLIANCE setting + protected Integer id; + + protected AbstractBaseEntity() { + } + + protected AbstractBaseEntity(Integer id) { + this.id = id; + } + + public void setId(Integer id) { + this.id = id; + } + + @Override + public Integer getId() { + return id; + } + + @Override + public boolean isNew() { + return this.id == null; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":" + id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !getClass().equals(Hibernate.getClass(o))) { + return false; + } + AbstractBaseEntity that = (AbstractBaseEntity) o; + return id != null && id.equals(that.id); + } + + @Override + public int hashCode() { + return id == null ? 0 : id; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java new file mode 100644 index 000000000000..edff2ab1718f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -0,0 +1,37 @@ +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; + + protected AbstractNamedEntity() { + } + + protected AbstractNamedEntity(Integer id, String name) { + super(id); + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return super.toString() + '(' + name + ')'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java new file mode 100644 index 000000000000..48989e05fb82 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -0,0 +1,110 @@ +package ru.javawebinar.topjava.model; + +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() { + } + + public Meal(LocalDateTime dateTime, String description, int calories) { + this(null, dateTime, description, calories); + } + + public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { + super(id); + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + 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; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } +} 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..0ecb4a400c34 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,147 @@ +package ru.javawebinar.topjava.model; + +import org.hibernate.annotations.BatchSize; +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.*; + +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 ORDER BY u.name, u.email"), +}) +@Entity +@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) +public class User extends AbstractNamedEntity { + + public static final String DELETE = "User.delete"; + public static final String BY_EMAIL = "User.getByEmail"; + public static final String ALL_SORTED = "User.getAllSorted"; + + @Column(name = "email", nullable = false, unique = true) + @Email + @NotBlank + @Size(max = 100) + private String email; + + @Column(name = "password", nullable = false) + @NotBlank + @Size(min = 5, max = 100) + private String password; + + @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") + private boolean enabled = true; + + @Column(name = "registered", nullable = false, columnDefinition = "timestamp default now()") + @NotNull + private Date registered = new Date(); + + @Enumerated(EnumType.STRING) + @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) + @Column(name = "role") + @ElementCollection(fetch = FetchType.EAGER) +// @Fetch(FetchMode.SUBSELECT) + @BatchSize(size = 200) + private Set roles; + + @Column(name = "calories_per_day", nullable = false, columnDefinition = "int default 2000") + @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() { + } + + public User(User u) { + this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRegistered(), u.getRoles()); + } + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, new Date(), EnumSet.of(role, roles)); + } + + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Date registered, Collection roles) { + super(id, name); + this.email = email; + this.password = password; + this.caloriesPerDay = caloriesPerDay; + this.enabled = enabled; + this.registered = registered; + setRoles(roles); + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getRegistered() { + return registered; + } + + public void setRegistered(Date registered) { + this.registered = registered; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getCaloriesPerDay() { + return caloriesPerDay; + } + + public void setCaloriesPerDay(int caloriesPerDay) { + this.caloriesPerDay = caloriesPerDay; + } + + public boolean isEnabled() { + return enabled; + } + + public Set getRoles() { + return roles; + } + + public String getPassword() { + return password; + } + + public void setRoles(Collection roles) { + this.roles = CollectionUtils.isEmpty(roles) ? EnumSet.noneOf(Role.class) : EnumSet.copyOf(roles); + } + + public List getMeals() { + return meals; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java new file mode 100644 index 000000000000..6d717ecbd033 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; + +import java.time.LocalDateTime; +import java.util.List; + +public interface MealRepository { + // null if updated meal do not belong to userId + Meal save(Meal meal, int userId); + + // false if meal do not belong to userId + boolean delete(int id, int userId); + + // null if meal do not belong to userId + Meal get(int id, int userId); + + // ORDERED dateTime desc + List getAll(int userId); + + // ORDERED dateTime desc + List 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 new file mode 100644 index 000000000000..9fecbddaaa19 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +public interface UserRepository { + // null if not found, when updated + User save(User user); + + // false if not found + boolean delete(int id); + + // null if not found + User get(int id); + + // null if not found + User getByEmail(String email); + + List getAll(); + + 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 new file mode 100644 index 000000000000..5ae264c292a3 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -0,0 +1,34 @@ +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 + @Transactional + Meal save(Meal item); + + @Query("SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC") + List getAll(@Param("userId") int userId); + + @SuppressWarnings("JpaQlInspection") + @Query("SELECT m from Meal m WHERE m.user.id=:userId AND m.dateTime BETWEEN :startDate AND :endDate ORDER BY m.dateTime DESC") + List getBetween(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate, @Param("userId") int userId); + + @Query("SELECT m FROM Meal m JOIN FETCH m.user WHERE m.id = ?1 and m.user.id = ?2") + Meal getWithUser(int id, int userId); +} \ 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 new file mode 100644 index 000000000000..806884a9c90b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -0,0 +1,23 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.model.User; + +@Transactional(readOnly = true) +public interface CrudUserRepository extends JpaRepository { + @Transactional + @Modifying + @Query("DELETE FROM User u WHERE u.id=:id") + int delete(@Param("id") int id); + + User getByEmail(String email); + + @EntityGraph(attributePaths = {"meals", "roles"}) + @Query("SELECT u FROM User u WHERE u.id=?1") + User getWithMeals(int id); +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java new file mode 100644 index 000000000000..c1245d83d877 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepository.java @@ -0,0 +1,55 @@ +package ru.javawebinar.topjava.repository.datajpa; + +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; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class DataJpaMealRepository implements MealRepository { + + @Autowired + private CrudMealRepository crudMealRepository; + + @Autowired + private CrudUserRepository crudUserRepository; + + @Override + @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 crudMealRepository.delete(id, userId) != 0; + } + + @Override + public Meal get(int id, int userId) { + return crudMealRepository.findById(id).filter(meal -> meal.getUser().getId() == userId).orElse(null); + } + + @Override + public List getAll(int userId) { + return crudMealRepository.getAll(userId); + } + + @Override + 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/DataJpaUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java new file mode 100644 index 000000000000..3f8c25e45cc9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepository.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.List; + +@Repository +public class DataJpaUserRepository 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); + } + + @Override + public User getWithMeals(int id) { + return crudRepository.getWithMeals(id); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java new file mode 100644 index 000000000000..fec32a8cca5b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java @@ -0,0 +1,87 @@ +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 java.time.LocalDateTime; +import java.util.List; + +@Repository +public class JdbcMealRepository 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 JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + @Override + public Meal save(Meal meal, int userId) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", meal.getId()) + .addValue("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 jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; + } + + @Override + public Meal get(int id, int userId) { + List meals = jdbcTemplate.query( + "SELECT * FROM meals WHERE id = ? AND user_id = ?", ROW_MAPPER, id, userId); + return DataAccessUtils.singleResult(meals); + } + + @Override + public List getAll(int userId) { + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? ORDER BY date_time DESC", ROW_MAPPER, userId); + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC", + ROW_MAPPER, userId, startDate, endDate); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java new file mode 100644 index 000000000000..412bfbebc5df --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepository.java @@ -0,0 +1,74 @@ +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.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.List; + +@Repository +public class JdbcUserRepository implements UserRepository { + + private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); + + private final JdbcTemplate jdbcTemplate; + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private final SimpleJdbcInsert insertUser; + + @Autowired + public JdbcUserRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertUser = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("users") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + @Override + public User save(User user) { + 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; + } + 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/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java new file mode 100644 index 000000000000..60cb654e55a9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepository.java @@ -0,0 +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 JpaMealRepository implements MealRepository { + + @PersistenceContext + private EntityManager em; + + @Override + @Transactional + public Meal save(Meal meal, int userId) { + 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 em.createNamedQuery(Meal.DELETE) + .setParameter("id", id) + .setParameter("userId", userId) + .executeUpdate() != 0; + } + + @Override + public Meal get(int id, int userId) { + Meal meal = em.find(Meal.class, id); + return meal != null && meal.getUser().getId() == userId ? meal : null; + } + + @Override + public List getAll(int userId) { + return em.createNamedQuery(Meal.ALL_SORTED, Meal.class) + .setParameter("userId", userId) + .getResultList(); + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + 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/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java new file mode 100644 index 000000000000..9ade13354ac1 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepository.java @@ -0,0 +1,72 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; + +@Repository +@Transactional(readOnly = true) +public class JpaUserRepository implements UserRepository { + +/* + @Autowired + private SessionFactory sessionFactory; + + private Session openSession() { + return sessionFactory.getCurrentSession(); + } +*/ + + @PersistenceContext + private EntityManager em; + + @Override + @Transactional + public User save(User user) { + if (user.isNew()) { + em.persist(user); + return user; + } else { + return em.merge(user); + } + } + + @Override + public User get(int id) { + return em.find(User.class, id); + } + + @Override + @Transactional + public boolean delete(int id) { + +/* User ref = em.getReference(User.class, id); + em.remove(ref); + + Query query = em.createQuery("DELETE FROM User u WHERE u.id=:id"); + return query.setParameter("id", id).executeUpdate() != 0; +*/ + return em.createNamedQuery(User.DELETE) + .setParameter("id", id) + .executeUpdate() != 0; + } + + @Override + public User getByEmail(String email) { + List users = em.createNamedQuery(User.BY_EMAIL, User.class) + .setParameter(1, email) + .getResultList(); + return DataAccessUtils.singleResult(users); + } + + @Override + public List getAll() { + return em.createNamedQuery(User.ALL_SORTED, User.class).getResultList(); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 000000000000..886ac12685e8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,63 @@ +package ru.javawebinar.topjava.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.DateTimeUtil.adjustEndDateTime; +import static ru.javawebinar.topjava.util.DateTimeUtil.adjustStartDateTime; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +@Service +public class MealService { + + private final MealRepository repository; + + @Autowired + public MealService(MealRepository repository) { + this.repository = repository; + } + + public Meal get(int id, int userId) { + return checkNotFoundWithId(repository.get(id, userId), id); + } + + public void delete(int id, int userId) { + checkNotFoundWithId(repository.delete(id, userId), id); + } + + public List getBetweenDates(@Nullable LocalDate startDate, @Nullable LocalDate endDate, int userId) { + return getBetweenDateTimes(adjustStartDateTime(startDate), adjustEndDateTime(endDate), userId); + } + + private 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); + } + + public List getAll(int userId) { + return repository.getAll(userId); + } + + public void update(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); + checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + + public Meal create(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); + return repository.save(meal, userId); + } + + public Meal getWithUser(int id, int userId) { + return checkNotFoundWithId(repository.getWithUser(id, userId), id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java new file mode 100644 index 000000000000..4510c44b4300 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,60 @@ +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; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +@Service +public class UserService { + + private final UserRepository repository; + + @Autowired + public UserService(UserRepository repository) { + this.repository = repository; + } + + @CacheEvict(value = "users", allEntries = true) + public User create(User user) { + Assert.notNull(user, "user must not be null"); + return repository.save(user); + } + + @CacheEvict(value = "users", allEntries = true) + public void delete(int id) { + checkNotFoundWithId(repository.delete(id), id); + } + + public User get(int id) { + return checkNotFoundWithId(repository.get(id), id); + } + + public User getByEmail(String email) { + Assert.notNull(email, "email must not be null"); + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + + @Cacheable("users") + public List getAll() { + return repository.getAll(); + } + + @CacheEvict(value = "users", allEntries = true) + public void update(User user) { + Assert.notNull(user, "user must not be null"); + checkNotFoundWithId(repository.save(user), user.getId()); + } + + public User getWithMeals(int id) { + return checkNotFoundWithId(repository.getWithMeals(id), id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/to/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java new file mode 100644 index 000000000000..06917066d03b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -0,0 +1,58 @@ +package ru.javawebinar.topjava.to; + +import java.time.LocalDateTime; + +public class MealTo { + private final Integer id; + + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private final boolean excess; + + public MealTo(Integer id, LocalDateTime dateTime, String description, int calories, boolean excess) { + this.id = id; + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.excess = excess; + } + + public Integer getId() { + return id; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExceed() { + return excess; + } + + public boolean isExcess() { + return excess; + } + + @Override + public String toString() { + return "MealTo{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", excess=" + excess + + '}'; + } +} \ 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 new file mode 100644 index 000000000000..1403cb3d8611 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,44 @@ +package ru.javawebinar.topjava.util; + +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + // DataBase doesn't support LocalDate.MIN/MAX + private static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); + private static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + + private DateTimeUtil() { + } + + public static LocalDateTime adjustStartDateTime(LocalDate localDate) { + return adjustDateTime(localDate, MIN_DATE, LocalTime.MIN); + } + + public static LocalDateTime adjustEndDateTime(LocalDate localDate) { + return adjustDateTime(localDate, MAX_DATE, LocalTime.MAX); + } + + private static LocalDateTime adjustDateTime(LocalDate localDate, LocalDate defaultDate, LocalTime adjustTime) { + return LocalDateTime.of(localDate != null ? localDate : defaultDate, adjustTime); + } + + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } + + public static LocalDate parseLocalDate(@Nullable String str) { + return StringUtils.isEmpty(str) ? null : LocalDate.parse(str); + } + + public static LocalTime parseLocalTime(@Nullable String str) { + return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); + } +} 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..1c704b5aea77 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,48 @@ +package ru.javawebinar.topjava.util; + +import org.springframework.lang.Nullable; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.to.MealTo; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; + +public class MealsUtil { + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + + private MealsUtil() { + } + + public static List getWithExcess(Collection meals, int caloriesPerDay) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); + } + + public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, @Nullable LocalTime startTime, @Nullable LocalTime endTime) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> Util.isBetween(meal.getTime(), startTime, endTime)); + } + + private static List getFilteredWithExcess(Collection meals, int caloriesPerDay, Predicate filter) { + 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(filter) + .map(meal -> createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(toList()); + } + + private static MealTo createWithExcess(Meal meal, boolean excess) { + return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java new file mode 100644 index 000000000000..bc62117d887f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava.util; + +import org.springframework.lang.Nullable; + +public class Util { + private Util() { + } + + public static > boolean isBetween(T value, @Nullable T start, @Nullable T end) { + return (start == null || value.compareTo(start) >= 0) && (end == null || value.compareTo(end) <= 0); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java new file mode 100644 index 000000000000..cb51f3c60ac3 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.util; + + +import ru.javawebinar.topjava.model.AbstractBaseEntity; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +public class ValidationUtil { + + private ValidationUtil() { + } + + public static T checkNotFoundWithId(T object, int id) { + return checkNotFound(object, "id=" + id); + } + + public static void checkNotFoundWithId(boolean found, int id) { + checkNotFound(found, "id=" + id); + } + + public static T checkNotFound(T object, String msg) { + checkNotFound(object != null, msg); + return object; + } + + public static void checkNotFound(boolean found, String msg) { + if (!found) { + throw new NotFoundException("Not found entity with " + msg); + } + } + + public static void checkNew(AbstractBaseEntity entity) { + if (!entity.isNew()) { + throw new IllegalArgumentException(entity + " must be new (id=null)"); + } + } + + public static void assureIdConsistent(AbstractBaseEntity entity, int id) { +// conservative when you reply, but accept liberally (http://stackoverflow.com/a/32728226/548473) + if (entity.isNew()) { + entity.setId(id); + } else if (entity.getId() != id) { + throw new IllegalArgumentException(entity + " must be with id=" + id); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java new file mode 100644 index 000000000000..f1e9b0e46376 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.util.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java new file mode 100644 index 000000000000..1f5524153d82 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,99 @@ +package ru.javawebinar.topjava.web; + +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.Profiles; +import org.springframework.util.StringUtils; +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 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(); + mealController = springContext.getBean(MealRestController.class); + } + + @Override + public void destroy() { + springContext.close(); + super.destroy(); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + Meal meal = new Meal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); + + if (StringUtils.isEmpty(request.getParameter("id"))) { + mealController.create(meal); + } else { + mealController.update(meal, getId(request)); + } + response.sendRedirect("meals"); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String action = request.getParameter("action"); + + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); + mealController.delete(id); + response.sendRedirect("meals"); + break; + case "create": + case "update": + final Meal meal = "create".equals(action) ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : + mealController.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); + break; + case "filter": + LocalDate startDate = parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = parseLocalTime(request.getParameter("endTime")); + request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + case "all": + default: + request.setAttribute("meals", mealController.getAll()); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.parseInt(paramId); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java new file mode 100644 index 000000000000..4bad5863e3c6 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.web; + +import ru.javawebinar.topjava.model.AbstractBaseEntity; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class SecurityUtil { + + private static int id = AbstractBaseEntity.START_SEQ; + + private SecurityUtil() { + } + + public static int authUserId() { + return id; + } + + public static void setAuthUserId(int id) { + SecurityUtil.id = id; + } + + public static int authUserCaloriesPerDay() { + return DEFAULT_CALORIES_PER_DAY; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java new file mode 100644 index 000000000000..226023400c70 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,28 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.slf4j.LoggerFactory.getLogger; + +public class UserServlet extends HttpServlet { + private static final Logger log = getLogger(UserServlet.class); + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + int userId = Integer.parseInt(request.getParameter("userId")); + SecurityUtil.setAuthUserId(userId); + response.sendRedirect("meals"); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); + } +} 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..a3ed418a1bf1 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -0,0 +1,76 @@ +package ru.javawebinar.topjava.web.meal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.web.SecurityUtil; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +@Controller +public class MealRestController { + private static final Logger log = LoggerFactory.getLogger(MealRestController.class); + + private final MealService service; + + @Autowired + public MealRestController(MealService service) { + this.service = service; + } + + public Meal get(int id) { + int userId = SecurityUtil.authUserId(); + log.info("get meal {} for user {}", id, userId); + return service.get(id, userId); + } + + public void delete(int id) { + int userId = SecurityUtil.authUserId(); + log.info("delete meal {} for user {}", id, userId); + service.delete(id, userId); + } + + public List getAll() { + int userId = SecurityUtil.authUserId(); + log.info("getAll for user {}", userId); + return MealsUtil.getWithExcess(service.getAll(userId), SecurityUtil.authUserCaloriesPerDay()); + } + + public Meal create(Meal meal) { + int userId = SecurityUtil.authUserId(); + checkNew(meal); + log.info("create {} for user {}", meal, userId); + return service.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = SecurityUtil.authUserId(); + 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 = SecurityUtil.authUserId(); + log.info("getBetween dates({} - {}) time({} - {}) for user {}", startDate, endDate, startTime, endTime, userId); + + List mealsDateFiltered = service.getBetweenDates(startDate, endDate, userId); + return MealsUtil.getFilteredWithExcess(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), startTime, endTime); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java new file mode 100644 index 000000000000..a5bccd1d5b09 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,35 @@ +package ru.javawebinar.topjava.web.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.UserService; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +public abstract class AbstractUserController { + protected final Logger log = LoggerFactory.getLogger(getClass()); + + @Autowired + protected UserService service; + + public User get(int id) { + log.info("get {}", id); + return service.get(id); + } + + 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); + } +} \ 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..6b78a1a25989 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.web.user; + +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +@Controller +public class AdminRestController extends AbstractUserController { + + public List getAll() { + log.info("getAll"); + return service.getAll(); + } + + @Override + public User get(int id) { + return super.get(id); + } + + public User create(User user) { + log.info("create {}", user); + checkNew(user); + return service.create(user); + } + + @Override + public void delete(int id) { + super.delete(id); + } + + @Override + public void update(User user, int id) { + super.update(user, id); + } + + 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/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java new file mode 100644 index 000000000000..7d3702c31c46 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.web.user; + +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.model.User; + +import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; + +@Controller +public class ProfileRestController extends AbstractUserController { + + public User get() { + return super.get(authUserId()); + } + + public void delete() { + super.delete(authUserId()); + } + + public void update(User user) { + super.update(user, authUserId()); + } +} \ No newline at end of file diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml new file mode 100644 index 000000000000..c6347bd940d6 --- /dev/null +++ b/src/main/resources/cache/ehcache.xml @@ -0,0 +1,25 @@ + + + + + + + + + + 5 + + 5000 + + + + + + + 1 + + + + diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties new file mode 100644 index 000000000000..0ea68eb3d308 --- /dev/null +++ b/src/main/resources/db/hsqldb.properties @@ -0,0 +1,11 @@ +#database.url=jdbc:hsqldb:file:D:/temp/topjava + +database.url=jdbc:hsqldb:mem:topjava +database.username=sa +database.password= + +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.sql b/src/main/resources/db/initDB.sql new file mode 100644 index 000000000000..f39e3d5c3294 --- /dev/null +++ b/src/main/resources/db/initDB.sql @@ -0,0 +1,37 @@ +DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS meals; +DROP TABLE IF EXISTS users; +DROP SEQUENCE IF EXISTS global_seq; + +CREATE SEQUENCE global_seq START WITH 100000; + +CREATE TABLE users +( + id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), + name VARCHAR NOT NULL, + email VARCHAR NOT NULL, + password VARCHAR NOT NULL, + registered TIMESTAMP DEFAULT now() NOT NULL, + enabled BOOL DEFAULT TRUE NOT NULL, + calories_per_day INTEGER DEFAULT 2000 NOT NULL +); +CREATE UNIQUE INDEX users_unique_email_idx ON users (email); + +CREATE TABLE user_roles +( + user_id INTEGER NOT NULL, + role VARCHAR, + CONSTRAINT user_roles_idx UNIQUE (user_id, role), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); + +CREATE TABLE meals ( + id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), + 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/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql new file mode 100644 index 000000000000..8306d5157829 --- /dev/null +++ b/src/main/resources/db/initDB_hsql.sql @@ -0,0 +1,39 @@ +DROP TABLE user_roles IF EXISTS; +DROP TABLE meals IF EXISTS; +DROP TABLE users IF EXISTS; +DROP SEQUENCE global_seq IF EXISTS; + +CREATE SEQUENCE GLOBAL_SEQ AS INTEGER START WITH 100000; + +CREATE TABLE users +( + id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, + registered TIMESTAMP DEFAULT now() NOT NULL, + enabled BOOLEAN DEFAULT TRUE NOT NULL, + calories_per_day INTEGER DEFAULT 2000 NOT NULL +); +CREATE UNIQUE INDEX users_unique_email_idx + ON USERS (email); + +CREATE TABLE user_roles +( + user_id INTEGER NOT NULL, + role VARCHAR(255), + CONSTRAINT user_roles_idx UNIQUE (user_id, role), + FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE +); + +CREATE TABLE meals +( + id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY, + date_time TIMESTAMP NOT NULL, + description VARCHAR(255) NOT NULL, + calories INT NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx + ON meals (user_id, date_time) \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql new file mode 100644 index 000000000000..29a97efdc2b8 --- /dev/null +++ b/src/main/resources/db/populateDB.sql @@ -0,0 +1,22 @@ +DELETE FROM user_roles; +DELETE FROM meals; +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); + +INSERT INTO meals (date_time, description, calories, user_id) +VALUES ('2015-05-30 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-30 13:00:00', 'Обед', 1000, 100000), + ('2015-05-30 20:00:00', 'Ужин', 500, 100000), + ('2015-05-31 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-31 13:00:00', 'Обед', 1000, 100000), + ('2015-05-31 20:00:00', 'Ужин', 510, 100000), + ('2015-06-01 14:00:00', 'Админ ланч', 510, 100001), + ('2015-06-01 21:00:00', 'Админ ужин', 1500, 100001); diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties new file mode 100644 index 000000000000..cc20ca447a7a --- /dev/null +++ b/src/main/resources/db/postgres.properties @@ -0,0 +1,13 @@ +#database.url=jdbc:postgresql://ec2-54-247-74-197.eu-west-1.compute.amazonaws.com:5432/de4fjsqhdvl7ld?ssl=true&sslmode=require&sslfactory=org.postgresql.ssl.NonValidatingFactory +#database.username=anbxkjtzukqacj +#database.password=da1f25b2a38784fb0d46858e5b8fc168e08c9e1e9c72faea5bbac9c0e1f9c24f + +database.url=jdbc:postgresql://localhost:5432/topjava +database.username=user +database.password=password + +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/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..12a4f63c6c15 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{50}.%M:%L - %msg%n + + + + + + UTF-8 + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n + + + + + + + + + + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..afc0d787add0 --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml new file mode 100644 index 000000000000..a64af1dd224d --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring-tools.xml b/src/main/resources/spring/spring-tools.xml new file mode 100644 index 000000000000..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/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 new file mode 100644 index 000000000000..bd98d3bf3f6a --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,29 @@ + + + Topjava + + + userServlet + ru.javawebinar.topjava.web.UserServlet + 0 + + + userServlet + /users + + + + mealServlet + ru.javawebinar.topjava.web.MealServlet + 0 + + + mealServlet + /meals + + + diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css new file mode 100644 index 000000000000..04eb5b6b155b --- /dev/null +++ b/src/main/webapp/css/style.css @@ -0,0 +1,24 @@ +dl { + background: none repeat scroll 0 0 #FAFAFA; + margin: 8px 0; + padding: 0; +} + +dt { + display: inline-block; + width: 170px; +} + +dd { + display: inline-block; + margin-left: 8px; + vertical-align: top; +} + +tr[data-mealExcess="false"] { + color: green; +} + +tr[data-mealExcess="true"] { + color: blue; +} diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..886449733a86 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,18 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

+
+
+ Meals of  + + +
+ + diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp new file mode 100644 index 000000000000..e9c01a94c999 --- /dev/null +++ b/src/main/webapp/mealForm.jsp @@ -0,0 +1,34 @@ +<%@ 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 new file mode 100644 index 000000000000..ada0065bfb0b --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,66 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> + + + Meal + + + +
+

Home

+
+

Meals

+
+ +
+
From Date:
+
+
+
+
To Date:
+
+
+
+
From Time:
+
+
+
+
To Time:
+
+
+ +
+
+ Add Meal +

+ + + + + + + + + + + + + + + + + + + + +
DateDescriptionCalories
+ <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}UpdateDelete
+
+ + \ No newline at end of file diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp new file mode 100644 index 000000000000..60bdc9b83192 --- /dev/null +++ b/src/main/webapp/users.jsp @@ -0,0 +1,11 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Users + + +

Home

+
+

Users

+ + \ No newline at end of file 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/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java new file mode 100644 index 000000000000..4567c7d32f39 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -0,0 +1,46 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Meal; + +import java.time.Month; +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 = List.of(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).isEqualToIgnoringGivenFields(expected, "user"); + } + + public static void assertMatch(Iterable actual, Meal... expected) { + assertMatch(actual, List.of(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields("user").isEqualTo(expected); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java new file mode 100644 index 000000000000..c0266272161f --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,37 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.support.GenericXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.web.meal.MealRestController; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +public class SpringMain { + public static void main(String[] args) { + // java 7 automatic resource management + 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@mail.ru", "password", Role.ROLE_ADMIN)); + System.out.println(); + + MealRestController mealController = appCtx.getBean(MealRestController.class); + List filteredMealsWithExcess = + mealController.getBetween( + LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), + LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); + filteredMealsWithExcess.forEach(System.out::println); + } + } +} 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..5a13a6f84fed --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TimingRules.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava; + +import org.junit.rules.ExternalResource; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public class TimingRules { + private static final Logger log = LoggerFactory.getLogger("result"); + + private static StringBuilder results = new StringBuilder(); + + // https://dzone.com/articles/applying-new-jdk-11-string-methods + private static String DELIM = "-".repeat(103); + + // 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" + DELIM + + "\nTest Duration, ms" + + "\n" + DELIM + "\n" + results + DELIM + "\n"); + } + }; +} 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..397af2b0d4f8 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class UserTestData { + public static final int USER_ID = START_SEQ; + public static final int ADMIN_ID = START_SEQ + 1; + + public static final 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", "meals"); + } + + public static void assertMatch(Iterable actual, User... expected) { + assertMatch(actual, List.of(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles", "meals").isEqualTo(expected); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java new file mode 100644 index 000000000000..5792262edef3 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepository.java @@ -0,0 +1,40 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.AbstractBaseEntity; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +@Repository +public class InMemoryBaseRepository { + + private static AtomicInteger counter = new AtomicInteger(0); + + Map entryMap = new ConcurrentHashMap<>(); + + public T save(T entry) { + Objects.requireNonNull(entry, "Entry must not be null"); + if (entry.isNew()) { + entry.setId(counter.incrementAndGet()); + entryMap.put(entry.getId(), entry); + return entry; + } + return entryMap.computeIfPresent(entry.getId(), (id, oldT) -> entry); + } + + public boolean delete(int id) { + return entryMap.remove(id) != null; + } + + public T get(int id) { + return entryMap.get(id); + } + + Collection getCollection() { + return entryMap.values(); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java new file mode 100644 index 000000000000..ea71948b8211 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepository.java @@ -0,0 +1,74 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.Util; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Repository +public class InMemoryMealRepository implements MealRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepository.class); + + // Map userId -> (mealId-> meal) + private Map> usersMealsMap = new ConcurrentHashMap<>(); + + @Override + public Meal save(Meal meal, int userId) { + Objects.requireNonNull(meal, "meal must not be null"); + var meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepository<>()); + return meals.save(meal); + } + + @PostConstruct + public void postConstruct() { + log.info("+++ PostConstruct"); + } + + @PreDestroy + public void preDestroy() { + log.info("+++ PreDestroy"); + } + + @Override + public boolean delete(int id, int userId) { + var meals = usersMealsMap.get(userId); + return meals != null && meals.delete(id); + } + + @Override + public Meal get(int id, int userId) { + var meals = usersMealsMap.get(userId); + return meals == null ? null : meals.get(id); + } + + @Override + public List getAll(int userId) { + return getAllFiltered(userId, meal -> true); + } + + @Override + public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Objects.requireNonNull(startDateTime, "startDateTime must not be null"); + Objects.requireNonNull(endDateTime, "endDateTime must not be null"); + return getAllFiltered(userId, meal -> Util.isBetween(meal.getDateTime(), startDateTime, endDateTime)); + } + + private List getAllFiltered(int userId, Predicate filter) { + var meals = usersMealsMap.get(userId); + return meals == null ? Collections.emptyList() : + meals.getCollection().stream() + .filter(filter) + .sorted(Comparator.comparing(Meal::getDateTime).reversed()) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java new file mode 100644 index 000000000000..a3a0a024738d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepository.java @@ -0,0 +1,41 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; +import static ru.javawebinar.topjava.UserTestData.USER; + + +@Repository +public class InMemoryUserRepository extends InMemoryBaseRepository implements UserRepository { + + public void init() { + entryMap.clear(); + entryMap.put(UserTestData.USER_ID, USER); + entryMap.put(UserTestData.ADMIN_ID, ADMIN); + } + + @Override + public List getAll() { + return getCollection().stream() + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { + Objects.requireNonNull(email, "email must not be null"); + return getCollection().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() + .orElse(null); + } +} \ 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 new file mode 100644 index 000000000000..d151b5b47f4a --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -0,0 +1,90 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +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; + +public abstract class AbstractMealServiceTest extends AbstractServiceTest { + + @Autowired + protected MealService service; + + @Test + public void delete() throws Exception { + service.delete(MEAL1_ID, USER_ID); + assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); + } + + @Test + public void deleteNotFound() throws Exception { + thrown.expect(NotFoundException.class); + service.delete(1, USER_ID); + } + + @Test + public void deleteNotOwn() throws Exception { + thrown.expect(NotFoundException.class); + service.delete(MEAL1_ID, ADMIN_ID); + } + + @Test + public void create() throws Exception { + Meal newMeal = getCreated(); + Meal created = service.create(newMeal, USER_ID); + newMeal.setId(created.getId()); + assertMatch(newMeal, created); + assertMatch(service.getAll(USER_ID), newMeal, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + } + + @Test + public void get() throws Exception { + Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); + assertMatch(actual, ADMIN_MEAL1); + } + + @Test + public void getNotFound() throws Exception { + thrown.expect(NotFoundException.class); + service.get(1, ADMIN_ID); + } + + @Test + public void getNotOwn() throws Exception { + thrown.expect(NotFoundException.class); + service.get(MEAL1_ID, ADMIN_ID); + } + + @Test + public void update() throws Exception { + Meal updated = getUpdated(); + service.update(updated, USER_ID); + assertMatch(service.get(MEAL1_ID, USER_ID), updated); + } + + @Test + 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 getAll() throws Exception { + assertMatch(service.getAll(USER_ID), MEALS); + } + + @Test + 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); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java new file mode 100644 index 000000000000..d7a270369caa --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -0,0 +1,33 @@ +package ru.javawebinar.topjava.service; + +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.RunWith; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.ActiveDbProfileResolver; +import ru.javawebinar.topjava.TimingRules; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) +abstract public class AbstractServiceTest { + @ClassRule + public static ExternalResource summary = TimingRules.SUMMARY; + + @Rule + public Stopwatch stopwatch = TimingRules.STOPWATCH; + + @Rule + public ExpectedException thrown = ExpectedException.none(); +} \ 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 new file mode 100644 index 000000000000..5b836b509ffa --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -0,0 +1,87 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.dao.DataAccessException; +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.Date; +import java.util.List; + +import static ru.javawebinar.topjava.UserTestData.*; + +public abstract class AbstractUserServiceTest extends AbstractServiceTest { + + @Autowired + protected 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, new Date(), Collections.singleton(Role.ROLE_USER)); + User created = service.create(newUser); + newUser.setId(created.getId()); + assertMatch(created, newUser); + 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 deletedNotFound() 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 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..2b8363626b13 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.junit.Test; +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; + +@ActiveProfiles(DATAJPA) +public class DataJpaMealServiceTest extends AbstractMealServiceTest { + @Test + public void testGetWithUser() throws Exception { + Meal adminMeal = service.getWithUser(ADMIN_MEAL_ID, ADMIN_ID); + 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 new file mode 100644 index 000000000000..b9d42b83f4a1 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.junit.Test; +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.UserTestData.*; + +@ActiveProfiles(DATAJPA) +public class DataJpaUserServiceTest extends AbstractUserServiceTest { + @Test + public void testGetWithMeals() throws Exception { + User user = service.getWithMeals(USER_ID); + 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 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 diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java new file mode 100644 index 000000000000..b535e7c261f1 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -0,0 +1,46 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.Collection; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; + +@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/inmemory.xml"}) +@RunWith(SpringRunner.class) +public class InMemoryAdminRestControllerSpringTest { + + @Autowired + private AdminRestController controller; + + @Autowired + private InMemoryUserRepository repository; + + @Before + public void setUp() throws Exception { + repository.init(); + } + + @Test + public void delete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(1, users.size()); + Assert.assertEquals(ADMIN, users.iterator().next()); + } + + @Test(expected = NotFoundException.class) + public void deleteNotFound() throws Exception { + controller.delete(10); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java new file mode 100644 index 000000000000..843073c3452c --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -0,0 +1,51 @@ +package ru.javawebinar.topjava.web.user; + +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.inmemory.InMemoryUserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +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", "spring/inmemory.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 + InMemoryUserRepository repository = appCtx.getBean(InMemoryUserRepository.class); + repository.init(); + } + + @Test + public void delete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(1, users.size()); + Assert.assertEquals(ADMIN, users.iterator().next()); + } + + @Test(expected = NotFoundException.class) + public void deleteNotFound() throws Exception { + controller.delete(10); + } +} \ No newline at end of file diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 000000000000..019d8a1edcb6 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,33 @@ + + + + true + + + + + UTF-8 + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n + + + + + + UTF-8 + %magenta(%msg%n) + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/spring/inmemory.xml b/src/test/resources/spring/inmemory.xml new file mode 100644 index 000000000000..c6a2710cbd88 --- /dev/null +++ b/src/test/resources/spring/inmemory.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file