From 87c4880a074c3cf874371070be1a6ced3fbb4bc6 Mon Sep 17 00:00:00 2001 From: JavaOPs Date: Mon, 14 May 2018 18:22:49 +0300 Subject: [PATCH 01/44] Add info --- README.md | 195 ++++++++++++++++++++++++++++++++++++++++++++++++ ReleaseNotes.md | 154 ++++++++++++++++++++++++++++++++++++++ cv.md | 107 ++++++++++++++++++++++++++ description.md | 77 +++++++++++++++++++ graduation.md | 66 ++++++++++++++++ 5 files changed, 599 insertions(+) create mode 100644 README.md create mode 100644 ReleaseNotes.md create mode 100644 cv.md create mode 100644 description.md create mode 100644 graduation.md diff --git a/README.md b/README.md new file mode 100644 index 000000000000..087341d555c9 --- /dev/null +++ b/README.md @@ -0,0 +1,195 @@ +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 + - [Основы 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: +- должны возвращаться только записи между startTime и endTime +- поле UserMealWithExceed.exceed должно показывать, + превышает ли сумма калорий за весь день параметра метода caloriesPerDay + +Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. + +- Проверьте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) +- Оцените Time complexity вашего алгоритма, если он O(N*N)- попробуйте сделать O(N). +``` +- Java 8 Date and Time API +- Алгоритмы и структуры данных для начинающих: сложность алгоритмов +- Time complexity +- Временная сложность алгоритма +- Вычислительная сложность + +#### Optional (Java 8 Stream API) +``` +Сделать реализацию через Java 8 Stream API. +``` +- Видео: Доступно о Java 8 Lambda +- Java 8: Lambda выражения +- Java 8: Потоки +- Pуководство по Java 8 Stream +- [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 +``` +#### Замечания по использованию 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: Не полагайтесь в решении на то, что список будет подаваться отсортированным. Такого условия нет. +----- + +### Полезные ресурсы +> ВНИМАНИЕ: +> - ДЗ первого урока будет связано с созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах. Введение будет, но предварительное знакомство не помешает. +> - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! + +Все остальное - опционально. + +#### HTML, JavaScript, CSS +- [Справочник по WEB](https://developer.mozilla.org/ru/) +- [Видео по WEB технологиям](https://www.youtube.com/user/WebMagistersRu/playlists) +- [Изучение JavaScript в одном видео уроке за час](https://www.youtube.com/watch?v=QBWWplFkdzw) +- HTML, CSS, JAVASCRIPT, SQL, JQUERY, BOOTSTRAP +- Введение в программирование на JavaScript +- Стандарты кодирования для HTML, CSS и JavaScript’a +- Основы работы с HTML/CSS/JavaScript +- JavaScript - Основы +- Основы JavaScript +- Bootstrap 3 - Основы +- jQuery для начинающих + +#### Java (базовые вещи) +- Интуит. Программирование на Java +- 1й урок MasterJava: Многопоточность +- Основы Java garbage collection +- Размер Java объектов +- Введение в Java Reflection API +- Структуры данных в картинках +- Обзор java.util.concurrent.* +- Синхронизация потоков +- String literal pool +- Маленькие хитрости Java +- A Guide to Java 8 + +### Туториалы, разное +[Что нужно знать о бэкенде новичку в веб-разработке](https://tproger.ru/translations/backend-web-development) +[Туториалы: Spring Framework, Hibernate, Java Core, JDBC](http://proselyte.net/tutorials/) + +#### Сервлеты +- Как создать Servlet? Полное руководство. + +#### 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..da7230a1bf32 --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,154 @@ +# TopJava Release Notes +### Topjava 13 +- [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) +- Для отображения цвета еды и выключенного юзера использую [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.x, Spring Data 2.x, 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..7046f27aac99 --- /dev/null +++ b/cv.md @@ -0,0 +1,107 @@ +## Составление резюме, подготовка к интервью, поиск работы + +![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 (в linkedin: https://www.linkedin.com/company/java-online-projects). +- После завершения проекта ты освошь все заявленные в нем технологии - вставь их в квалификацию (включая java 8 Stream and Time API). +- В разделе опыт работы (если нет коммерческого опыта) вставь: + + Участие в разработке Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей + на стеке Maven/ Spring MVC/ Security/ REST(Jackson)/ Java 8 Stream API: + - реализация сохранения в базы Postgres и HSQLDB на основе Spring JBDC, JPA(Hibernate) и Spring-Data-JPA + - реализация и тестирование REST и AJAX контроллеров + - реализация клиента на Bootstrap (css/js), datatables, jQuery + plugins. + - собственная доработка проекта + +- Делай упор не на обучение, а на **участие в проекте**. Выполнение домашних заданий это полноценное участие с написанием функционала по всем пройденным технологиям. На собеседовании смотрят не на то, что ты заканчивал, а на опыт и знания. + +### В процессе обучения +- Если рассмотриваешь предложения по работе, подними в своем профиле этот флаг и обязательно заполни ссылку на резюме. Обновления нашей базы выпускников смотрят уже более 125 партнеров по трудоустройству (компании и индивидуальные рекрутеры). Проверь содержание "Информация для HR": по нему принимают решение, открывать резюме или нет. + +- Вступайте в нашу группу участников Slack: каналы помощи с Java, отзывы о работодателях, обсуждение тестовых заданий, вакансии, цены на рынке труда, IT события, интересные видео и многое другое. + +- Подпишитесь на рассылку вакансий под себя + +### После прохождения испытательного срока жду твою [историю успеха](http://javaops.ru/view/story) + +### Основные сайты поиска работы: +- Яндекс агрегатор +- HH +- LinkedIn +- djinni.co (более актуально для Украины) + +## Как выжить на испытательном сроке +- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В + процессе формулирования очень часто приходит ее решение. +- Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки + разработчика. В логах надо читать верх самого нижнего эксепшена - там причина всей портянки. +- Грамотно уделяй время каждой проблеме. Две крайности - сразу бросаться за помощью и + бится нам ней часами. + Пробуй решить ее сам и в зависимости от проблемы выделяй на это разумное время. +- Если тебе что-то объясняют по проекту - обязательно записывай. +- Когда получаешь задачу - уточни все очень подробно. +- Получай в процессе решения обратную связь - в том ли направлении ты идешь. +- Не игнорируй совместные ланчи (курилки) +- Готовься к стендапам/летучкам. Задавай на них вменяемые вопросы. Выказывай заинтересованность +- Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи. +- [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) + +## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246) diff --git a/description.md b/description.md new file mode 100644 index 000000000000..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/graduation.md b/graduation.md new file mode 100644 index 000000000000..8dc5a19817dd --- /dev/null +++ b/graduation.md @@ -0,0 +1,66 @@ +## Тестовое задание на оплачиваемую стажировку + +Design and implement a REST API using Hibernate/Spring/SpringMVC (or Spring-Boot) **without frontend**. + +The task is: + +Build a voting system for deciding where to have lunch. + + * 2 types of users: admin and regular users + * Admin can input a restaurant and it's lunch menu of the day (2-5 items usually, just a dish name and price) + * Menu changes each day (admins do the updates) + * Users can vote on which restaurant they want to have lunch at + * Only one vote counted per user + * If user votes again the same day: + - If it is before 11:00 we asume that he changed his mind. + - If it is after 11:00 then it is too late, vote can't be changed + +Each restaurant provides new menu each day. + +As a result, provide a link to github repository. It should contain the code, README.md with API documentation and couple curl commands to test it. + +----------------------------- +P.S.: Make sure everything works with latest version that is on github :) + +P.P.S.: Asume that your API will be used by a frontend developer to build frontend on top of that. + +----------------------------- +### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации + +- **Если ты оканчивал [стажировку Topjava](http://javaops.ru/reg/topjava/grd), cделай новый проект и добавляй туда из Topjava только то что нужно! Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** +- **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)** +- **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** + +*Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас конечно есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если конечно в ТЗ нет оговорок). Все свои вопросы и предложения и хотелки оформляйете отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например справочник еды)- объязательно напишите в read.me. Как и выбор стратегии кэширования.* + +> Совершенство достигнуто не тогда, когда нечего добавить, а тогда, когда нечего отнять + +_Антуан де Сент-Экзюпери_ + +- 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** +- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу** +- 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** +- 4: **учитывайте, что пользователей может быть ооочень много, а админов- мало** +- 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: + - 5.1 НЕ надо делать разные профили базы и работы с ней. + - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. + - 5.3 НЕ надо делать классов репозиториев, если там нет ничего, кроме делегирования. + - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. + - 5.5 НЕ надо все бездумно кэшировать +- 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). В случае JPA позаботьтесь о своем собственном generic DAO. +- 17: На topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. +- 18: Проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. +- 19: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! +- 20: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). С ним можно убрать из `AuthorizedUser` все статические методы. From 424fa54cd8d4aee0314524a35cf690dc557ae456 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 17 May 2018 12:14:22 +0300 Subject: [PATCH 02/44] Update ReleaseNotes.md --- ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index da7230a1bf32..bef0b97e924a 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,6 +1,7 @@ # TopJava Release Notes ### 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) From d36c80e5616e83a0f37d0b0dca9c5c79947145f2 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 17 May 2018 12:14:34 +0300 Subject: [PATCH 03/44] Update ReleaseNotes.md --- ReleaseNotes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index bef0b97e924a..7ee52244a5d3 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,7 +1,7 @@ # TopJava Release Notes ### Topjava 13 - [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) -> - Добавил [Responsive behaviors](https://getbootstrap.com/docs/4.1/components/navbar/#responsive-behaviors) - при уменшении ширины экрана навигация сворачивается в кнопку +- Добавил [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) From 87bfbf2a0ce16f948d26584fa330d6d4c0dd73f7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 24 May 2018 00:49:54 +0300 Subject: [PATCH 04/44] Update graduation.md --- graduation.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/graduation.md b/graduation.md index 8dc5a19817dd..2dd23a95b6fb 100644 --- a/graduation.md +++ b/graduation.md @@ -46,7 +46,6 @@ _Антуан де Сент-Экзюпери_ - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. - 5.3 НЕ надо делать классов репозиториев, если там нет ничего, кроме делегирования. - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. - - 5.5 НЕ надо все бездумно кэшировать - 6: базу лучше взять без установки (H2 или HSQLDB) - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок @@ -59,8 +58,11 @@ _Антуан де Сент-Экзюпери_ - 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). В случае JPA позаботьтесь о своем собственном generic DAO. -- 17: На topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. -- 18: Проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. -- 19: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! -- 20: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). С ним можно убрать из `AuthorizedUser` все статические методы. +- 16: *Предпочтительно использовать DATA-JPA* (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository). В случае JPA позаботьтесь о своем собственном generic DAO. +- 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). С ним можно убрать из `AuthorizedUser` все статические методы. +- 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB +- 23: Если используете кэширование, тщательно продумайте, что надо кэшировать (самые частые запросы), а что нет (большие или редкозапрашиваемые данные)! From 9385560a7b53f929fcac01484ebffc2d8c2db89b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 24 May 2018 16:02:53 +0300 Subject: [PATCH 05/44] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 2dd23a95b6fb..c2b0bcb1d3b8 100644 --- a/graduation.md +++ b/graduation.md @@ -44,7 +44,7 @@ _Антуан де Сент-Экзюпери_ - 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: - 5.1 НЕ надо делать разные профили базы и работы с ней. - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. - - 5.3 НЕ надо делать классов репозиториев, если там нет ничего, кроме делегирования. + - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования. - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. - 6: базу лучше взять без установки (H2 или HSQLDB) - 7: по возможности сделать JUnit тесты From f713db0b7dc6a229c57940c779a368d1d29f4b37 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 6 Jun 2018 21:25:39 +0300 Subject: [PATCH 06/44] Update ReleaseNotes.md --- ReleaseNotes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 7ee52244a5d3..2e8fc212c740 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -3,7 +3,7 @@ - [Миграция на 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) +- В `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.x, Spring Data 2.x, Ehcache 3.x, datatables, datetimepicker From 937be5ba6acfaf06f755c8dff0cf3ea55a1d4d60 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 2 Jul 2018 14:58:47 +0300 Subject: [PATCH 07/44] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 087341d555c9..073ddf2a0e09 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Java Enterprise Online Project ### Требования к участникам, Wiki ### Составление резюме, подготовка к интервью, поиск работы -Вводное занятие +Вводное занятие (обязательно смотреть все видео) =============== ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Осваиваем Java Enterprise. Трудоустройство. Ответы на вопросы. - Слайды презентации @@ -105,7 +105,7 @@ Java Enterprise Online Project ``` Сделать реализацию со сложностью O(N): - циклом за 1 проход по List. Обратите внимание на п.13 замечаний -- через Stream API за 1 проход по Stream +- через Stream API за 1 проход по полному списку Stream ``` #### Замечания по использованию Stream API: - Когда встречаешь что-то непривычное, приходится перестраивать мозги. Например, переход с процедурного на ООП программирование дается непросто. Те, кто не знает шаблонов (и не хотят учить) также их встречают плохо. Хорошая новость в том, что если это принять и начать использовать, то начинаешь получать от этого удовольствие. И тут главное не впасть в другую крайность: From 07aaf06b50e912612fe2dd880acc34ae4b7ca654 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 14:14:50 +0300 Subject: [PATCH 08/44] 2 4 add spring context --- .../ru/javawebinar/topjava/SpringMain.java | 20 ++++++++ .../mock/InMemoryMealRepositoryImpl.java | 46 +++++++++++++++++++ .../mock/MockUserRepositoryImpl.java | 45 ++++++++++++++++++ src/main/resources/spring/spring-app.xml | 24 ++++++++++ 4 files changed, 135 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java create mode 100644 src/main/resources/spring/spring-app.xml diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java new file mode 100644 index 000000000000..6000def7f1c8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Arrays; + +public class SpringMain { + public static void main(String[] args) { + // java 7 Automatic resource management + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); + } + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java new file mode 100644 index 000000000000..21caea61c151 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -0,0 +1,46 @@ +package ru.javawebinar.topjava.repository.mock; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +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 void delete(int id) { + repository.remove(id); + } + + @Override + public Meal get(int id) { + return repository.get(id); + } + + @Override + public Collection getAll() { + return repository.values(); + } +} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java new file mode 100644 index 000000000000..3825d9a48654 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Collections; +import java.util.List; + +@Repository +public class MockUserRepositoryImpl implements UserRepository { + private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); + + @Override + public boolean delete(int id) { + log.info("delete {}", id); + return true; + } + + @Override + public User save(User user) { + log.info("save {}", user); + return user; + } + + @Override + public User get(int id) { + log.info("get {}", id); + return null; + } + + @Override + public List getAll() { + log.info("getAll"); + return Collections.emptyList(); + } + + @Override + public User getByEmail(String email) { + log.info("getByEmail {}", email); + return null; + } +} diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..306726024f3c --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + \ No newline at end of file From 50f0cfc25ecfe5e14b6235f053279c476b002d77 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 14:18:34 +0300 Subject: [PATCH 09/44] 2 4 add spring context --- 1_0_fix.patch | 119 +++++++ 1_1_HW0_stream.patch | 116 +++++++ 1_2_HW0_cycle.patch | 57 ++++ 1_3_HW0_optional2.patch | 90 +++++ 1_4_switch_to_war.patch | 83 +++++ 1_5_add_servlet_api.patch | 45 +++ 1_6_forward_to_redirect.patch | 32 ++ 1_7_logging.patch | 113 +++++++ 2_1_HW1.patch | 406 +++++++++++++++++++++++ 2_2_HW1_optional.patch | 398 ++++++++++++++++++++++ 2_3_app_layers.patch | 569 ++++++++++++++++++++++++++++++++ 2_4_add_spring_context.patch | 167 ++++++++++ 2_5_dependency_injection.patch | 63 ++++ 2_6_annotation_processing.patch | 191 +++++++++++ 2_7_constructor_injection.patch | 44 +++ Prepare_to_HW0.patch | 158 +++++++++ 16 files changed, 2651 insertions(+) create mode 100644 1_0_fix.patch create mode 100644 1_1_HW0_stream.patch create mode 100644 1_2_HW0_cycle.patch create mode 100644 1_3_HW0_optional2.patch create mode 100644 1_4_switch_to_war.patch create mode 100644 1_5_add_servlet_api.patch create mode 100644 1_6_forward_to_redirect.patch create mode 100644 1_7_logging.patch create mode 100644 2_1_HW1.patch create mode 100644 2_2_HW1_optional.patch create mode 100644 2_3_app_layers.patch create mode 100644 2_4_add_spring_context.patch create mode 100644 2_5_dependency_injection.patch create mode 100644 2_6_annotation_processing.patch create mode 100644 2_7_constructor_injection.patch create mode 100644 Prepare_to_HW0.patch diff --git a/1_0_fix.patch b/1_0_fix.patch new file mode 100644 index 000000000000..7c2d636c0f61 --- /dev/null +++ b/1_0_fix.patch @@ -0,0 +1,119 @@ +Index: src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) ++++ src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) +@@ -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 1530137160353) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530137160353) +@@ -0,0 +1,31 @@ ++package ru.javawebinar.topjava.util; ++ ++import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.model.MealWithExceed; ++ ++import java.time.LocalDateTime; ++import java.time.LocalTime; ++import java.time.Month; ++import java.util.Arrays; ++import java.util.List; ++ ++public class MealsUtil { ++ public static void main(String[] args) { ++ List mealList = Arrays.asList( ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) ++ ); ++ getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); ++// .toLocalDate(); ++// .toLocalTime(); ++ } ++ ++ public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ // TODO return filtered list with correctly exceeded field ++ return null; ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530137160292) +@@ -2,7 +2,7 @@ + + import java.time.LocalDateTime; + +-public class UserMealWithExceed { ++public class MealWithExceed { + private final LocalDateTime dateTime; + + private final String description; +@@ -11,7 +11,7 @@ + + private final boolean exceed; + +- public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { ++ public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; +Index: src/main/java/ru/javawebinar/topjava/model/UserMeal.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMeal.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1530137142426) +@@ -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; diff --git a/1_1_HW0_stream.patch b/1_1_HW0_stream.patch new file mode 100644 index 000000000000..4c11a4be2034 --- /dev/null +++ b/1_1_HW0_stream.patch @@ -0,0 +1,116 @@ +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 1530137394000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530137413670) +@@ -3,29 +3,41 @@ + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.model.MealWithExceed; + ++import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.LocalTime; + import java.time.Month; + import java.util.Arrays; + import java.util.List; ++import java.util.Map; ++import java.util.stream.Collectors; + + public class MealsUtil { + public static void main(String[] args) { +- List mealList = Arrays.asList( +- new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), +- new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), +- new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), +- new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), +- new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), +- new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) ++ List meals = Arrays.asList( ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) + ); +- getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +-// .toLocalDate(); +-// .toLocalTime(); ++ List mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); ++ mealsWithExceeded.forEach(System.out::println); + } + +- public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- // TODO return filtered list with correctly exceeded field +- return null; ++ public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ Map caloriesSumByDate = meals.stream() ++ .collect( ++ Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) ++// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) ++ ); ++ ++ return meals.stream() ++ .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .map(meal -> ++ new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), ++ caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) ++ .collect(Collectors.toList()); + } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530137394000) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530137413609) +@@ -17,4 +17,14 @@ + this.calories = calories; + this.exceed = exceed; + } +-} ++ ++ @Override ++ public String toString() { ++ return "UserMealWithExceed{" + ++ "dateTime=" + dateTime + ++ ", description='" + description + '\'' + ++ ", calories=" + calories + ++ ", exceed=" + exceed + ++ '}'; ++ } ++} +\ No newline at end of file +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 1530137394000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1530137413502) +@@ -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(); ++ } + } diff --git a/1_2_HW0_cycle.patch b/1_2_HW0_cycle.patch new file mode 100644 index 000000000000..4ecbb830ff9e --- /dev/null +++ b/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 1499897832000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (revision ) +@@ -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 mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExceeded.forEach(System.out::println); ++ ++ System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -35,9 +35,25 @@ + + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) +- .map(meal -> +- new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), +- caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) ++ .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } ++ ++ public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ ++ final Map caloriesSumByDate = new HashMap<>(); ++ meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); ++ ++ final List mealsWithExceeded = new ArrayList<>(); ++ meals.forEach(meal -> { ++ if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { ++ mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); ++ } ++ }); ++ return mealsWithExceeded; ++ } ++ ++ public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { ++ return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); ++ } + } +\ No newline at end of file diff --git a/1_3_HW0_optional2.patch b/1_3_HW0_optional2.patch new file mode 100644 index 000000000000..26dec67cb4e9 --- /dev/null +++ b/1_3_HW0_optional2.patch @@ -0,0 +1,90 @@ +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 1509555460000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (revision ) +@@ -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 @@ + mealsWithExceeded.forEach(System.out::println); + + System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -36,7 +43,7 @@ + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) +- .collect(Collectors.toList()); ++ .collect(toList()); + } + + public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -53,6 +60,51 @@ + return mealsWithExceeded; + } + ++ public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ Collection> list = meals.stream() ++ .collect(Collectors.groupingBy(Meal::getDate)).values(); ++ ++ return list.stream().flatMap(dayMeals -> { ++ boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; ++ return dayMeals.stream().filter(meal -> ++ TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .map(meal -> createWithExceed(meal, exceed)); ++ }).collect(toList()); ++ } ++ ++ public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ final class Aggregate { ++ private final List dailyMeals = new ArrayList<>(); ++ private int dailySumOfCalories; ++ ++ private void accumulate(Meal meal) { ++ dailySumOfCalories += meal.getCalories(); ++ if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { ++ dailyMeals.add(meal); ++ } ++ } ++ ++ // never invoked if the upstream is sequential ++ private Aggregate combine(Aggregate that) { ++ this.dailySumOfCalories += that.dailySumOfCalories; ++ this.dailyMeals.addAll(that.dailyMeals); ++ return this; ++ } ++ ++ private Stream finisher() { ++ final boolean exceed = dailySumOfCalories > caloriesPerDay; ++ return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); ++ } ++ } ++ ++ Collection> values = meals.stream() ++ .collect(Collectors.groupingBy(Meal::getDate, ++ Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) ++ ).values(); ++ ++ return values.stream().flatMap(identity()).collect(toList()); ++ } ++ + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } diff --git a/1_4_switch_to_war.patch b/1_4_switch_to_war.patch new file mode 100644 index 000000000000..ed7403bcf79b --- /dev/null +++ b/1_4_switch_to_war.patch @@ -0,0 +1,83 @@ +Index: src/main/webapp/users.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/users.jsp (revision ) ++++ src/main/webapp/users.jsp (revision ) +@@ -0,0 +1,10 @@ ++<%@ 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 (revision ) ++++ src/main/webapp/index.html (revision ) +@@ -0,0 +1,13 @@ ++ ++ ++ ++ Java Enterprise (Topjava) ++ ++ ++

Проект Java Enterprise (Topjava)

++
++ ++ ++ +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 ) ++++ src/main/webapp/WEB-INF/web.xml (revision ) +@@ -0,0 +1,19 @@ ++ ++ ++ Topjava ++ ++ ++ userServlet ++ ru.javawebinar.topjava.web.UserServlet ++ 0 ++ ++ ++ userServlet ++ /users ++ ++ ++ +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1499898097000) ++++ pom.xml (revision ) +@@ -4,7 +4,7 @@ + + ru.javawebinar + topjava +- jar ++ war + + 1.0-SNAPSHOT + diff --git a/1_5_add_servlet_api.patch b/1_5_add_servlet_api.patch new file mode 100644 index 000000000000..9bd9d2658d69 --- /dev/null +++ b/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 (revision ) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (revision ) +@@ -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 1499898685000) ++++ pom.xml (revision ) +@@ -34,6 +34,14 @@ + + + ++ ++ ++ ++ javax.servlet ++ javax.servlet-api ++ 3.1.0 ++ provided ++ + + + diff --git a/1_6_forward_to_redirect.patch b/1_6_forward_to_redirect.patch new file mode 100644 index 000000000000..35fc459218a3 --- /dev/null +++ b/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/1_7_logging.patch b/1_7_logging.patch new file mode 100644 index 000000000000..72fad180b032 --- /dev/null +++ b/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/2_1_HW1.patch b/2_1_HW1.patch new file mode 100644 index 000000000000..e5b49a9fa53d --- /dev/null +++ b/2_1_HW1.patch @@ -0,0 +1,406 @@ +Index: src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) +@@ -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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/Main.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) +@@ -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/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 1530650966615) ++++ src/main/webapp/WEB-INF/tld/functions.tld (date 1530650966615) +@@ -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: 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 1530650926186) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1530650926186) +@@ -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/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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1530650966609) +@@ -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/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 1530650966602) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1530650966602) +@@ -0,0 +1,22 @@ ++package ru.javawebinar.topjava.web; ++ ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import ru.javawebinar.topjava.util.MealsUtil; ++ ++import javax.servlet.ServletException; ++import javax.servlet.http.HttpServlet; ++import javax.servlet.http.HttpServletRequest; ++import javax.servlet.http.HttpServletResponse; ++import java.io.IOException; ++ ++public class MealServlet extends HttpServlet { ++ private static final Logger log = LoggerFactory.getLogger(MealServlet.class); ++ ++ @Override ++ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ++ log.info("getAll"); ++ request.setAttribute("meals", MealsUtil.getWithExceeded(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530650926177) +@@ -18,9 +18,25 @@ + this.exceed = exceed; + } + ++ public LocalDateTime getDateTime() { ++ return dateTime; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ ++ public int getCalories() { ++ return calories; ++ } ++ ++ public boolean isExceed() { ++ return exceed; ++ } ++ + @Override + public String toString() { +- return "UserMealWithExceed{" + ++ return "MealWithExceed{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ pom.xml (date 1530650966647) +@@ -67,6 +67,12 @@ + 3.1.0 + 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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530651123956) +@@ -8,32 +8,32 @@ + import java.time.LocalTime; + import java.time.Month; + import java.util.*; +-import java.util.stream.Collector; ++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 mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); +- mealsWithExceeded.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(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); +- System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); +- System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ public static List getWithExceeded(List meals, int caloriesPerDay) { ++ return getFilteredWithExceeded(meals, caloriesPerDay, meal -> true); + } + +- public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ public static List getFilteredWithExceeded(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { ++ return getFilteredWithExceeded(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); ++ } ++ ++ private static List getFilteredWithExceeded(List meals, int caloriesPerDay, Predicate filter) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +@@ -41,70 +41,11 @@ + ); + + return meals.stream() +- .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .filter(filter) + .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(toList()); + } + +- public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- +- final Map caloriesSumByDate = new HashMap<>(); +- meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); +- +- final List mealsWithExceeded = new ArrayList<>(); +- meals.forEach(meal -> { +- if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { +- mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); +- } +- }); +- return mealsWithExceeded; +- } +- +- public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- Collection> list = meals.stream() +- .collect(Collectors.groupingBy(Meal::getDate)).values(); +- +- return list.stream().flatMap(dayMeals -> { +- boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; +- return dayMeals.stream().filter(meal -> +- TimeUtil.isBetween(meal.getTime(), startTime, endTime)) +- .map(meal -> createWithExceed(meal, exceed)); +- }).collect(toList()); +- } +- +- public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- final class Aggregate { +- private final List dailyMeals = new ArrayList<>(); +- private int dailySumOfCalories; +- +- private void accumulate(Meal meal) { +- dailySumOfCalories += meal.getCalories(); +- if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { +- dailyMeals.add(meal); +- } +- } +- +- // never invoked if the upstream is sequential +- private Aggregate combine(Aggregate that) { +- this.dailySumOfCalories += that.dailySumOfCalories; +- this.dailyMeals.addAll(that.dailyMeals); +- return this; +- } +- +- private Stream finisher() { +- final boolean exceed = dailySumOfCalories > caloriesPerDay; +- return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); +- } +- } +- +- Collection> values = meals.stream() +- .collect(Collectors.groupingBy(Meal::getDate, +- Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) +- ).values(); +- +- return values.stream().flatMap(identity()).collect(toList()); +- } +- + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } +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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/webapp/WEB-INF/web.xml (date 1530650966622) +@@ -16,4 +16,14 @@ + /users + + ++ ++ mealServlet ++ ru.javawebinar.topjava.web.MealServlet ++ 0 ++ ++ ++ mealServlet ++ /meals ++ ++ + +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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/webapp/index.html (date 1530650966629) +@@ -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 1530650966639) ++++ src/main/webapp/meals.jsp (date 1530650966639) +@@ -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 diff --git a/2_2_HW1_optional.patch b/2_2_HW1_optional.patch new file mode 100644 index 000000000000..4a168766c9e3 --- /dev/null +++ b/2_2_HW1_optional.patch @@ -0,0 +1,398 @@ +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 1530651650797) ++++ src/main/webapp/mealForm.jsp (date 1530651650797) +@@ -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/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 1530651607835) ++++ src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1530651607835) +@@ -0,0 +1,15 @@ ++package ru.javawebinar.topjava.repository; ++ ++import ru.javawebinar.topjava.model.Meal; ++ ++import java.util.Collection; ++ ++public interface MealRepository { ++ Meal save(Meal meal); ++ ++ void delete(int id); ++ ++ Meal get(int id); ++ ++ Collection getAll(); ++} +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 1530651607825) ++++ src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java (date 1530651607825) +@@ -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 void delete(int id) { ++ repository.remove(id); ++ } ++ ++ @Override ++ public Meal get(int id) { ++ return repository.get(id); ++ } ++ ++ @Override ++ public Collection getAll() { ++ return repository.values(); ++ } ++} ++ +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 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1530651650782) +@@ -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.getWithExceeded(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); +- request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ String action = request.getParameter("action"); ++ ++ switch (action == null ? "all" : action) { ++ case "delete": ++ int id = getId(request); ++ log.info("Delete {}", id); ++ repository.delete(id); ++ response.sendRedirect("meals"); ++ break; ++ case "create": ++ case "update": ++ final Meal meal = "create".equals(action) ? ++ new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : ++ repository.get(getId(request)); ++ request.setAttribute("meal", meal); ++ request.getRequestDispatcher("/mealForm.jsp").forward(request, response); ++ break; ++ case "all": ++ default: ++ log.info("getAll"); ++ request.setAttribute("meals", ++ MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ break; ++ } ++ } ++ ++ private int getId(HttpServletRequest request) { ++ String paramId = Objects.requireNonNull(request.getParameter("id")); ++ return Integer.parseInt(paramId); + } + } +Index: src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530651607815) +@@ -3,6 +3,8 @@ + import java.time.LocalDateTime; + + public class MealWithExceed { ++ private final Integer id; ++ + private final LocalDateTime dateTime; + + private final String description; +@@ -11,13 +13,18 @@ + + private final boolean exceed; + +- public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { ++ public MealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { ++ this.id = id; + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.exceed = exceed; + } + ++ public Integer getId() { ++ return id; ++ } ++ + public LocalDateTime getDateTime() { + return dateTime; + } +@@ -37,7 +44,8 @@ + @Override + public String toString() { + return "MealWithExceed{" + +- "dateTime=" + dateTime + ++ "id=" + id + ++ ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + +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 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1530651607805) +@@ -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/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 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530651650741) +@@ -25,15 +25,15 @@ + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + +- public static List getWithExceeded(List meals, int caloriesPerDay) { ++ public static List getWithExceeded(Collection meals, int caloriesPerDay) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> true); + } + +- public static List getFilteredWithExceeded(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { ++ public static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); + } + +- private static List getFilteredWithExceeded(List meals, int caloriesPerDay, Predicate filter) { ++ private static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, Predicate filter) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +@@ -47,6 +47,6 @@ + } + + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { +- return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); ++ return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } + } +\ No newline at end of file +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 1530651578000) ++++ src/main/webapp/meals.jsp (date 1530651650811) +@@ -20,6 +20,7 @@ +
+

Home

+

Meals

++ Add Meal +
+ + +@@ -27,6 +28,8 @@ + + + ++ ++ + + + +@@ -40,6 +43,8 @@ + + + ++ ++ + + +
DateDescriptionCalories
${meal.description}${meal.calories}UpdateDelete
diff --git a/2_3_app_layers.patch b/2_3_app_layers.patch new file mode 100644 index 000000000000..8e878d713bf9 --- /dev/null +++ b/2_3_app_layers.patch @@ -0,0 +1,569 @@ +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 1530739373367) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1530739373367) +@@ -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/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 1530739373368) ++++ src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java (date 1530739373368) +@@ -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/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 1530739373370) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1530739373370) +@@ -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/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 1530739373423) ++++ src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java (date 1530739373423) +@@ -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 1530741235278) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1530741235278) +@@ -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/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 1530739482294) ++++ src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1530739482294) +@@ -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/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 1530741235263) ++++ src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1530741235263) +@@ -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/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 1530740790618) ++++ src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1530740790618) +@@ -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/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 1530739373358) ++++ src/main/java/ru/javawebinar/topjava/model/Role.java (date 1530739373358) +@@ -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/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 1530740790635) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1530740790635) +@@ -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/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 1530739373354) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1530739373354) +@@ -0,0 +1,24 @@ ++package ru.javawebinar.topjava.model; ++ ++public abstract class AbstractNamedEntity extends AbstractBaseEntity { ++ ++ protected String name; ++ ++ protected AbstractNamedEntity(Integer id, String name) { ++ super(id); ++ this.name = name; ++ } ++ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ public String getName() { ++ return this.name; ++ } ++ ++ @Override ++ public String toString() { ++ return String.format("Entity %s (%s, '%s')", getClass().getName(), id, name); ++ } ++} +\ No newline at end of file +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 1530739373365) ++++ src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (date 1530739373365) +@@ -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/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 1530739482288) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530739482288) +@@ -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) { ++ checkNotFoundWithId(repository.save(user), user.getId()); ++ } ++} +\ 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 1530739373421) ++++ src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java (date 1530739373421) +@@ -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/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 1530740790656) ++++ src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java (date 1530740790656) +@@ -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) { ++// 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/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 1530739373352) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1530739373352) +@@ -0,0 +1,26 @@ ++package ru.javawebinar.topjava.model; ++ ++public abstract class AbstractBaseEntity { ++ protected Integer id; ++ ++ protected AbstractBaseEntity(Integer id) { ++ this.id = id; ++ } ++ ++ public void setId(Integer id) { ++ this.id = id; ++ } ++ ++ public Integer getId() { ++ return id; ++ } ++ ++ public boolean isNew() { ++ return this.id == null; ++ } ++ ++ @Override ++ public String toString() { ++ return String.format("Entity %s (%s)", getClass().getName(), id); ++ } ++} +\ No newline at end of file diff --git a/2_4_add_spring_context.patch b/2_4_add_spring_context.patch new file mode 100644 index 000000000000..2283c901708e --- /dev/null +++ b/2_4_add_spring_context.patch @@ -0,0 +1,167 @@ +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 1530652518211) ++++ src/main/resources/spring/spring-app.xml (date 1530652518211) +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530652518187) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530652518187) +@@ -0,0 +1,43 @@ ++package ru.javawebinar.topjava.repository.mock; ++ ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.UserRepository; ++ ++import java.util.Collections; ++import java.util.List; ++ ++public class MockUserRepositoryImpl implements UserRepository { ++ private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); ++ ++ @Override ++ public boolean delete(int id) { ++ log.info("delete {}", id); ++ return true; ++ } ++ ++ @Override ++ public User save(User user) { ++ log.info("save {}", user); ++ return user; ++ } ++ ++ @Override ++ public User get(int id) { ++ log.info("get {}", id); ++ return null; ++ } ++ ++ @Override ++ public List getAll() { ++ log.info("getAll"); ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public User getByEmail(String email) { ++ log.info("getByEmail {}", email); ++ return null; ++ } ++} +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 1530652518204) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1530652518204) +@@ -0,0 +1,19 @@ ++package ru.javawebinar.topjava; ++ ++import org.springframework.context.ConfigurableApplicationContext; ++import org.springframework.context.support.ClassPathXmlApplicationContext; ++import ru.javawebinar.topjava.repository.UserRepository; ++ ++import java.util.Arrays; ++ ++public class SpringMain { ++ public static void main(String[] args) { ++ ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); ++ System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); ++ ++// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); ++ UserRepository userRepository = appCtx.getBean(UserRepository.class); ++ userRepository.getAll(); ++ appCtx.close(); ++ } ++} +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 1530652485000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1530652518196) +@@ -3,8 +3,8 @@ + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import ru.javawebinar.topjava.model.Meal; +-import ru.javawebinar.topjava.repository.InMemoryMealRepositoryImpl; + import ru.javawebinar.topjava.repository.MealRepository; ++import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; + import ru.javawebinar.topjava.util.MealsUtil; + + import javax.servlet.ServletConfig; +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1530652485000) ++++ pom.xml (date 1530652892544) +@@ -16,6 +16,8 @@ + UTF-8 + UTF-8 + ++ 5.0.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} ++ + + + +Index: src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java (date 1530652485000) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1530652518136) +@@ -1,6 +1,7 @@ +-package ru.javawebinar.topjava.repository; ++package ru.javawebinar.topjava.repository.mock; + + import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.repository.MealRepository; + import ru.javawebinar.topjava.util.MealsUtil; + + import java.util.Collection; diff --git a/2_5_dependency_injection.patch b/2_5_dependency_injection.patch new file mode 100644 index 000000000000..46ea4d91f4bc --- /dev/null +++ b/2_5_dependency_injection.patch @@ -0,0 +1,63 @@ +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 1530652947000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530653294435) +@@ -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); +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 1530652947000) ++++ src/main/resources/spring/spring-app.xml (date 1530653294440) +@@ -4,4 +4,7 @@ + + + ++ ++ ++ + +\ 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 1530652947000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1530653316991) +@@ -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("mockUserRepository"); + 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(); + } + } diff --git a/2_6_annotation_processing.patch b/2_6_annotation_processing.patch new file mode 100644 index 000000000000..cf7228292ac3 --- /dev/null +++ b/2_6_annotation_processing.patch @@ -0,0 +1,191 @@ +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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530741375310) +@@ -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/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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1530741375333) +@@ -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/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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1530741391339) +@@ -4,23 +4,17 @@ + import org.springframework.context.support.ClassPathXmlApplicationContext; + import ru.javawebinar.topjava.model.Role; + import ru.javawebinar.topjava.model.User; +-import ru.javawebinar.topjava.repository.UserRepository; +-import ru.javawebinar.topjava.service.UserService; ++import ru.javawebinar.topjava.web.user.AdminRestController; + + import java.util.Arrays; + + public class SpringMain { + public static void main(String[] args) { +- ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); +- System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); +- +-// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); +- UserRepository userRepository = appCtx.getBean(UserRepository.class); +- userRepository.getAll(); +- +- UserService userService = appCtx.getBean(UserService.class); +- userService.create(new User(null, "userName", "email@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/mock/MockUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530741375289) +@@ -2,12 +2,14 @@ + + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; ++import org.springframework.stereotype.Repository; + import ru.javawebinar.topjava.model.User; + import ru.javawebinar.topjava.repository.UserRepository; + + import java.util.Collections; + import java.util.List; + ++@Repository + public class MockUserRepositoryImpl implements UserRepository { + private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); + +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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1530741391288) +@@ -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/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 1530741372000) ++++ src/main/resources/spring/spring-app.xml (date 1530741391353) +@@ -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/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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1530741375322) +@@ -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/2_7_constructor_injection.patch b/2_7_constructor_injection.patch new file mode 100644 index 000000000000..14757e127642 --- /dev/null +++ b/2_7_constructor_injection.patch @@ -0,0 +1,44 @@ +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 1530653665000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530653786000) +@@ -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) { +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 1530653665000) ++++ src/main/resources/spring/spring-app.xml (date 1530653786000) +@@ -5,11 +5,11 @@ + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + + + diff --git a/Prepare_to_HW0.patch b/Prepare_to_HW0.patch new file mode 100644 index 000000000000..c62d68e2e01d --- /dev/null +++ b/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} From 3fd5a2d74c2c1c4a466a8f0d57471263153b4e2b Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 14:40:09 +0300 Subject: [PATCH 10/44] 2 4 add spring context --- src/main/java/ru/javawebinar/topjava/SpringMain.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 6000def7f1c8..5e7cd6096f5c 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -11,6 +11,7 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management + //сщььше try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); From 712a39affe9d3c8503b3638ea9a9b8a52e67aac9 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 15:06:21 +0300 Subject: [PATCH 11/44] 1 0 fix --- .../ru/javawebinar/topjava/model/Meal.java | 68 +++++++++++++++++++ .../topjava/model/MealWithExceed.java | 54 +++++++++++++++ .../javawebinar/topjava/util/MealsUtil.java | 52 ++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/Meal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java 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..3abbee42511e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -0,0 +1,68 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +public class Meal { + private Integer id; + + private final LocalDateTime dateTime; + + private final String description; + + 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; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } + + public boolean isNew() { + return id == null; + } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java new file mode 100644 index 000000000000..2b375e45eecc --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -0,0 +1,54 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class MealWithExceed { + private final Integer id; + + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private final boolean exceed; + + public MealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.id = id; + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.exceed = exceed; + } + + public Integer getId() { + return id; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExceed() { + return exceed; + } + + @Override + public String toString() { + return "MealWithExceed{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java new file mode 100644 index 000000000000..46112186ed89 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,52 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealWithExceed; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +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; + + public static List getWithExceeded(Collection meals, int caloriesPerDay) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> true); + } + + public static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); + } + + private static List getFilteredWithExceeded(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 -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(toList()); + } + + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } +} \ No newline at end of file From 1f7a214c991dc92406155f957fdee6f78ad71a73 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 15:08:29 +0300 Subject: [PATCH 12/44] 2 3 app layers --- .../topjava/model/AbstractBaseEntity.java | 26 ++++++ .../topjava/model/AbstractNamedEntity.java | 24 +++++ .../ru/javawebinar/topjava/model/Role.java | 6 ++ .../ru/javawebinar/topjava/model/User.java | 91 +++++++++++++++++++ .../topjava/repository/UserRepository.java | 20 ++++ .../topjava/service/MealService.java | 4 + .../topjava/service/MealServiceImpl.java | 9 ++ .../topjava/service/UserService.java | 22 +++++ .../topjava/service/UserServiceImpl.java | 53 +++++++++++ .../topjava/util/ValidationUtil.java | 42 +++++++++ .../util/exception/NotFoundException.java | 7 ++ .../javawebinar/topjava/web/SecurityUtil.java | 14 +++ .../topjava/web/meal/MealRestController.java | 8 ++ .../web/user/AbstractUserController.java | 51 +++++++++++ .../topjava/web/user/AdminRestController.java | 40 ++++++++ .../web/user/ProfileRestController.java | 22 +++++ 16 files changed, 439 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/Role.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/User.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java new file mode 100644 index 000000000000..a3d71fcb46ed --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractBaseEntity { + protected Integer id; + + protected AbstractBaseEntity(Integer id) { + this.id = id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public boolean isNew() { + return this.id == null; + } + + @Override + public String toString() { + return String.format("Entity %s (%s)", getClass().getName(), id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java new file mode 100644 index 000000000000..259511dd0b65 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractNamedEntity extends AbstractBaseEntity { + + protected String name; + + protected AbstractNamedEntity(Integer id, String name) { + super(id); + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return String.format("Entity %s (%s, '%s')", getClass().getName(), id, name); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/Role.java b/src/main/java/ru/javawebinar/topjava/model/Role.java new file mode 100644 index 000000000000..84d62071ad9c --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -0,0 +1,6 @@ +package ru.javawebinar.topjava.model; + +public enum Role { + ROLE_USER, + ROLE_ADMIN +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java new file mode 100644 index 000000000000..d88e381945d8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,91 @@ +package ru.javawebinar.topjava.model; + +import java.util.Date; +import java.util.EnumSet; +import java.util.Set; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class User extends AbstractNamedEntity { + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + } + + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + super(id, name); + this.email = email; + this.password = password; + this.caloriesPerDay = caloriesPerDay; + this.enabled = enabled; + this.roles = roles; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getRegistered() { + return registered; + } + + public void setRegistered(Date registered) { + this.registered = registered; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getCaloriesPerDay() { + return caloriesPerDay; + } + + public void setCaloriesPerDay(int caloriesPerDay) { + this.caloriesPerDay = caloriesPerDay; + } + + public boolean isEnabled() { + return enabled; + } + + public Set getRoles() { + return roles; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "User (" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + + ')'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java new file mode 100644 index 000000000000..c37b84d5fd77 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +public interface UserRepository { + User save(User user); + + // false if not found + boolean delete(int id); + + // null if not found + User get(int id); + + // null if not found + User getByEmail(String email); + + List getAll(); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 000000000000..b63fc9b1df00 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,4 @@ +package ru.javawebinar.topjava.service; + +public interface MealService { +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java new file mode 100644 index 000000000000..9017380f392b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.repository.MealRepository; + +public class MealServiceImpl implements MealService { + + private MealRepository repository; + +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java new file mode 100644 index 000000000000..d0cf33e30815 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.service; + + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +public interface UserService { + + User create(User user); + + void delete(int id) throws NotFoundException; + + User get(int id) throws NotFoundException; + + User getByEmail(String email) throws NotFoundException; + + void update(User user); + + List getAll(); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java new file mode 100644 index 000000000000..c651c41d3fb9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -0,0 +1,53 @@ +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) { + checkNotFoundWithId(repository.save(user), user.getId()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java new file mode 100644 index 000000000000..cd0eec397a48 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.util; + + +import ru.javawebinar.topjava.model.AbstractBaseEntity; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +public class ValidationUtil { + + public static 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) { +// 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/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java new file mode 100644 index 000000000000..e78a4b284a9a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -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 diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java new file mode 100644 index 000000000000..ab4e8ea8bb8e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.web.meal; + +import ru.javawebinar.topjava.service.MealService; + +public class MealRestController { + private MealService service; + +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java new file mode 100644 index 000000000000..0000f1c1e02f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,51 @@ +package ru.javawebinar.topjava.web.user; + +import org.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 + private UserService service; + + public List getAll() { + log.info("getAll"); + return service.getAll(); + } + + public User get(int id) { + log.info("get {}", id); + return service.get(id); + } + + public User create(User user) { + log.info("create {}", user); + checkNew(user); + return service.create(user); + } + + public void delete(int id) { + log.info("delete {}", id); + service.delete(id); + } + + public void update(User user, int id) { + log.info("update {} with id={}", user, id); + assureIdConsistent(user, id); + service.update(user); + } + + public User getByMail(String email) { + log.info("getByEmail {}", email); + return service.getByEmail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java new file mode 100644 index 000000000000..b37a8ed6c8a5 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,40 @@ +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 + public List getAll() { + return super.getAll(); + } + + @Override + public User get(int id) { + return super.get(id); + } + + @Override + public User create(User user) { + return super.create(user); + } + + @Override + public void delete(int id) { + super.delete(id); + } + + @Override + public void update(User user, int id) { + super.update(user, id); + } + + @Override + public User getByMail(String email) { + return super.getByMail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java new file mode 100644 index 000000000000..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 From 0c68c255a4ddd07d0a122c3f87194b02a679933f Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 15:08:48 +0300 Subject: [PATCH 13/44] 1 4 switch to war --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 29 +++++++++++++++++++++++++++++ src/main/webapp/index.html | 14 ++++++++++++++ src/main/webapp/users.jsp | 10 ++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index c8a1c78f3b29..6f0e7fd7cf47 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..d2e475517f7e --- /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/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..cd88b335a454 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,14 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

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

Home

+

Users

+ + \ No newline at end of file From a0e75a1bc783da7a39b124355ecef87b76f5e6e2 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 15:08:59 +0300 Subject: [PATCH 14/44] 1 5 add servlet api --- .../javawebinar/topjava/web/UserServlet.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java new file mode 100644 index 000000000000..f6cf12e69976 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,21 @@ +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("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); + } +} From 58802d6422092f9c79732e1546457a47b6e5cba6 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 15:09:10 +0300 Subject: [PATCH 15/44] 1 7 logging --- pom.xml | 45 ++++++++++++++++++++++++++++++++-- src/main/resources/logback.xml | 29 ++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 6f0e7fd7cf47..c95d888b5f4b 100644 --- a/pom.xml +++ b/pom.xml @@ -15,16 +15,22 @@ 1.8 UTF-8 UTF-8 + + 5.0.7.RELEASE + + + 1.2.3 + 1.7.25 topjava - install + package org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.7.0 ${java.version} ${java.version} @@ -34,6 +40,41 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + + + + + org.springframework + spring-context + ${spring.version} + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + javax.servlet + jstl + 1.2 + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..e9b900b26669 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From d840c6a34f8a2466d4c312700c6cdbe2d93bef5e Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 15:09:23 +0300 Subject: [PATCH 16/44] 2 1 HW1 --- .../java/ru/javawebinar/topjava/Main.java | 14 ---- .../topjava/util/DateTimeUtil.java | 17 ++++ .../javawebinar/topjava/web/MealServlet.java | 79 +++++++++++++++++++ src/main/webapp/WEB-INF/tld/functions.tld | 16 ++++ src/main/webapp/meals.jsp | 53 +++++++++++++ 5 files changed, 165 insertions(+), 14 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/meals.jsp diff --git a/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/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java new file mode 100644 index 000000000000..5de28849657a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,17 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; + } + + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java new file mode 100644 index 000000000000..dc509a1061d6 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,79 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; +import ru.javawebinar.topjava.util.MealsUtil; + +import 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 { + String action = request.getParameter("action"); + + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); + log.info("Delete {}", id); + repository.delete(id); + response.sendRedirect("meals"); + break; + case "create": + case "update": + final Meal meal = "create".equals(action) ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : + repository.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); + break; + case "all": + default: + log.info("getAll"); + request.setAttribute("meals", + MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.parseInt(paramId); + } +} 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/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 000000000000..8152fcf68c64 --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,53 @@ +<%@ 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

+ 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 From e04ef7d64bee8394f8db848e4bb8cd147ec6dfb9 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 31 Jul 2018 15:09:35 +0300 Subject: [PATCH 17/44] 2 2 HW1 optional --- .../topjava/repository/MealRepository.java | 15 ++++++ src/main/webapp/mealForm.jsp | 51 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/MealRepository.java create mode 100644 src/main/webapp/mealForm.jsp diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java new file mode 100644 index 000000000000..9602991c2526 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -0,0 +1,15 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; + +import java.util.Collection; +/**/ +public interface MealRepository { + Meal save(Meal meal); + + void delete(int id); + + Meal get(int id); + + Collection getAll(); +} diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp new file mode 100644 index 000000000000..ddc71d3c0a7a --- /dev/null +++ b/src/main/webapp/mealForm.jsp @@ -0,0 +1,51 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
+

Home

+

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

+
+ +
+ +
+
DateTime:
+
+
+
+
Description:
+
+
+
+
Calories:
+
+
+ + +
+
+ + From 7db534a9cf98924d74428d256ba2826cde6e9750 Mon Sep 17 00:00:00 2001 From: Alb Date: Sun, 5 Aug 2018 12:55:03 +0300 Subject: [PATCH 18/44] IC-2016.1.4 + + + \ No newline at end of file From fd0e3570dc1fb48fbad4439c1667abfb1347d96f Mon Sep 17 00:00:00 2001 From: Alb Date: Sun, 5 Aug 2018 13:41:06 +0300 Subject: [PATCH 19/44] IC-2016.1.4 + + + + \ No newline at end of file diff --git a/_windows/laf.xml b/_windows/laf.xml new file mode 100644 index 000000000000..dbf49b40fd2a --- /dev/null +++ b/_windows/laf.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/_windows/path.macros.xml b/_windows/path.macros.xml new file mode 100644 index 000000000000..17f0dd57bcb7 --- /dev/null +++ b/_windows/path.macros.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/debugger.xml b/debugger.xml new file mode 100644 index 000000000000..baa3691a18a1 --- /dev/null +++ b/debugger.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/diff.xml b/diff.xml new file mode 100644 index 000000000000..a38d56a7cde4 --- /dev/null +++ b/diff.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/inspection/Default.xml b/inspection/Default.xml new file mode 100644 index 000000000000..60121f310680 --- /dev/null +++ b/inspection/Default.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file From 67554f3b091cd97cdfdde32eeeec5d35c9b4cae3 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:02:02 +0300 Subject: [PATCH 20/44] 1 0 fix --- .../ru/javawebinar/topjava/model/Meal.java | 39 ++++++ .../topjava/model/MealWithExceed.java | 30 +++++ .../javawebinar/topjava/util/MealsUtil.java | 111 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/Meal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java 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..943ff5cd59fa --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -0,0 +1,39 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +public class Meal { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + public Meal(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; + } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java new file mode 100644 index 000000000000..4751c9e4fd69 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -0,0 +1,30 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class MealWithExceed { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private final boolean exceed; + + public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.exceed = exceed; + } + + @Override + public String toString() { + return "UserMealWithExceed{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java new file mode 100644 index 000000000000..209284416ff6 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,111 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealWithExceed; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.*; +import java.util.stream.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) { + List meals = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) + ); + List mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExceeded.forEach(System.out::println); + + System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) + ); + + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(toList()); + } + + public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + + final Map caloriesSumByDate = new HashMap<>(); + meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); + + final List mealsWithExceeded = new ArrayList<>(); + meals.forEach(meal -> { + if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { + mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + }); + return mealsWithExceeded; + } + + public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Collection> list = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate)).values(); + + return list.stream().flatMap(dayMeals -> { + boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; + return dayMeals.stream().filter(meal -> + TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExceed(meal, exceed)); + }).collect(toList()); + } + + public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final class Aggregate { + private final List dailyMeals = new ArrayList<>(); + private int dailySumOfCalories; + + private void accumulate(Meal meal) { + dailySumOfCalories += meal.getCalories(); + if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { + dailyMeals.add(meal); + } + } + + // never invoked if the upstream is sequential + private Aggregate combine(Aggregate that) { + this.dailySumOfCalories += that.dailySumOfCalories; + this.dailyMeals.addAll(that.dailyMeals); + return this; + } + + private Stream finisher() { + final boolean exceed = dailySumOfCalories > caloriesPerDay; + return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); + } + } + + Collection> values = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate, + Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) + ).values(); + + return values.stream().flatMap(identity()).collect(toList()); + } + + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } +} \ No newline at end of file From 39bd1a77773cfdfcbd37cdd8616102ed00d4c4ff Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:02:34 +0300 Subject: [PATCH 21/44] Prepare to HW0 --- src/main/java/ru/javawebinar/topjava/Main.java | 7 ++----- src/main/java/ru/javawebinar/topjava/util/TimeUtil.java | 9 +++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java index b23a2f0961fc..cb7e35af6afa 100644 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ b/src/main/java/ru/javawebinar/topjava/Main.java @@ -1,11 +1,8 @@ package ru.javawebinar.topjava; /** - * User: gkislin - * Date: 05.08.2015 - * - * @link http://caloriesmng.herokuapp.com/ - * @link https://github.com/JavaOPs/topjava + * @see Demo + * @see Initial project */ public class Main { public static void main(String[] args) { diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java new file mode 100644 index 000000000000..b7eb2af6f93e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -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; + } +} From 57b06e8a196b58e475495787d6f0f569881f8b8d Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:03:22 +0300 Subject: [PATCH 22/44] 1 4 switch to war --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 19 +++++++++++++++++++ src/main/webapp/index.html | 13 +++++++++++++ src/main/webapp/users.jsp | 10 ++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index c8a1c78f3b29..6f0e7fd7cf47 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..1c91ffe2cbaf --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + Topjava + + + userServlet + ru.javawebinar.topjava.web.UserServlet + 0 + + + userServlet + /users + + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..6253517f8b84 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,13 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

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

Home

+

Users

+ + \ No newline at end of file From 8e3664eee6aaaa3a5b1d34753a8c27fc2713eb8e Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:03:35 +0300 Subject: [PATCH 23/44] 1 5 add servlet api --- .../javawebinar/topjava/web/UserServlet.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java new file mode 100644 index 000000000000..ef52d67576c0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,23 @@ +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"); + } +} From c104c51e58dda0181090cb13d4c84653f196e934 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:03:52 +0300 Subject: [PATCH 24/44] 1 7 logging --- pom.xml | 37 ++++++++++++++++++++++++++++++++-- src/main/resources/logback.xml | 29 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 6f0e7fd7cf47..d2ac57ffd6f5 100644 --- a/pom.xml +++ b/pom.xml @@ -15,16 +15,20 @@ 1.8 UTF-8 UTF-8 + + + 1.2.3 + 1.7.25 topjava - install + package org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.7.0 ${java.version} ${java.version} @@ -34,6 +38,35 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..e9b900b26669 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From c22c88413f0d5210fd2e0c9b0683188aebb0b281 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:04:10 +0300 Subject: [PATCH 25/44] HW1 --- 1_0_fix.patch | 119 ++++++ 1_1_HW0_stream.patch | 116 ++++++ 1_2_HW0_cycle.patch | 57 +++ 1_3_HW0_optional2.patch | 90 +++++ 1_4_switch_to_war.patch | 83 ++++ 1_5_add_servlet_api.patch | 45 +++ 1_6_forward_to_redirect.patch | 32 ++ 1_7_logging.patch | 113 ++++++ 2_1_HW1.patch | 406 ++++++++++++++++++++ 2_2_HW1_optional.patch | 398 +++++++++++++++++++ 2_3_app_layers.patch | 569 ++++++++++++++++++++++++++++ 2_4_add_spring_context.patch | 167 ++++++++ 2_5_dependency_injection.patch | 63 +++ 2_6_annotation_processing.patch | 191 ++++++++++ 2_7_constructor_injection.patch | 44 +++ 3_01_HW2_repository.patch | 319 ++++++++++++++++ 3_02_HW2_meal_layers.patch | 368 ++++++++++++++++++ 3_03_HW2_optional_MealServlet.patch | 94 +++++ 3_04_HW2_optional_filter.patch | 220 +++++++++++ 3_05_HW2_optional_select_user.patch | 69 ++++ 3_06_bean_life_cycle.patch | 49 +++ 3_07_add_junit.patch | 185 +++++++++ 3_08_add_spring_test.patch | 75 ++++ 3_09_add_postgresql.patch | 45 +++ 3_10_db_implementation.patch | 196 ++++++++++ 3_11_test_UserService.patch | 335 ++++++++++++++++ 3_12_test_logging.patch | 104 +++++ 3_13_fix_servlet.patch | 55 +++ Prepare_to_HW0.patch | 158 ++++++++ 29 files changed, 4765 insertions(+) create mode 100644 1_0_fix.patch create mode 100644 1_1_HW0_stream.patch create mode 100644 1_2_HW0_cycle.patch create mode 100644 1_3_HW0_optional2.patch create mode 100644 1_4_switch_to_war.patch create mode 100644 1_5_add_servlet_api.patch create mode 100644 1_6_forward_to_redirect.patch create mode 100644 1_7_logging.patch create mode 100644 2_1_HW1.patch create mode 100644 2_2_HW1_optional.patch create mode 100644 2_3_app_layers.patch create mode 100644 2_4_add_spring_context.patch create mode 100644 2_5_dependency_injection.patch create mode 100644 2_6_annotation_processing.patch create mode 100644 2_7_constructor_injection.patch create mode 100644 3_01_HW2_repository.patch create mode 100644 3_02_HW2_meal_layers.patch create mode 100644 3_03_HW2_optional_MealServlet.patch create mode 100644 3_04_HW2_optional_filter.patch create mode 100644 3_05_HW2_optional_select_user.patch create mode 100644 3_06_bean_life_cycle.patch create mode 100644 3_07_add_junit.patch create mode 100644 3_08_add_spring_test.patch create mode 100644 3_09_add_postgresql.patch create mode 100644 3_10_db_implementation.patch create mode 100644 3_11_test_UserService.patch create mode 100644 3_12_test_logging.patch create mode 100644 3_13_fix_servlet.patch create mode 100644 Prepare_to_HW0.patch diff --git a/1_0_fix.patch b/1_0_fix.patch new file mode 100644 index 000000000000..7c2d636c0f61 --- /dev/null +++ b/1_0_fix.patch @@ -0,0 +1,119 @@ +Index: src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) ++++ src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) +@@ -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 1530137160353) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530137160353) +@@ -0,0 +1,31 @@ ++package ru.javawebinar.topjava.util; ++ ++import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.model.MealWithExceed; ++ ++import java.time.LocalDateTime; ++import java.time.LocalTime; ++import java.time.Month; ++import java.util.Arrays; ++import java.util.List; ++ ++public class MealsUtil { ++ public static void main(String[] args) { ++ List mealList = Arrays.asList( ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) ++ ); ++ getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); ++// .toLocalDate(); ++// .toLocalTime(); ++ } ++ ++ public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ // TODO return filtered list with correctly exceeded field ++ return null; ++ } ++} +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530137160292) +@@ -2,7 +2,7 @@ + + import java.time.LocalDateTime; + +-public class UserMealWithExceed { ++public class MealWithExceed { + private final LocalDateTime dateTime; + + private final String description; +@@ -11,7 +11,7 @@ + + private final boolean exceed; + +- public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { ++ public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; +Index: src/main/java/ru/javawebinar/topjava/model/UserMeal.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/UserMeal.java (revision 44067f66e66df23f8edb88112d3e152c779e0c38) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1530137142426) +@@ -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; diff --git a/1_1_HW0_stream.patch b/1_1_HW0_stream.patch new file mode 100644 index 000000000000..4c11a4be2034 --- /dev/null +++ b/1_1_HW0_stream.patch @@ -0,0 +1,116 @@ +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 1530137394000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530137413670) +@@ -3,29 +3,41 @@ + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.model.MealWithExceed; + ++import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.LocalTime; + import java.time.Month; + import java.util.Arrays; + import java.util.List; ++import java.util.Map; ++import java.util.stream.Collectors; + + public class MealsUtil { + public static void main(String[] args) { +- List mealList = Arrays.asList( +- new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), +- new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), +- new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), +- new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), +- new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), +- new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) ++ List meals = Arrays.asList( ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), ++ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) + ); +- getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +-// .toLocalDate(); +-// .toLocalTime(); ++ List mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); ++ mealsWithExceeded.forEach(System.out::println); + } + +- public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- // TODO return filtered list with correctly exceeded field +- return null; ++ public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ Map caloriesSumByDate = meals.stream() ++ .collect( ++ Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) ++// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) ++ ); ++ ++ return meals.stream() ++ .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .map(meal -> ++ new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), ++ caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) ++ .collect(Collectors.toList()); + } + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530137394000) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530137413609) +@@ -17,4 +17,14 @@ + this.calories = calories; + this.exceed = exceed; + } +-} ++ ++ @Override ++ public String toString() { ++ return "UserMealWithExceed{" + ++ "dateTime=" + dateTime + ++ ", description='" + description + '\'' + ++ ", calories=" + calories + ++ ", exceed=" + exceed + ++ '}'; ++ } ++} +\ No newline at end of file +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 1530137394000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1530137413502) +@@ -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(); ++ } + } diff --git a/1_2_HW0_cycle.patch b/1_2_HW0_cycle.patch new file mode 100644 index 000000000000..4ecbb830ff9e --- /dev/null +++ b/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 1499897832000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (revision ) +@@ -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 mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExceeded.forEach(System.out::println); ++ ++ System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -35,9 +35,25 @@ + + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) +- .map(meal -> +- new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), +- caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) ++ .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } ++ ++ public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ ++ final Map caloriesSumByDate = new HashMap<>(); ++ meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); ++ ++ final List mealsWithExceeded = new ArrayList<>(); ++ meals.forEach(meal -> { ++ if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { ++ mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); ++ } ++ }); ++ return mealsWithExceeded; ++ } ++ ++ public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { ++ return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); ++ } + } +\ No newline at end of file diff --git a/1_3_HW0_optional2.patch b/1_3_HW0_optional2.patch new file mode 100644 index 000000000000..26dec67cb4e9 --- /dev/null +++ b/1_3_HW0_optional2.patch @@ -0,0 +1,90 @@ +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 1509555460000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (revision ) +@@ -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 @@ + mealsWithExceeded.forEach(System.out::println); + + System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -36,7 +43,7 @@ + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) +- .collect(Collectors.toList()); ++ .collect(toList()); + } + + public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +@@ -53,6 +60,51 @@ + return mealsWithExceeded; + } + ++ public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ Collection> list = meals.stream() ++ .collect(Collectors.groupingBy(Meal::getDate)).values(); ++ ++ return list.stream().flatMap(dayMeals -> { ++ boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; ++ return dayMeals.stream().filter(meal -> ++ TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .map(meal -> createWithExceed(meal, exceed)); ++ }).collect(toList()); ++ } ++ ++ public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ final class Aggregate { ++ private final List dailyMeals = new ArrayList<>(); ++ private int dailySumOfCalories; ++ ++ private void accumulate(Meal meal) { ++ dailySumOfCalories += meal.getCalories(); ++ if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { ++ dailyMeals.add(meal); ++ } ++ } ++ ++ // never invoked if the upstream is sequential ++ private Aggregate combine(Aggregate that) { ++ this.dailySumOfCalories += that.dailySumOfCalories; ++ this.dailyMeals.addAll(that.dailyMeals); ++ return this; ++ } ++ ++ private Stream finisher() { ++ final boolean exceed = dailySumOfCalories > caloriesPerDay; ++ return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); ++ } ++ } ++ ++ Collection> values = meals.stream() ++ .collect(Collectors.groupingBy(Meal::getDate, ++ Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) ++ ).values(); ++ ++ return values.stream().flatMap(identity()).collect(toList()); ++ } ++ + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } diff --git a/1_4_switch_to_war.patch b/1_4_switch_to_war.patch new file mode 100644 index 000000000000..ed7403bcf79b --- /dev/null +++ b/1_4_switch_to_war.patch @@ -0,0 +1,83 @@ +Index: src/main/webapp/users.jsp +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/webapp/users.jsp (revision ) ++++ src/main/webapp/users.jsp (revision ) +@@ -0,0 +1,10 @@ ++<%@ 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 (revision ) ++++ src/main/webapp/index.html (revision ) +@@ -0,0 +1,13 @@ ++ ++ ++ ++ Java Enterprise (Topjava) ++ ++ ++

Проект Java Enterprise (Topjava)

++
++ ++ ++ +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 ) ++++ src/main/webapp/WEB-INF/web.xml (revision ) +@@ -0,0 +1,19 @@ ++ ++ ++ Topjava ++ ++ ++ userServlet ++ ru.javawebinar.topjava.web.UserServlet ++ 0 ++ ++ ++ userServlet ++ /users ++ ++ ++ +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1499898097000) ++++ pom.xml (revision ) +@@ -4,7 +4,7 @@ + + ru.javawebinar + topjava +- jar ++ war + + 1.0-SNAPSHOT + diff --git a/1_5_add_servlet_api.patch b/1_5_add_servlet_api.patch new file mode 100644 index 000000000000..9bd9d2658d69 --- /dev/null +++ b/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 (revision ) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (revision ) +@@ -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 1499898685000) ++++ pom.xml (revision ) +@@ -34,6 +34,14 @@ + + + ++ ++ ++ ++ javax.servlet ++ javax.servlet-api ++ 3.1.0 ++ provided ++ + + + diff --git a/1_6_forward_to_redirect.patch b/1_6_forward_to_redirect.patch new file mode 100644 index 000000000000..35fc459218a3 --- /dev/null +++ b/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/1_7_logging.patch b/1_7_logging.patch new file mode 100644 index 000000000000..72fad180b032 --- /dev/null +++ b/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/2_1_HW1.patch b/2_1_HW1.patch new file mode 100644 index 000000000000..e5b49a9fa53d --- /dev/null +++ b/2_1_HW1.patch @@ -0,0 +1,406 @@ +Index: src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/util/TimeUtil.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) +@@ -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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/Main.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) +@@ -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/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 1530650966615) ++++ src/main/webapp/WEB-INF/tld/functions.tld (date 1530650966615) +@@ -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: 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 1530650926186) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1530650926186) +@@ -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/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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1530650966609) +@@ -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/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 1530650966602) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1530650966602) +@@ -0,0 +1,22 @@ ++package ru.javawebinar.topjava.web; ++ ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import ru.javawebinar.topjava.util.MealsUtil; ++ ++import javax.servlet.ServletException; ++import javax.servlet.http.HttpServlet; ++import javax.servlet.http.HttpServletRequest; ++import javax.servlet.http.HttpServletResponse; ++import java.io.IOException; ++ ++public class MealServlet extends HttpServlet { ++ private static final Logger log = LoggerFactory.getLogger(MealServlet.class); ++ ++ @Override ++ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ++ log.info("getAll"); ++ request.setAttribute("meals", MealsUtil.getWithExceeded(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ } ++} +Index: src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530650926177) +@@ -18,9 +18,25 @@ + this.exceed = exceed; + } + ++ public LocalDateTime getDateTime() { ++ return dateTime; ++ } ++ ++ public String getDescription() { ++ return description; ++ } ++ ++ public int getCalories() { ++ return calories; ++ } ++ ++ public boolean isExceed() { ++ return exceed; ++ } ++ + @Override + public String toString() { +- return "UserMealWithExceed{" + ++ return "MealWithExceed{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (revision 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ pom.xml (date 1530650966647) +@@ -67,6 +67,12 @@ + 3.1.0 + 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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530651123956) +@@ -8,32 +8,32 @@ + import java.time.LocalTime; + import java.time.Month; + import java.util.*; +-import java.util.stream.Collector; ++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 mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); +- mealsWithExceeded.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(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); +- System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); +- System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); ++ public static List getWithExceeded(List meals, int caloriesPerDay) { ++ return getFilteredWithExceeded(meals, caloriesPerDay, meal -> true); + } + +- public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { ++ public static List getFilteredWithExceeded(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { ++ return getFilteredWithExceeded(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); ++ } ++ ++ private static List getFilteredWithExceeded(List meals, int caloriesPerDay, Predicate filter) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +@@ -41,70 +41,11 @@ + ); + + return meals.stream() +- .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) ++ .filter(filter) + .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(toList()); + } + +- public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- +- final Map caloriesSumByDate = new HashMap<>(); +- meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); +- +- final List mealsWithExceeded = new ArrayList<>(); +- meals.forEach(meal -> { +- if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { +- mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); +- } +- }); +- return mealsWithExceeded; +- } +- +- public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- Collection> list = meals.stream() +- .collect(Collectors.groupingBy(Meal::getDate)).values(); +- +- return list.stream().flatMap(dayMeals -> { +- boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; +- return dayMeals.stream().filter(meal -> +- TimeUtil.isBetween(meal.getTime(), startTime, endTime)) +- .map(meal -> createWithExceed(meal, exceed)); +- }).collect(toList()); +- } +- +- public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { +- final class Aggregate { +- private final List dailyMeals = new ArrayList<>(); +- private int dailySumOfCalories; +- +- private void accumulate(Meal meal) { +- dailySumOfCalories += meal.getCalories(); +- if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { +- dailyMeals.add(meal); +- } +- } +- +- // never invoked if the upstream is sequential +- private Aggregate combine(Aggregate that) { +- this.dailySumOfCalories += that.dailySumOfCalories; +- this.dailyMeals.addAll(that.dailyMeals); +- return this; +- } +- +- private Stream finisher() { +- final boolean exceed = dailySumOfCalories > caloriesPerDay; +- return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); +- } +- } +- +- Collection> values = meals.stream() +- .collect(Collectors.groupingBy(Meal::getDate, +- Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) +- ).values(); +- +- return values.stream().flatMap(identity()).collect(toList()); +- } +- + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } +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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/webapp/WEB-INF/web.xml (date 1530650966622) +@@ -16,4 +16,14 @@ + /users + + ++ ++ mealServlet ++ ru.javawebinar.topjava.web.MealServlet ++ 0 ++ ++ ++ mealServlet ++ /meals ++ ++ + +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 3caf90e8a49823ca3b35c71774cafb8da88ce9c2) ++++ src/main/webapp/index.html (date 1530650966629) +@@ -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 1530650966639) ++++ src/main/webapp/meals.jsp (date 1530650966639) +@@ -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 diff --git a/2_2_HW1_optional.patch b/2_2_HW1_optional.patch new file mode 100644 index 000000000000..4a168766c9e3 --- /dev/null +++ b/2_2_HW1_optional.patch @@ -0,0 +1,398 @@ +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 1530651650797) ++++ src/main/webapp/mealForm.jsp (date 1530651650797) +@@ -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/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 1530651607835) ++++ src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1530651607835) +@@ -0,0 +1,15 @@ ++package ru.javawebinar.topjava.repository; ++ ++import ru.javawebinar.topjava.model.Meal; ++ ++import java.util.Collection; ++ ++public interface MealRepository { ++ Meal save(Meal meal); ++ ++ void delete(int id); ++ ++ Meal get(int id); ++ ++ Collection getAll(); ++} +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 1530651607825) ++++ src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java (date 1530651607825) +@@ -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 void delete(int id) { ++ repository.remove(id); ++ } ++ ++ @Override ++ public Meal get(int id) { ++ return repository.get(id); ++ } ++ ++ @Override ++ public Collection getAll() { ++ return repository.values(); ++ } ++} ++ +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 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1530651650782) +@@ -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.getWithExceeded(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); +- request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ String action = request.getParameter("action"); ++ ++ switch (action == null ? "all" : action) { ++ case "delete": ++ int id = getId(request); ++ log.info("Delete {}", id); ++ repository.delete(id); ++ response.sendRedirect("meals"); ++ break; ++ case "create": ++ case "update": ++ final Meal meal = "create".equals(action) ? ++ new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : ++ repository.get(getId(request)); ++ request.setAttribute("meal", meal); ++ request.getRequestDispatcher("/mealForm.jsp").forward(request, response); ++ break; ++ case "all": ++ default: ++ log.info("getAll"); ++ request.setAttribute("meals", ++ MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ break; ++ } ++ } ++ ++ private int getId(HttpServletRequest request) { ++ String paramId = Objects.requireNonNull(request.getParameter("id")); ++ return Integer.parseInt(paramId); + } + } +Index: src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1530651607815) +@@ -3,6 +3,8 @@ + import java.time.LocalDateTime; + + public class MealWithExceed { ++ private final Integer id; ++ + private final LocalDateTime dateTime; + + private final String description; +@@ -11,13 +13,18 @@ + + private final boolean exceed; + +- public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { ++ public MealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { ++ this.id = id; + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.exceed = exceed; + } + ++ public Integer getId() { ++ return id; ++ } ++ + public LocalDateTime getDateTime() { + return dateTime; + } +@@ -37,7 +44,8 @@ + @Override + public String toString() { + return "MealWithExceed{" + +- "dateTime=" + dateTime + ++ "id=" + id + ++ ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + +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 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1530651607805) +@@ -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/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 1530651578000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1530651650741) +@@ -25,15 +25,15 @@ + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + +- public static List getWithExceeded(List meals, int caloriesPerDay) { ++ public static List getWithExceeded(Collection meals, int caloriesPerDay) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> true); + } + +- public static List getFilteredWithExceeded(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { ++ public static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); + } + +- private static List getFilteredWithExceeded(List meals, int caloriesPerDay, Predicate filter) { ++ private static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, Predicate filter) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +@@ -47,6 +47,6 @@ + } + + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { +- return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); ++ return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } + } +\ No newline at end of file +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 1530651578000) ++++ src/main/webapp/meals.jsp (date 1530651650811) +@@ -20,6 +20,7 @@ +
+

Home

+

Meals

++ Add Meal +
+ + +@@ -27,6 +28,8 @@ + + + ++ ++ + + + +@@ -40,6 +43,8 @@ + + + ++ ++ + + +
DateDescriptionCalories
${meal.description}${meal.calories}UpdateDelete
diff --git a/2_3_app_layers.patch b/2_3_app_layers.patch new file mode 100644 index 000000000000..8e878d713bf9 --- /dev/null +++ b/2_3_app_layers.patch @@ -0,0 +1,569 @@ +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 1530739373367) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1530739373367) +@@ -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/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 1530739373368) ++++ src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java (date 1530739373368) +@@ -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/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 1530739373370) ++++ src/main/java/ru/javawebinar/topjava/service/UserService.java (date 1530739373370) +@@ -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/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 1530739373423) ++++ src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java (date 1530739373423) +@@ -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 1530741235278) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1530741235278) +@@ -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/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 1530739482294) ++++ src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1530739482294) +@@ -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/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 1530741235263) ++++ src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1530741235263) +@@ -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/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 1530740790618) ++++ src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1530740790618) +@@ -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/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 1530739373358) ++++ src/main/java/ru/javawebinar/topjava/model/Role.java (date 1530739373358) +@@ -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/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 1530740790635) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1530740790635) +@@ -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/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 1530739373354) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1530739373354) +@@ -0,0 +1,24 @@ ++package ru.javawebinar.topjava.model; ++ ++public abstract class AbstractNamedEntity extends AbstractBaseEntity { ++ ++ protected String name; ++ ++ protected AbstractNamedEntity(Integer id, String name) { ++ super(id); ++ this.name = name; ++ } ++ ++ public void setName(String name) { ++ this.name = name; ++ } ++ ++ public String getName() { ++ return this.name; ++ } ++ ++ @Override ++ public String toString() { ++ return String.format("Entity %s (%s, '%s')", getClass().getName(), id, name); ++ } ++} +\ No newline at end of file +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 1530739373365) ++++ src/main/java/ru/javawebinar/topjava/repository/UserRepository.java (date 1530739373365) +@@ -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/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 1530739482288) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530739482288) +@@ -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) { ++ checkNotFoundWithId(repository.save(user), user.getId()); ++ } ++} +\ 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 1530739373421) ++++ src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java (date 1530739373421) +@@ -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/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 1530740790656) ++++ src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java (date 1530740790656) +@@ -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) { ++// 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/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 1530739373352) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1530739373352) +@@ -0,0 +1,26 @@ ++package ru.javawebinar.topjava.model; ++ ++public abstract class AbstractBaseEntity { ++ protected Integer id; ++ ++ protected AbstractBaseEntity(Integer id) { ++ this.id = id; ++ } ++ ++ public void setId(Integer id) { ++ this.id = id; ++ } ++ ++ public Integer getId() { ++ return id; ++ } ++ ++ public boolean isNew() { ++ return this.id == null; ++ } ++ ++ @Override ++ public String toString() { ++ return String.format("Entity %s (%s)", getClass().getName(), id); ++ } ++} +\ No newline at end of file diff --git a/2_4_add_spring_context.patch b/2_4_add_spring_context.patch new file mode 100644 index 000000000000..2283c901708e --- /dev/null +++ b/2_4_add_spring_context.patch @@ -0,0 +1,167 @@ +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 1530652518211) ++++ src/main/resources/spring/spring-app.xml (date 1530652518211) +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530652518187) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530652518187) +@@ -0,0 +1,43 @@ ++package ru.javawebinar.topjava.repository.mock; ++ ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.UserRepository; ++ ++import java.util.Collections; ++import java.util.List; ++ ++public class MockUserRepositoryImpl implements UserRepository { ++ private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); ++ ++ @Override ++ public boolean delete(int id) { ++ log.info("delete {}", id); ++ return true; ++ } ++ ++ @Override ++ public User save(User user) { ++ log.info("save {}", user); ++ return user; ++ } ++ ++ @Override ++ public User get(int id) { ++ log.info("get {}", id); ++ return null; ++ } ++ ++ @Override ++ public List getAll() { ++ log.info("getAll"); ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public User getByEmail(String email) { ++ log.info("getByEmail {}", email); ++ return null; ++ } ++} +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 1530652518204) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1530652518204) +@@ -0,0 +1,19 @@ ++package ru.javawebinar.topjava; ++ ++import org.springframework.context.ConfigurableApplicationContext; ++import org.springframework.context.support.ClassPathXmlApplicationContext; ++import ru.javawebinar.topjava.repository.UserRepository; ++ ++import java.util.Arrays; ++ ++public class SpringMain { ++ public static void main(String[] args) { ++ ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); ++ System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); ++ ++// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); ++ UserRepository userRepository = appCtx.getBean(UserRepository.class); ++ userRepository.getAll(); ++ appCtx.close(); ++ } ++} +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 1530652485000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1530652518196) +@@ -3,8 +3,8 @@ + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import ru.javawebinar.topjava.model.Meal; +-import ru.javawebinar.topjava.repository.InMemoryMealRepositoryImpl; + import ru.javawebinar.topjava.repository.MealRepository; ++import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; + import ru.javawebinar.topjava.util.MealsUtil; + + import javax.servlet.ServletConfig; +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1530652485000) ++++ pom.xml (date 1530652892544) +@@ -16,6 +16,8 @@ + UTF-8 + UTF-8 + ++ 5.0.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} ++ + + + +Index: src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java (date 1530652485000) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1530652518136) +@@ -1,6 +1,7 @@ +-package ru.javawebinar.topjava.repository; ++package ru.javawebinar.topjava.repository.mock; + + import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.repository.MealRepository; + import ru.javawebinar.topjava.util.MealsUtil; + + import java.util.Collection; diff --git a/2_5_dependency_injection.patch b/2_5_dependency_injection.patch new file mode 100644 index 000000000000..46ea4d91f4bc --- /dev/null +++ b/2_5_dependency_injection.patch @@ -0,0 +1,63 @@ +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 1530652947000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530653294435) +@@ -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); +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 1530652947000) ++++ src/main/resources/spring/spring-app.xml (date 1530653294440) +@@ -4,4 +4,7 @@ + + + ++ ++ ++ + +\ 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 1530652947000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1530653316991) +@@ -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("mockUserRepository"); + 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(); + } + } diff --git a/2_6_annotation_processing.patch b/2_6_annotation_processing.patch new file mode 100644 index 000000000000..cf7228292ac3 --- /dev/null +++ b/2_6_annotation_processing.patch @@ -0,0 +1,191 @@ +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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530741375310) +@@ -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/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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java (date 1530741375333) +@@ -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/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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1530741391339) +@@ -4,23 +4,17 @@ + import org.springframework.context.support.ClassPathXmlApplicationContext; + import ru.javawebinar.topjava.model.Role; + import ru.javawebinar.topjava.model.User; +-import ru.javawebinar.topjava.repository.UserRepository; +-import ru.javawebinar.topjava.service.UserService; ++import ru.javawebinar.topjava.web.user.AdminRestController; + + import java.util.Arrays; + + public class SpringMain { + public static void main(String[] args) { +- ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); +- System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); +- +-// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); +- UserRepository userRepository = appCtx.getBean(UserRepository.class); +- userRepository.getAll(); +- +- UserService userService = appCtx.getBean(UserService.class); +- userService.create(new User(null, "userName", "email@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/mock/MockUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (date 1530741375289) +@@ -2,12 +2,14 @@ + + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; ++import org.springframework.stereotype.Repository; + import ru.javawebinar.topjava.model.User; + import ru.javawebinar.topjava.repository.UserRepository; + + import java.util.Collections; + import java.util.List; + ++@Repository + public class MockUserRepositoryImpl implements UserRepository { + private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); + +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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java (date 1530741391288) +@@ -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/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 1530741372000) ++++ src/main/resources/spring/spring-app.xml (date 1530741391353) +@@ -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/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 1530741372000) ++++ src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java (date 1530741375322) +@@ -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/2_7_constructor_injection.patch b/2_7_constructor_injection.patch new file mode 100644 index 000000000000..14757e127642 --- /dev/null +++ b/2_7_constructor_injection.patch @@ -0,0 +1,44 @@ +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 1530653665000) ++++ src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java (date 1530653786000) +@@ -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) { +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 1530653665000) ++++ src/main/resources/spring/spring-app.xml (date 1530653786000) +@@ -5,11 +5,11 @@ + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + + + diff --git a/3_01_HW2_repository.patch b/3_01_HW2_repository.patch new file mode 100644 index 000000000000..c86e1808c194 --- /dev/null +++ b/3_01_HW2_repository.patch @@ -0,0 +1,319 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (revision 329c11142d66f81cce0fc07d3a5e9660763cab12) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java (revision 329c11142d66f81cce0fc07d3a5e9660763cab12) +@@ -1,45 +0,0 @@ +-package ru.javawebinar.topjava.repository.mock; +- +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; +-import org.springframework.stereotype.Repository; +-import ru.javawebinar.topjava.model.User; +-import ru.javawebinar.topjava.repository.UserRepository; +- +-import java.util.Collections; +-import java.util.List; +- +-@Repository +-public class MockUserRepositoryImpl implements UserRepository { +- private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); +- +- @Override +- public boolean delete(int id) { +- log.info("delete {}", id); +- return true; +- } +- +- @Override +- public User save(User user) { +- log.info("save {}", user); +- return user; +- } +- +- @Override +- public User get(int id) { +- log.info("get {}", id); +- return null; +- } +- +- @Override +- public List getAll() { +- log.info("getAll"); +- return Collections.emptyList(); +- } +- +- @Override +- public User getByEmail(String email) { +- log.info("getByEmail {}", email); +- return null; +- } +-} +Index: src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java (date 1531046236490) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java (date 1531046236490) +@@ -0,0 +1,57 @@ ++package ru.javawebinar.topjava.repository.mock; ++ ++import org.springframework.stereotype.Repository; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.UserRepository; ++ ++import java.util.Comparator; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.stream.Collectors; ++ ++@Repository ++public class InMemoryUserRepositoryImpl implements UserRepository { ++ ++ public static final int USER_ID = 1; ++ public static final int ADMIN_ID = 2; ++ ++ private Map repository = new ConcurrentHashMap<>(); ++ private AtomicInteger counter = new AtomicInteger(0); ++ ++ @Override ++ public User save(User user) { ++ if (user.isNew()) { ++ user.setId(counter.incrementAndGet()); ++ repository.put(user.getId(), user); ++ return user; ++ } ++ return repository.computeIfPresent(user.getId(), (id, oldUser) -> user); ++ } ++ ++ @Override ++ public boolean delete(int id) { ++ return repository.remove(id) != null; ++ } ++ ++ @Override ++ public User get(int id) { ++ return repository.get(id); ++ } ++ ++ @Override ++ public List getAll() { ++ return repository.values().stream() ++ .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) ++ .collect(Collectors.toList()); ++ } ++ ++ @Override ++ public User getByEmail(String email) { ++ return repository.values().stream() ++ .filter(u -> email.equals(u.getEmail())) ++ .findFirst() ++ .orElse(null); ++ } ++} +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 329c11142d66f81cce0fc07d3a5e9660763cab12) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1531048110547) +@@ -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.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); ++ MealsUtil.getWithExceeded(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } +Index: src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (revision 329c11142d66f81cce0fc07d3a5e9660763cab12) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1531048081608) +@@ -1,46 +1,78 @@ + package ru.javawebinar.topjava.repository.mock; + ++import org.springframework.util.CollectionUtils; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; ++import ru.javawebinar.topjava.util.DateTimeUtil; + import ru.javawebinar.topjava.util.MealsUtil; + +-import java.util.Collection; ++import java.time.LocalDateTime; ++import java.time.Month; ++import java.util.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.mock.InMemoryUserRepositoryImpl.ADMIN_ID; ++import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; + + public class InMemoryMealRepositoryImpl implements MealRepository { +- private Map repository = new ConcurrentHashMap<>(); ++ ++ // Map userId -> (mealId-> meal) ++ private Map> repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + { +- MealsUtil.MEALS.forEach(this::save); ++ MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); ++ ++ save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); ++ save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); + } ++ + + @Override +- public Meal save(Meal meal) { ++ public Meal save(Meal meal, int userId) { ++ Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); + if (meal.isNew()) { + meal.setId(counter.incrementAndGet()); +- repository.put(meal.getId(), meal); ++ meals.put(meal.getId(), meal); + return meal; + } +- // treat case: update, but absent in storage +- return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); ++ return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + } + + @Override +- public void delete(int id) { +- repository.remove(id); ++ public boolean delete(int id, int userId) { ++ Map meals = repository.get(userId); ++ return meals != null && meals.remove(id) != null; + } + + @Override +- public Meal get(int id) { +- return repository.get(id); ++ public Meal get(int id, int userId) { ++ Map meals = repository.get(userId); ++ return meals == null ? null : meals.get(id); + } + + @Override +- public Collection getAll() { +- return repository.values(); ++ public List getAll(int userId) { ++ return getAllFiltered(userId, meal -> true); + } +-} ++ ++ @Override ++ public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { ++ return getAllFiltered(userId, meal -> DateTimeUtil.isBetween(meal.getDateTime(), startDateTime, endDateTime)); ++ } + ++ private List getAllFiltered(int userId, Predicate filter) { ++ Map meals = repository.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/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 (revision 329c11142d66f81cce0fc07d3a5e9660763cab12) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1531048081612) +@@ -1,14 +1,13 @@ + package ru.javawebinar.topjava.util; + + import java.time.LocalDateTime; +-import java.time.LocalTime; + import java.time.format.DateTimeFormatter; + + public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + +- public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { +- return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; ++ public static > boolean isBetween(T value, T start, T end) { ++ return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; + } + + public static String toString(LocalDateTime ldt) { +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 329c11142d66f81cce0fc07d3a5e9660763cab12) ++++ src/main/java/ru/javawebinar/topjava/repository/MealRepository.java (date 1531046236501) +@@ -2,14 +2,22 @@ + + import ru.javawebinar.topjava.model.Meal; + +-import java.util.Collection; ++import java.time.LocalDateTime; ++import java.util.List; + + public interface MealRepository { +- Meal save(Meal meal); ++ // null if updated meal do not belong to userId ++ Meal save(Meal meal, int userId); + +- void delete(int id); ++ // false if meal do not belong to userId ++ boolean delete(int id, int userId); + +- Meal get(int id); ++ // null if meal do not belong to userId ++ Meal get(int id, int userId); + +- Collection getAll(); ++ // ORDERED dateTime desc ++ List getAll(int userId); ++ ++ // ORDERED dateTime desc ++ List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); + } diff --git a/3_02_HW2_meal_layers.patch b/3_02_HW2_meal_layers.patch new file mode 100644 index 000000000000..2c6fd4976b84 --- /dev/null +++ b/3_02_HW2_meal_layers.patch @@ -0,0 +1,368 @@ +Index: src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java (date 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java (date 1531047226255) +@@ -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/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 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/model/Meal.java (date 1531047226208) +@@ -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/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 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java (date 1531047424659) +@@ -1,8 +1,83 @@ + 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.MealWithExceed; ++import ru.javawebinar.topjava.util.DateTimeUtil; ++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.getWithExceeded(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 != null ? startDate : DateTimeUtil.MIN_DATE, ++ endDate != null ? endDate : DateTimeUtil.MAX_DATE, userId); ++ ++ return MealsUtil.getFilteredWithExceeded(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), ++ startTime != null ? startTime : LocalTime.MIN, ++ endTime != null ? endTime : LocalTime.MAX ++ ); ++ } + } +\ 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 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java (date 1531047226247) +@@ -1,9 +1,52 @@ + package ru.javawebinar.topjava.service; + ++import org.springframework.beans.factory.annotation.Autowired; ++import org.springframework.stereotype.Service; ++import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; + ++import java.time.LocalDateTime; ++import java.util.List; ++ ++import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; ++ ++@Service + public class MealServiceImpl implements MealService { + +- private MealRepository repository; ++ private final MealRepository repository; ++ ++ @Autowired ++ public MealServiceImpl(MealRepository repository) { ++ this.repository = repository; ++ } ++ ++ @Override ++ public Meal get(int id, int userId) { ++ return checkNotFoundWithId(repository.get(id, userId), id); ++ } ++ ++ @Override ++ public void delete(int id, int userId) { ++ checkNotFoundWithId(repository.delete(id, userId), id); ++ } ++ ++ @Override ++ public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { ++ return repository.getBetween(startDateTime, endDateTime, userId); ++ } + ++ @Override ++ public List getAll(int userId) { ++ return repository.getAll(userId); ++ } ++ ++ @Override ++ public Meal update(Meal meal, int userId) { ++ return checkNotFoundWithId(repository.save(meal, userId), meal.getId()); ++ } ++ ++ @Override ++ public Meal create(Meal meal, int userId) { ++ return repository.save(meal, userId); ++ } + } +\ No newline at end of file +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 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/SpringMain.java (date 1531047424679) +@@ -4,9 +4,15 @@ + import org.springframework.context.support.ClassPathXmlApplicationContext; + import ru.javawebinar.topjava.model.Role; + import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.to.MealWithExceed; ++import ru.javawebinar.topjava.web.meal.MealRestController; + import ru.javawebinar.topjava.web.user.AdminRestController; + ++import java.time.LocalDate; ++import java.time.LocalTime; ++import java.time.Month; + import java.util.Arrays; ++import java.util.List; + + public class SpringMain { + public static void main(String[] args) { +@@ -15,6 +21,14 @@ + 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 filteredMealsWithExceeded = ++ mealController.getBetween( ++ LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), ++ LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); ++ filteredMealsWithExceeded.forEach(System.out::println); + } + } + } +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 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/service/MealService.java (date 1531047226237) +@@ -1,4 +1,27 @@ + package ru.javawebinar.topjava.service; + ++import ru.javawebinar.topjava.model.Meal; ++import ru.javawebinar.topjava.util.exception.NotFoundException; ++ ++import java.time.LocalDate; ++import java.time.LocalDateTime; ++import java.time.LocalTime; ++import java.util.List; ++ + public interface MealService { ++ Meal get(int id, int userId) throws NotFoundException; ++ ++ void delete(int id, int userId) throws NotFoundException; ++ ++ default List getBetweenDates(LocalDate startDate, LocalDate endDate, int userId) { ++ return getBetweenDateTimes(LocalDateTime.of(startDate, LocalTime.MIN), LocalDateTime.of(endDate, LocalTime.MAX), userId); ++ } ++ ++ List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId); ++ ++ List getAll(int userId); ++ ++ Meal update(Meal meal, int userId) throws NotFoundException; ++ ++ Meal create(Meal meal, int userId); + } +\ No newline at end of file +Index: src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1531047226221) +@@ -1,5 +1,6 @@ + package ru.javawebinar.topjava.repository.mock; + ++import org.springframework.stereotype.Repository; + import org.springframework.util.CollectionUtils; + import ru.javawebinar.topjava.model.Meal; + import ru.javawebinar.topjava.repository.MealRepository; +@@ -20,6 +21,7 @@ + import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.ADMIN_ID; + import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; + ++@Repository + public class InMemoryMealRepositoryImpl implements MealRepository { + + // Map userId -> (mealId-> meal) +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 1531047209000) ++++ src/main/webapp/meals.jsp (date 1531047240744) +@@ -33,7 +33,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 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1531047226290) +@@ -1,11 +1,16 @@ + package ru.javawebinar.topjava.util; + ++import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.format.DateTimeFormatter; + + public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + ++ // DataBase doesn't support LocalDate.MIN/MAX ++ public static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); ++ public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); ++ + public static > boolean isBetween(T value, T start, T end) { + return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; + } +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 1531047209000) ++++ src/main/java/ru/javawebinar/topjava/util/MealsUtil.java (date 1531047226298) +@@ -1,7 +1,7 @@ + package ru.javawebinar.topjava.util; + + import ru.javawebinar.topjava.model.Meal; +-import ru.javawebinar.topjava.model.MealWithExceed; ++import ru.javawebinar.topjava.to.MealWithExceed; + + import java.time.LocalDate; + import java.time.LocalDateTime; diff --git a/3_03_HW2_optional_MealServlet.patch b/3_03_HW2_optional_MealServlet.patch new file mode 100644 index 000000000000..cd9e3141468e --- /dev/null +++ b/3_03_HW2_optional_MealServlet.patch @@ -0,0 +1,94 @@ +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 1531048138000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1531049480960) +@@ -1,11 +1,9 @@ + package ru.javawebinar.topjava.web; + +-import org.slf4j.Logger; +-import org.slf4j.LoggerFactory; ++import org.springframework.context.ConfigurableApplicationContext; ++import org.springframework.context.support.ClassPathXmlApplicationContext; + import ru.javawebinar.topjava.model.Meal; +-import ru.javawebinar.topjava.repository.MealRepository; +-import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; +-import ru.javawebinar.topjava.util.MealsUtil; ++import ru.javawebinar.topjava.web.meal.MealRestController; + + import javax.servlet.ServletConfig; + import javax.servlet.ServletException; +@@ -18,28 +16,36 @@ + import java.util.Objects; + + public class MealServlet extends HttpServlet { +- private static final Logger log = LoggerFactory.getLogger(MealServlet.class); + +- private MealRepository repository; ++ private ConfigurableApplicationContext springContext; ++ private MealRestController mealController; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); +- repository = new InMemoryMealRepositoryImpl(); ++ springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml"); ++ mealController = springContext.getBean(MealRestController.class); ++ } ++ ++ @Override ++ public void destroy() { ++ springContext.close(); ++ super.destroy(); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); +- String id = request.getParameter("id"); +- +- Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), ++ Meal meal = new Meal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); + +- log.info(meal.isNew() ? "Create {}" : "Update {}", meal); +- repository.save(meal, SecurityUtil.authUserId()); ++ if (request.getParameter("id").isEmpty()) { ++ mealController.create(meal); ++ } else { ++ mealController.update(meal, getId(request)); ++ } + response.sendRedirect("meals"); + } + +@@ -50,23 +56,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.getWithExceeded(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/3_04_HW2_optional_filter.patch b/3_04_HW2_optional_filter.patch new file mode 100644 index 000000000000..458c168e5008 --- /dev/null +++ b/3_04_HW2_optional_filter.patch @@ -0,0 +1,220 @@ +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 1531049556494) ++++ src/main/webapp/css/style.css (date 1531049556494) +@@ -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-mealExceed="false"] { ++ color: green; ++} ++ ++tr[data-mealExceed="true"] { ++ color: red; ++} +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 1531049529000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1531049556484) +@@ -11,10 +11,15 @@ + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + import java.io.IOException; ++import java.time.LocalDate; + import java.time.LocalDateTime; ++import java.time.LocalTime; + import java.time.temporal.ChronoUnit; + import java.util.Objects; + ++import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; ++import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; ++ + public class MealServlet extends HttpServlet { + + private ConfigurableApplicationContext springContext; +@@ -36,17 +41,28 @@ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); +- Meal meal = new Meal( +- LocalDateTime.parse(request.getParameter("dateTime")), +- request.getParameter("description"), +- Integer.parseInt(request.getParameter("calories"))); ++ String action = request.getParameter("action"); ++ if (action == null) { ++ Meal meal = new Meal( ++ LocalDateTime.parse(request.getParameter("dateTime")), ++ request.getParameter("description"), ++ Integer.parseInt(request.getParameter("calories"))); + +- if (request.getParameter("id").isEmpty()) { +- mealController.create(meal); +- } else { +- mealController.update(meal, getId(request)); +- } +- response.sendRedirect("meals"); ++ if (request.getParameter("id").isEmpty()) { ++ mealController.create(meal); ++ } else { ++ mealController.update(meal, getId(request)); ++ } ++ response.sendRedirect("meals"); ++ ++ } else if ("filter".equals(action)) { ++ LocalDate startDate = parseLocalDate(request.getParameter("startDate")); ++ LocalDate endDate = parseLocalDate(request.getParameter("endDate")); ++ LocalTime startTime = parseLocalTime(request.getParameter("startTime")); ++ LocalTime endTime = parseLocalTime(request.getParameter("endTime")); ++ request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); ++ request.getRequestDispatcher("/meals.jsp").forward(request, response); ++ } + } + + @Override +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 1531049529000) ++++ src/main/webapp/mealForm.jsp (date 1531049556521) +@@ -4,24 +4,7 @@ + + + Meal +- ++ + + +
+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 1531049529000) ++++ src/main/webapp/meals.jsp (date 1531049556535) +@@ -2,24 +2,35 @@ + <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> +-<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> + + + Meal list +- ++ + + +
+

Home

+

Meals

++
++
++
From Date:
++
++
++
++
To Date:
++
++
++
++
From Time:
++
++
++
++
To Time:
++
++
++ ++
++
+ Add Meal +
+ +@@ -34,7 +45,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 1531049529000) ++++ src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java (date 1531049556468) +@@ -1,7 +1,10 @@ + package ru.javawebinar.topjava.util; + ++import org.springframework.util.StringUtils; ++ + import java.time.LocalDate; + import java.time.LocalDateTime; ++import java.time.LocalTime; + import java.time.format.DateTimeFormatter; + + public class DateTimeUtil { +@@ -18,4 +21,12 @@ + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } ++ ++ public static LocalDate parseLocalDate(String str) { ++ return StringUtils.isEmpty(str) ? null : LocalDate.parse(str); ++ } ++ ++ public static LocalTime parseLocalTime(String str) { ++ return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); ++ } + } diff --git a/3_05_HW2_optional_select_user.patch b/3_05_HW2_optional_select_user.patch new file mode 100644 index 000000000000..12b0a583fdc5 --- /dev/null +++ b/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 1531049646000) ++++ src/main/java/ru/javawebinar/topjava/web/UserServlet.java (date 1531051687862) +@@ -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 1531049646000) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1531051687855) +@@ -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 1531049646000) ++++ src/main/webapp/index.html (date 1531051525032) +@@ -6,9 +6,13 @@ + +

Проект Java Enterprise (Topjava)

+
+- ++
++ Meals of  ++ ++ ++
+ + diff --git a/3_06_bean_life_cycle.patch b/3_06_bean_life_cycle.patch new file mode 100644 index 000000000000..23f2be2f6866 --- /dev/null +++ b/3_06_bean_life_cycle.patch @@ -0,0 +1,49 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1520799144000) ++++ src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1520877425484) +@@ -1,5 +1,7 @@ + package ru.javawebinar.topjava.repository.mock; + ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; + import org.springframework.stereotype.Repository; + import org.springframework.util.CollectionUtils; + import ru.javawebinar.topjava.model.Meal; +@@ -7,6 +9,8 @@ + import ru.javawebinar.topjava.util.DateTimeUtil; + import ru.javawebinar.topjava.util.MealsUtil; + ++import javax.annotation.PostConstruct; ++import javax.annotation.PreDestroy; + import java.time.LocalDateTime; + import java.time.Month; + import java.util.Collections; +@@ -23,6 +27,7 @@ + + @Repository + public class InMemoryMealRepositoryImpl implements MealRepository { ++ private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepositoryImpl.class); + + // Map userId -> (mealId-> meal) + private Map> repository = new ConcurrentHashMap<>(); +@@ -47,6 +52,16 @@ + return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + } + ++ @PostConstruct ++ public void postConstruct() { ++ log.info("+++ PostConstruct"); ++ } ++ ++ @PreDestroy ++ public void preDestroy() { ++ log.info("+++ PreDestroy"); ++ } ++ + @Override + public boolean delete(int id, int userId) { + Map meals = repository.get(userId); diff --git a/3_07_add_junit.patch b/3_07_add_junit.patch new file mode 100644 index 000000000000..af29dbf47907 --- /dev/null +++ b/3_07_add_junit.patch @@ -0,0 +1,185 @@ +Index: src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java (date 1531052086529) ++++ src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java (date 1531052086529) +@@ -0,0 +1,52 @@ ++package ru.javawebinar.topjava.web; ++ ++import org.junit.*; ++import org.springframework.context.ConfigurableApplicationContext; ++import org.springframework.context.support.ClassPathXmlApplicationContext; ++import ru.javawebinar.topjava.UserTestData; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl; ++import ru.javawebinar.topjava.util.exception.NotFoundException; ++import ru.javawebinar.topjava.web.user.AdminRestController; ++ ++import java.util.Arrays; ++import java.util.Collection; ++ ++import static ru.javawebinar.topjava.UserTestData.ADMIN; ++ ++public class InMemoryAdminRestControllerTest { ++ private static ConfigurableApplicationContext appCtx; ++ private static AdminRestController controller; ++ ++ @BeforeClass ++ public static void beforeClass() { ++ appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); ++ System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); ++ controller = appCtx.getBean(AdminRestController.class); ++ } ++ ++ @AfterClass ++ public static void afterClass() { ++ appCtx.close(); ++ } ++ ++ @Before ++ public void setUp() throws Exception { ++ // re-initialize ++ InMemoryUserRepositoryImpl repository = appCtx.getBean(InMemoryUserRepositoryImpl.class); ++ repository.init(); ++ } ++ ++ @Test ++ public void testDelete() throws Exception { ++ controller.delete(UserTestData.USER_ID); ++ Collection users = controller.getAll(); ++ Assert.assertEquals(users.size(), 1); ++ Assert.assertEquals(users.iterator().next(), ADMIN); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void testDeleteNotFound() throws Exception { ++ controller.delete(10); ++ } ++} +\ No newline at end of file +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 1531052086544) ++++ src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1531052086544) +@@ -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/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java (date 1531052024000) ++++ src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java (date 1531052086516) +@@ -1,6 +1,7 @@ + package ru.javawebinar.topjava.repository.mock; + + import org.springframework.stereotype.Repository; ++import ru.javawebinar.topjava.UserTestData; + import ru.javawebinar.topjava.model.User; + import ru.javawebinar.topjava.repository.UserRepository; + +@@ -11,14 +12,20 @@ + import java.util.concurrent.atomic.AtomicInteger; + import java.util.stream.Collectors; + ++import static ru.javawebinar.topjava.UserTestData.ADMIN; ++import static ru.javawebinar.topjava.UserTestData.USER; ++ + @Repository + public class InMemoryUserRepositoryImpl implements UserRepository { + +- public static final int USER_ID = 1; +- public static final int ADMIN_ID = 2; +- + private Map repository = new ConcurrentHashMap<>(); +- private AtomicInteger counter = new AtomicInteger(0); ++ private AtomicInteger counter = new AtomicInteger(100); ++ ++ public void init() { ++ repository.clear(); ++ repository.put(UserTestData.USER_ID, USER); ++ repository.put(UserTestData.ADMIN_ID, ADMIN); ++ } + + @Override + public User save(User user) { +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1531052024000) ++++ pom.xml (date 1531052205398) +@@ -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.0 ++ ++ -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 1531052024000) ++++ src/test/java/ru/javawebinar/topjava/SpringMain.java (date 1531048128205) +@@ -1,0 +1,0 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1531052024000) ++++ src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java (date 1531052086499) +@@ -22,8 +22,8 @@ + import java.util.function.Predicate; + import java.util.stream.Collectors; + +-import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.ADMIN_ID; +-import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; ++import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; ++import static ru.javawebinar.topjava.UserTestData.USER_ID; + + @Repository + public class InMemoryMealRepositoryImpl implements MealRepository { diff --git a/3_08_add_spring_test.patch b/3_08_add_spring_test.patch new file mode 100644 index 000000000000..2fb3fcb0ba65 --- /dev/null +++ b/3_08_add_spring_test.patch @@ -0,0 +1,75 @@ +Index: src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java (date 1531053182886) ++++ src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java (date 1531053182886) +@@ -0,0 +1,47 @@ ++package ru.javawebinar.topjava.web; ++ ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.springframework.beans.factory.annotation.Autowired; ++import org.springframework.test.context.ContextConfiguration; ++import org.springframework.test.context.junit4.SpringRunner; ++import ru.javawebinar.topjava.UserTestData; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl; ++import ru.javawebinar.topjava.util.exception.NotFoundException; ++import ru.javawebinar.topjava.web.user.AdminRestController; ++ ++import java.util.Collection; ++ ++import static ru.javawebinar.topjava.UserTestData.ADMIN; ++ ++@ContextConfiguration("classpath:spring/spring-app.xml") ++@RunWith(SpringRunner.class) ++public class InMemoryAdminRestControllerSpringTest { ++ ++ @Autowired ++ private AdminRestController controller; ++ ++ @Autowired ++ private InMemoryUserRepositoryImpl repository; ++ ++ @Before ++ public void setUp() throws Exception { ++ repository.init(); ++ } ++ ++ @Test ++ public void testDelete() throws Exception { ++ controller.delete(UserTestData.USER_ID); ++ Collection users = controller.getAll(); ++ Assert.assertEquals(users.size(), 1); ++ Assert.assertEquals(users.iterator().next(), ADMIN); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void testDeleteNotFound() throws Exception { ++ controller.delete(10); ++ } ++} +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1531053144000) ++++ pom.xml (date 1531053209265) +@@ -94,6 +94,12 @@ + ${junit.version} + test + ++ ++ org.springframework ++ spring-test ++ ${spring.version} ++ test ++ + + + diff --git a/3_09_add_postgresql.patch b/3_09_add_postgresql.patch new file mode 100644 index 000000000000..db71ef006aa7 --- /dev/null +++ b/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 1531053474390) ++++ src/main/resources/db/postgres.properties (date 1531053474390) +@@ -0,0 +1,7 @@ ++#database.url=jdbc:postgresql://ec2-54-247-74-197.eu-west-1.compute.amazonaws.com:5432/de4fjsqhdvl7ld?ssl=true&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 1531053254000) ++++ pom.xml (date 1531053521184) +@@ -22,6 +22,8 @@ + 1.2.3 + 1.7.25 + ++ ++ 42.2.2 + + 4.12 + +@@ -72,6 +74,13 @@ + spring-context + ${spring.version} + ++ ++ ++ ++ org.postgresql ++ postgresql ++ ${postgresql.version} ++ + + + diff --git a/3_10_db_implementation.patch b/3_10_db_implementation.patch new file mode 100644 index 000000000000..e3a6ecd162cd --- /dev/null +++ b/3_10_db_implementation.patch @@ -0,0 +1,196 @@ +Index: src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java (date 1531053669342) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java (date 1531053669342) +@@ -0,0 +1,82 @@ ++package ru.javawebinar.topjava.repository.jdbc; ++ ++import org.springframework.beans.factory.annotation.Autowired; ++import org.springframework.dao.support.DataAccessUtils; ++import org.springframework.jdbc.core.BeanPropertyRowMapper; ++import org.springframework.jdbc.core.JdbcTemplate; ++import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; ++import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; ++import org.springframework.jdbc.core.simple.SimpleJdbcInsert; ++import org.springframework.stereotype.Repository; ++import ru.javawebinar.topjava.model.User; ++import ru.javawebinar.topjava.repository.UserRepository; ++ ++import javax.sql.DataSource; ++import java.util.List; ++ ++@Repository ++public class JdbcUserRepositoryImpl implements UserRepository { ++ ++ private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); ++ ++ private final JdbcTemplate jdbcTemplate; ++ ++ private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; ++ ++ private final SimpleJdbcInsert insertUser; ++ ++ @Autowired ++ public JdbcUserRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { ++ this.insertUser = new SimpleJdbcInsert(dataSource) ++ .withTableName("users") ++ .usingGeneratedKeyColumns("id"); ++ ++ this.jdbcTemplate = jdbcTemplate; ++ this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; ++ } ++ ++ @Override ++ public User save(User user) { ++ MapSqlParameterSource map = new MapSqlParameterSource() ++ .addValue("id", user.getId()) ++ .addValue("name", user.getName()) ++ .addValue("email", user.getEmail()) ++ .addValue("password", user.getPassword()) ++ .addValue("registered", user.getRegistered()) ++ .addValue("enabled", user.isEnabled()) ++ .addValue("caloriesPerDay", user.getCaloriesPerDay()); ++ ++ if (user.isNew()) { ++ Number newKey = insertUser.executeAndReturnKey(map); ++ user.setId(newKey.intValue()); ++ } else 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: 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 1531053669358) ++++ src/main/resources/db/populateDB.sql (date 1531053669358) +@@ -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 1531053669352) ++++ src/main/resources/db/initDB.sql (date 1531053669352) +@@ -0,0 +1,25 @@ ++DROP TABLE IF EXISTS user_roles; ++DROP TABLE IF EXISTS users; ++DROP SEQUENCE IF EXISTS global_seq; ++ ++CREATE SEQUENCE global_seq START 100000; ++ ++CREATE TABLE users ++( ++ id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), ++ name VARCHAR NOT NULL, ++ email VARCHAR NOT NULL, ++ password VARCHAR NOT NULL, ++ registered TIMESTAMP DEFAULT now() NOT NULL, ++ enabled BOOL DEFAULT TRUE NOT NULL, ++ calories_per_day INTEGER DEFAULT 2000 NOT NULL ++); ++CREATE UNIQUE INDEX users_unique_email_idx ON users (email); ++ ++CREATE TABLE user_roles ++( ++ user_id INTEGER NOT NULL, ++ role VARCHAR, ++ CONSTRAINT user_roles_idx UNIQUE (user_id, role), ++ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ++); +\ No newline at end of file +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 1531053669365) ++++ src/main/resources/spring/spring-db.xml (date 1531053669365) +@@ -0,0 +1,25 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ 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 1531053555000) ++++ pom.xml (date 1531053669375) +@@ -74,6 +74,11 @@ + spring-context + ${spring.version} + ++ ++ org.springframework ++ spring-jdbc ++ ${spring.version} ++ + + + diff --git a/3_11_test_UserService.patch b/3_11_test_UserService.patch new file mode 100644 index 000000000000..881983cfb2bc --- /dev/null +++ b/3_11_test_UserService.patch @@ -0,0 +1,335 @@ +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 1531054719665) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1531054719665) +@@ -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 notFoundDelete() throws Exception { ++ service.delete(1); ++ } ++ ++ @Test ++ public void get() throws Exception { ++ User user = service.get(USER_ID); ++ assertMatch(user, USER); ++ } ++ ++ @Test(expected = NotFoundException.class) ++ public void getNotFound() throws Exception { ++ service.get(1); ++ } ++ ++ @Test ++ public void getByEmail() throws Exception { ++ User user = service.getByEmail("user@yandex.ru"); ++ assertMatch(user, USER); ++ } ++ ++ @Test ++ public void update() throws Exception { ++ User updated = new User(USER); ++ updated.setName("UpdatedName"); ++ updated.setCaloriesPerDay(330); ++ service.update(updated); ++ assertMatch(service.get(USER_ID), updated); ++ } ++ ++ @Test ++ public void getAll() throws Exception { ++ List all = service.getAll(); ++ assertMatch(all, ADMIN, USER); ++ } ++} +\ No newline at end of file +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 1531054719648) ++++ src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java (date 1531054719648) +@@ -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/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 1531054144000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java (date 1531054719601) +@@ -1,8 +1,13 @@ + package ru.javawebinar.topjava.model; + + public abstract class AbstractBaseEntity { ++ public static final int START_SEQ = 100000; ++ + protected Integer id; + ++ public AbstractBaseEntity() { ++ } ++ + protected AbstractBaseEntity(Integer id) { + this.id = id; + } +@@ -23,4 +28,22 @@ + public String toString() { + return String.format("Entity %s (%s)", getClass().getName(), id); + } ++ ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ AbstractBaseEntity that = (AbstractBaseEntity) o; ++ return id != null && id.equals(that.id); ++ } ++ ++ @Override ++ public int hashCode() { ++ return id == null ? 0 : id; ++ } + } +\ No newline at end of file +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 1531054144000) ++++ src/main/java/ru/javawebinar/topjava/model/User.java (date 1531054719640) +@@ -1,8 +1,8 @@ + package ru.javawebinar.topjava.model; + +-import java.util.Date; +-import java.util.EnumSet; +-import java.util.Set; ++import org.springframework.util.CollectionUtils; ++ ++import java.util.*; + + import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +@@ -20,17 +20,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,6 +85,10 @@ + return password; + } + ++ public void setRoles(Collection roles) { ++ this.roles = CollectionUtils.isEmpty(roles) ? Collections.emptySet() : EnumSet.copyOf(roles); ++ } ++ + @Override + public String toString() { + return "User (" + +Index: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1531054144000) ++++ pom.xml (date 1531054768609) +@@ -114,6 +114,12 @@ + ${spring.version} + test + ++ ++ org.assertj ++ assertj-core ++ 3.10.0 ++ 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 1531054144000) ++++ src/main/resources/spring/spring-app.xml (date 1531054719656) +@@ -15,7 +15,7 @@ + + + +- ++ + + + +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 1531054144000) ++++ src/test/java/ru/javawebinar/topjava/UserTestData.java (date 1531054719672) +@@ -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: 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 1531054144000) ++++ src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java (date 1531054719622) +@@ -4,6 +4,9 @@ + + protected String name; + ++ public AbstractNamedEntity() { ++ } ++ + protected AbstractNamedEntity(Integer id, String name) { + super(id); + this.name = name; diff --git a/3_12_test_logging.patch b/3_12_test_logging.patch new file mode 100644 index 000000000000..22b18e3eeae2 --- /dev/null +++ b/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 1531055033296) ++++ src/test/resources/logback-test.xml (date 1531055033296) +@@ -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/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 1531054983000) ++++ src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java (date 1531055033286) +@@ -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: src/main/resources/logback.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/main/resources/logback.xml (date 1531054983000) ++++ src/main/resources/logback.xml (date 1531055033267) +@@ -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: pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- pom.xml (date 1531054983000) ++++ pom.xml (date 1531055033308) +@@ -61,6 +61,13 @@ + compile + + ++ ++ org.slf4j ++ jul-to-slf4j ++ ${slf4j.version} ++ runtime ++ ++ + + ch.qos.logback + logback-classic diff --git a/3_13_fix_servlet.patch b/3_13_fix_servlet.patch new file mode 100644 index 000000000000..fa4b0aa5464c --- /dev/null +++ b/3_13_fix_servlet.patch @@ -0,0 +1,55 @@ +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 1531055160000) ++++ src/main/java/ru/javawebinar/topjava/web/MealServlet.java (date 1531055373733) +@@ -28,7 +28,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); + } + +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 1531055160000) ++++ src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java (date 1531055471005) +@@ -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 1531055160000) ++++ src/main/webapp/index.html (date 1531055373749) +@@ -9,8 +9,8 @@ +
+ Meals of  + + +
diff --git a/Prepare_to_HW0.patch b/Prepare_to_HW0.patch new file mode 100644 index 000000000000..c62d68e2e01d --- /dev/null +++ b/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} From e5770169f2e31c45df6e1dc4509c0dffcdb0c78c Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:22:17 +0300 Subject: [PATCH 26/44] 2 1 HW1 --- pom.xml | 6 ++ .../java/ru/javawebinar/topjava/Main.java | 11 --- .../topjava/util/DateTimeUtil.java | 17 ++++ .../javawebinar/topjava/util/MealsUtil.java | 86 +++---------------- .../ru/javawebinar/topjava/util/TimeUtil.java | 9 -- .../javawebinar/topjava/web/MealServlet.java | 79 +++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 6 +- src/main/webapp/WEB-INF/tld/functions.tld | 16 ++++ src/main/webapp/WEB-INF/web.xml | 10 +++ src/main/webapp/index.html | 1 + src/main/webapp/meals.jsp | 53 ++++++++++++ 11 files changed, 196 insertions(+), 98 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/meals.jsp diff --git a/pom.xml b/pom.xml index d2ac57ffd6f5..6f484d0beec9 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,12 @@ 3.1.0 provided
+ + + javax.servlet + jstl + 1.2 + diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java deleted file mode 100644 index cb7e35af6afa..000000000000 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.javawebinar.topjava; - -/** - * @see Demo - * @see Initial project - */ -public class Main { - public static void main(String[] args) { - System.out.format("Hello Topjava Enterprise!"); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java new file mode 100644 index 000000000000..5de28849657a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,17 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; + } + + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 209284416ff6..f94b1feec297 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -8,25 +8,22 @@ import java.time.LocalTime; import java.time.Month; import java.util.*; -import java.util.stream.Collector; +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 mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsWithExceeded.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(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); @@ -41,70 +38,11 @@ public static List getFilteredWithExceeded(List meals, Loc ); return meals.stream() - .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .filter(filter) .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) .collect(toList()); } - public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - - final Map caloriesSumByDate = new HashMap<>(); - meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); - - final List mealsWithExceeded = new ArrayList<>(); - meals.forEach(meal -> { - if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { - mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); - } - }); - return mealsWithExceeded; - } - - public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - Collection> list = meals.stream() - .collect(Collectors.groupingBy(Meal::getDate)).values(); - - return list.stream().flatMap(dayMeals -> { - boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; - return dayMeals.stream().filter(meal -> - TimeUtil.isBetween(meal.getTime(), startTime, endTime)) - .map(meal -> createWithExceed(meal, exceed)); - }).collect(toList()); - } - - public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - final class Aggregate { - private final List dailyMeals = new ArrayList<>(); - private int dailySumOfCalories; - - private void accumulate(Meal meal) { - dailySumOfCalories += meal.getCalories(); - if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { - dailyMeals.add(meal); - } - } - - // never invoked if the upstream is sequential - private Aggregate combine(Aggregate that) { - this.dailySumOfCalories += that.dailySumOfCalories; - this.dailyMeals.addAll(that.dailyMeals); - return this; - } - - private Stream finisher() { - final boolean exceed = dailySumOfCalories > caloriesPerDay; - return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); - } - } - - Collection> values = meals.stream() - .collect(Collectors.groupingBy(Meal::getDate, - Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) - ).values(); - - return values.stream().flatMap(identity()).collect(toList()); - } - public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java deleted file mode 100644 index b7eb2af6f93e..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.javawebinar.topjava.util; - -import java.time.LocalTime; - -public class TimeUtil { - public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java new file mode 100644 index 000000000000..dc509a1061d6 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,79 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; +import ru.javawebinar.topjava.util.MealsUtil; + +import 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 { + String action = request.getParameter("action"); + + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); + log.info("Delete {}", id); + repository.delete(id); + response.sendRedirect("meals"); + break; + case "create": + case "update": + final Meal meal = "create".equals(action) ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : + repository.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); + break; + case "all": + default: + log.info("getAll"); + request.setAttribute("meals", + MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.parseInt(paramId); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index ef52d67576c0..f6cf12e69976 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -15,9 +15,7 @@ public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.debug("redirect to users"); - -// request.getRequestDispatcher("/users.jsp").forward(request, response); - response.sendRedirect("users.jsp"); + log.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); } } diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld new file mode 100644 index 000000000000..d138fecdbfb5 --- /dev/null +++ b/src/main/webapp/WEB-INF/tld/functions.tld @@ -0,0 +1,16 @@ + + + + 1.0 + functions + http://topjava.javawebinar.ru/functions + + + formatDateTime + ru.javawebinar.topjava.util.DateTimeUtil + java.lang.String toString(java.time.LocalDateTime) + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 1c91ffe2cbaf..d2e475517f7e 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -16,4 +16,14 @@ /users + + mealServlet + ru.javawebinar.topjava.web.MealServlet + 0 + + + mealServlet + /meals + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 6253517f8b84..cd88b335a454 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -8,6 +8,7 @@

Проект Users +
  • Meals
  • diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 000000000000..8152fcf68c64 --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,53 @@ +<%@ 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

    + 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 From 40a02af2b1343aa5b9f0de074b8e4560f39c3462 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:22:39 +0300 Subject: [PATCH 27/44] 2 2 HW1 optional --- .../ru/javawebinar/topjava/model/Meal.java | 29 +++++++++++ .../topjava/model/MealWithExceed.java | 30 +++++++++-- .../topjava/repository/MealRepository.java | 15 ++++++ .../javawebinar/topjava/util/MealsUtil.java | 13 +++-- src/main/webapp/mealForm.jsp | 51 +++++++++++++++++++ 5 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/MealRepository.java create mode 100644 src/main/webapp/mealForm.jsp diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 943ff5cd59fa..3abbee42511e 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,6 +5,8 @@ import java.time.LocalTime; public class Meal { + private Integer id; + private final LocalDateTime dateTime; private final String description; @@ -12,11 +14,24 @@ public class Meal { private final int calories; public Meal(LocalDateTime dateTime, String description, int calories) { + this(null, dateTime, description, calories); + } + + public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -36,4 +51,18 @@ public LocalDate getDate() { public LocalTime getTime() { return dateTime.toLocalTime(); } + + public boolean isNew() { + return id == null; + } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index 4751c9e4fd69..2b375e45eecc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -3,6 +3,8 @@ import java.time.LocalDateTime; public class MealWithExceed { + private final Integer id; + private final LocalDateTime dateTime; private final String description; @@ -11,17 +13,39 @@ public class MealWithExceed { private final boolean exceed; - public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + public MealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; this.exceed = exceed; } + public Integer getId() { + return id; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExceed() { + return exceed; + } + @Override public String toString() { - return "UserMealWithExceed{" + - "dateTime=" + dateTime + + return "MealWithExceed{" + + "id=" + id + + ", dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + ", exceed=" + exceed + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java new file mode 100644 index 000000000000..e249a885c8bf --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -0,0 +1,15 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; + +import java.util.Collection; + +public interface MealRepository { + Meal save(Meal meal); + + void delete(int id); + + Meal get(int id); + + Collection getAll(); +} diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index f94b1feec297..46112186ed89 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -25,12 +25,15 @@ public class MealsUtil { public static final int DEFAULT_CALORIES_PER_DAY = 2000; - System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + public static List getWithExceeded(Collection meals, int caloriesPerDay) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> true); } - public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + public static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); + } + + private static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -44,6 +47,6 @@ public static List getFilteredWithExceeded(List meals, Loc } public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { - return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } } \ No newline at end of file diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp new file mode 100644 index 000000000000..ddc71d3c0a7a --- /dev/null +++ b/src/main/webapp/mealForm.jsp @@ -0,0 +1,51 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
    +

    Home

    +

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

    +
    + +
    + +
    +
    DateTime:
    +
    +
    +
    +
    Description:
    +
    +
    +
    +
    Calories:
    +
    +
    + + +
    +
    + + From bdbdd1dfe66c86e6055590322b89be113d7284e1 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:22:51 +0300 Subject: [PATCH 28/44] 2 3 app layers --- .../topjava/model/AbstractBaseEntity.java | 26 ++++++ .../topjava/model/AbstractNamedEntity.java | 24 +++++ .../ru/javawebinar/topjava/model/Role.java | 6 ++ .../ru/javawebinar/topjava/model/User.java | 91 +++++++++++++++++++ .../topjava/repository/UserRepository.java | 20 ++++ .../topjava/service/MealService.java | 4 + .../topjava/service/MealServiceImpl.java | 9 ++ .../topjava/service/UserService.java | 22 +++++ .../topjava/service/UserServiceImpl.java | 53 +++++++++++ .../topjava/util/ValidationUtil.java | 42 +++++++++ .../util/exception/NotFoundException.java | 7 ++ .../javawebinar/topjava/web/SecurityUtil.java | 14 +++ .../topjava/web/meal/MealRestController.java | 8 ++ .../web/user/AbstractUserController.java | 51 +++++++++++ .../topjava/web/user/AdminRestController.java | 40 ++++++++ .../web/user/ProfileRestController.java | 22 +++++ 16 files changed, 439 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/Role.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/User.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java new file mode 100644 index 000000000000..a3d71fcb46ed --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractBaseEntity { + protected Integer id; + + protected AbstractBaseEntity(Integer id) { + this.id = id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public boolean isNew() { + return this.id == null; + } + + @Override + public String toString() { + return String.format("Entity %s (%s)", getClass().getName(), id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java new file mode 100644 index 000000000000..259511dd0b65 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractNamedEntity extends AbstractBaseEntity { + + protected String name; + + protected AbstractNamedEntity(Integer id, String name) { + super(id); + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return String.format("Entity %s (%s, '%s')", getClass().getName(), id, name); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/Role.java b/src/main/java/ru/javawebinar/topjava/model/Role.java new file mode 100644 index 000000000000..84d62071ad9c --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -0,0 +1,6 @@ +package ru.javawebinar.topjava.model; + +public enum Role { + ROLE_USER, + ROLE_ADMIN +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java new file mode 100644 index 000000000000..d88e381945d8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,91 @@ +package ru.javawebinar.topjava.model; + +import java.util.Date; +import java.util.EnumSet; +import java.util.Set; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class User extends AbstractNamedEntity { + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + } + + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + super(id, name); + this.email = email; + this.password = password; + this.caloriesPerDay = caloriesPerDay; + this.enabled = enabled; + this.roles = roles; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getRegistered() { + return registered; + } + + public void setRegistered(Date registered) { + this.registered = registered; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getCaloriesPerDay() { + return caloriesPerDay; + } + + public void setCaloriesPerDay(int caloriesPerDay) { + this.caloriesPerDay = caloriesPerDay; + } + + public boolean isEnabled() { + return enabled; + } + + public Set getRoles() { + return roles; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "User (" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + + ')'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java new file mode 100644 index 000000000000..c37b84d5fd77 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +public interface UserRepository { + User save(User user); + + // false if not found + boolean delete(int id); + + // null if not found + User get(int id); + + // null if not found + User getByEmail(String email); + + List getAll(); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 000000000000..b63fc9b1df00 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,4 @@ +package ru.javawebinar.topjava.service; + +public interface MealService { +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java new file mode 100644 index 000000000000..9017380f392b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.repository.MealRepository; + +public class MealServiceImpl implements MealService { + + private MealRepository repository; + +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java new file mode 100644 index 000000000000..d0cf33e30815 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.service; + + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +public interface UserService { + + User create(User user); + + void delete(int id) throws NotFoundException; + + User get(int id) throws NotFoundException; + + User getByEmail(String email) throws NotFoundException; + + void update(User user); + + List getAll(); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java new file mode 100644 index 000000000000..c651c41d3fb9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -0,0 +1,53 @@ +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) { + checkNotFoundWithId(repository.save(user), user.getId()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java new file mode 100644 index 000000000000..cd0eec397a48 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.util; + + +import ru.javawebinar.topjava.model.AbstractBaseEntity; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +public class ValidationUtil { + + public static 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) { +// 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/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java new file mode 100644 index 000000000000..e78a4b284a9a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -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 diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java new file mode 100644 index 000000000000..ab4e8ea8bb8e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.web.meal; + +import ru.javawebinar.topjava.service.MealService; + +public class MealRestController { + private MealService service; + +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java new file mode 100644 index 000000000000..0000f1c1e02f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,51 @@ +package ru.javawebinar.topjava.web.user; + +import org.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 + private UserService service; + + public List getAll() { + log.info("getAll"); + return service.getAll(); + } + + public User get(int id) { + log.info("get {}", id); + return service.get(id); + } + + public User create(User user) { + log.info("create {}", user); + checkNew(user); + return service.create(user); + } + + public void delete(int id) { + log.info("delete {}", id); + service.delete(id); + } + + public void update(User user, int id) { + log.info("update {} with id={}", user, id); + assureIdConsistent(user, id); + service.update(user); + } + + public User getByMail(String email) { + log.info("getByEmail {}", email); + return service.getByEmail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java new file mode 100644 index 000000000000..b37a8ed6c8a5 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,40 @@ +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 + public List getAll() { + return super.getAll(); + } + + @Override + public User get(int id) { + return super.get(id); + } + + @Override + public User create(User user) { + return super.create(user); + } + + @Override + public void delete(int id) { + super.delete(id); + } + + @Override + public void update(User user, int id) { + super.update(user, id); + } + + @Override + public User getByMail(String email) { + return super.getByMail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java new file mode 100644 index 000000000000..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 From 4bc8bdc210ee64facf9924076b122546c5b30af5 Mon Sep 17 00:00:00 2001 From: Albert Date: Tue, 14 Aug 2018 12:23:03 +0300 Subject: [PATCH 29/44] 2 4 add spring context --- pom.xml | 16 ++++--- .../ru/javawebinar/topjava/SpringMain.java | 20 ++++++++ .../mock/InMemoryMealRepositoryImpl.java | 46 +++++++++++++++++++ .../mock/MockUserRepositoryImpl.java | 45 ++++++++++++++++++ src/main/resources/spring/spring-app.xml | 24 ++++++++++ 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java create mode 100644 src/main/resources/spring/spring-app.xml diff --git a/pom.xml b/pom.xml index 6f484d0beec9..c95d888b5f4b 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 5.0.7.RELEASE + 1.2.3 1.7.25 @@ -46,13 +48,6 @@ compile - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - runtime - - ch.qos.logback logback-classic @@ -60,6 +55,13 @@ runtime + + + org.springframework + spring-context + ${spring.version} + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java new file mode 100644 index 000000000000..6000def7f1c8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Arrays; + +public class SpringMain { + public static void main(String[] args) { + // java 7 Automatic resource management + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); + } + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java new file mode 100644 index 000000000000..21caea61c151 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -0,0 +1,46 @@ +package ru.javawebinar.topjava.repository.mock; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +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 void delete(int id) { + repository.remove(id); + } + + @Override + public Meal get(int id) { + return repository.get(id); + } + + @Override + public Collection getAll() { + return repository.values(); + } +} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java new file mode 100644 index 000000000000..3825d9a48654 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Collections; +import java.util.List; + +@Repository +public class MockUserRepositoryImpl implements UserRepository { + private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); + + @Override + public boolean delete(int id) { + log.info("delete {}", id); + return true; + } + + @Override + public User save(User user) { + log.info("save {}", user); + return user; + } + + @Override + public User get(int id) { + log.info("get {}", id); + return null; + } + + @Override + public List getAll() { + log.info("getAll"); + return Collections.emptyList(); + } + + @Override + public User getByEmail(String email) { + log.info("getByEmail {}", email); + return null; + } +} diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..306726024f3c --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + \ No newline at end of file From 1ffe16e012d0dc198c249762cda063f8d49967eb Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:42:46 +0300 Subject: [PATCH 30/44] 3 01 HW2 repository --- .../topjava/repository/MealRepository.java | 18 +++++--- .../mock/MockUserRepositoryImpl.java | 45 ------------------- .../topjava/util/DateTimeUtil.java | 8 +++- 3 files changed, 19 insertions(+), 52 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index e249a885c8bf..f0578ff40457 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -2,14 +2,22 @@ import ru.javawebinar.topjava.model.Meal; -import java.util.Collection; +import java.time.LocalDateTime; +import java.util.List; public interface MealRepository { - Meal save(Meal meal); + // null if updated meal do not belong to userId + Meal save(Meal meal, int userId); - void delete(int id); + // false if meal do not belong to userId + boolean delete(int id, int userId); - Meal get(int id); + // null if meal do not belong to userId + Meal get(int id, int userId); - Collection getAll(); + // ORDERED dateTime desc + List getAll(int userId); + + // ORDERED dateTime desc + List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java deleted file mode 100644 index 3825d9a48654..000000000000 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.javawebinar.topjava.repository.mock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Repository; -import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.UserRepository; - -import java.util.Collections; -import java.util.List; - -@Repository -public class MockUserRepositoryImpl implements UserRepository { - private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); - - @Override - public boolean delete(int id) { - log.info("delete {}", id); - return true; - } - - @Override - public User save(User user) { - log.info("save {}", user); - return user; - } - - @Override - public User get(int id) { - log.info("get {}", id); - return null; - } - - @Override - public List getAll() { - log.info("getAll"); - return Collections.emptyList(); - } - - @Override - public User getByEmail(String email) { - log.info("getByEmail {}", email); - return null; - } -} diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 5de28849657a..d4bb099efb86 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -7,8 +7,12 @@ 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; + // DataBase doesn't support LocalDate.MIN/MAX + public static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); + public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + + public static > boolean isBetween(T value, T start, T end) { + return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; } public static String toString(LocalDateTime ldt) { From e7b51d46c8bdfbc9fa6beedfceb89cee6d81d450 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:43:29 +0300 Subject: [PATCH 31/44] 3 02 HW2 meal layers --- .../ru/javawebinar/topjava/model/Meal.java | 18 +---- .../topjava/service/MealService.java | 23 ++++++ .../topjava/service/MealServiceImpl.java | 46 ++++++++++- .../topjava/{model => to}/MealWithExceed.java | 2 +- .../javawebinar/topjava/util/MealsUtil.java | 2 +- .../topjava/web/meal/MealRestController.java | 77 ++++++++++++++++++- src/main/webapp/meals.jsp | 35 ++++++--- .../ru/javawebinar/topjava/SpringMain.java | 14 ++++ 8 files changed, 185 insertions(+), 32 deletions(-) rename src/main/java/ru/javawebinar/topjava/{model => to}/MealWithExceed.java (96%) rename src/{main => test}/java/ru/javawebinar/topjava/SpringMain.java (57%) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 3abbee42511e..9eed15f706be 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -4,9 +4,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; -public class Meal { - private Integer id; - +public class Meal extends AbstractBaseEntity { private final LocalDateTime dateTime; private final String description; @@ -18,20 +16,12 @@ public Meal(LocalDateTime dateTime, String description, int calories) { } public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { - this.id = id; + super(id); this.dateTime = dateTime; this.description = description; this.calories = calories; } - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - public LocalDateTime getDateTime() { return dateTime; } @@ -52,10 +42,6 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } - public boolean isNew() { - return id == null; - } - @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index b63fc9b1df00..3f956057ea19 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -1,4 +1,27 @@ package ru.javawebinar.topjava.service; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + public interface MealService { + Meal get(int id, int userId) throws NotFoundException; + + void delete(int id, int userId) throws NotFoundException; + + default List getBetweenDates(LocalDate startDate, LocalDate endDate, int userId) { + return getBetweenDateTimes(LocalDateTime.of(startDate, LocalTime.MIN), LocalDateTime.of(endDate, LocalTime.MAX), userId); + } + + List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId); + + List getAll(int userId); + + Meal update(Meal meal, int userId) throws NotFoundException; + + Meal create(Meal meal, int userId); } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 9017380f392b..9be17b281396 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -1,9 +1,53 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; +import java.time.LocalDateTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +@Service public class MealServiceImpl implements MealService { - private MealRepository repository; + private final MealRepository repository; + + @Autowired + public MealServiceImpl(MealRepository repository) { + this.repository = repository; + } + + @Override + public Meal get(int id, int userId) { + return checkNotFoundWithId(repository.get(id, userId), id); + } + + @Override + public void delete(int id, int userId) { + checkNotFoundWithId(repository.delete(id, userId), id); + } + + @Override + public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return repository.getBetween(startDateTime, endDateTime, userId); + } + + @Override + public List getAll(int userId) { + return repository.getAll(userId); + } + + @Override + public Meal update(Meal meal, int userId) { + return checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + + @Override + public Meal create(Meal meal, int userId) { + return repository.save(meal, userId); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java similarity index 96% rename from src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java rename to src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java index 2b375e45eecc..c8ab125a80a4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealWithExceed.java @@ -1,4 +1,4 @@ -package ru.javawebinar.topjava.model; +package ru.javawebinar.topjava.to; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 46112186ed89..a9d97b494519 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.util; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.model.MealWithExceed; +import ru.javawebinar.topjava.to.MealWithExceed; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index ab4e8ea8bb8e..e463916d31b3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,8 +1,83 @@ 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.MealWithExceed; +import ru.javawebinar.topjava.util.DateTimeUtil; +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.getWithExceeded(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 != null ? startDate : DateTimeUtil.MIN_DATE, + endDate != null ? endDate : DateTimeUtil.MAX_DATE, userId); + return MealsUtil.getFilteredWithExceeded(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), + startTime != null ? startTime : LocalTime.MIN, + endTime != null ? endTime : LocalTime.MAX + ); + } } \ No newline at end of file diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 8152fcf68c64..3d7412b53e1e 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -2,24 +2,35 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> -<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> Meal list - +

    Home

    Meals

    +
    +
    +
    From Date:
    +
    +
    +
    +
    To Date:
    +
    +
    +
    +
    From Time:
    +
    +
    +
    +
    To Time:
    +
    +
    + +
    +
    Add Meal
    @@ -33,8 +44,8 @@ - - + +
    <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java similarity index 57% rename from src/main/java/ru/javawebinar/topjava/SpringMain.java rename to src/test/java/ru/javawebinar/topjava/SpringMain.java index 6000def7f1c8..974d494dae9d 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -4,9 +4,15 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.MealWithExceed; +import ru.javawebinar.topjava.web.meal.MealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; import java.util.Arrays; +import java.util.List; public class SpringMain { public static void main(String[] args) { @@ -15,6 +21,14 @@ public static void main(String[] args) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); + System.out.println(); + + MealRestController mealController = appCtx.getBean(MealRestController.class); + List filteredMealsWithExceeded = + mealController.getBetween( + LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), + LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); + filteredMealsWithExceeded.forEach(System.out::println); } } } From afca6c5233421fd4e28de71ee1fe37d8e4275764 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:44:13 +0300 Subject: [PATCH 32/44] 3 03 HW2 optional MealServlet --- .../javawebinar/topjava/web/MealServlet.java | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index dc509a1061d6..e378f9129f4d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,11 +1,9 @@ package ru.javawebinar.topjava.web; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; -import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.web.meal.MealRestController; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -13,14 +11,19 @@ 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 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 { @@ -31,16 +34,28 @@ public void init(ServletConfig config) throws ServletException { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); - String id = request.getParameter("id"); + String action = request.getParameter("action"); + if (action == null) { + Meal meal = new Meal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); - Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.parseInt(request.getParameter("calories"))); + if (request.getParameter("id").isEmpty()) { + mealController.create(meal); + } else { + mealController.update(meal, getId(request)); + } + response.sendRedirect("meals"); - log.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal); - response.sendRedirect("meals"); + } else if ("filter".equals(action)) { + LocalDate startDate = parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = parseLocalTime(request.getParameter("endTime")); + request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + } } @Override @@ -50,23 +65,20 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t switch (action == null ? "all" : action) { case "delete": int id = getId(request); - log.info("Delete {}", id); - repository.delete(id); + 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)); + mealController.get(getId(request)); request.setAttribute("meal", meal); request.getRequestDispatcher("/mealForm.jsp").forward(request, response); break; case "all": default: - log.info("getAll"); - request.setAttribute("meals", - MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.setAttribute("meals", mealController.getAll()); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } From e7afcc50ac9e56df353021aa8469ac545257588b Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:44:58 +0300 Subject: [PATCH 33/44] 3 04 HW2 optional filter --- .../topjava/util/DateTimeUtil.java | 11 +++++++++ src/main/webapp/css/style.css | 24 +++++++++++++++++++ src/main/webapp/mealForm.jsp | 19 +-------------- 3 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 src/main/webapp/css/style.css diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index d4bb099efb86..a1dd3144a50a 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,5 +1,8 @@ package ru.javawebinar.topjava.util; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -18,4 +21,12 @@ public static > boolean isBetween(T value, T sta public static String toString(LocalDateTime ldt) { return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); } + + public static LocalDate parseLocalDate(String str) { + return StringUtils.isEmpty(str) ? null : LocalDate.parse(str); + } + + public static LocalTime parseLocalTime(String str) { + return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); + } } diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css new file mode 100644 index 000000000000..32e4f65a8e9f --- /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-mealExceed="false"] { + color: green; +} + +tr[data-mealExceed="true"] { + color: red; +} diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp index ddc71d3c0a7a..d4509bb3417a 100644 --- a/src/main/webapp/mealForm.jsp +++ b/src/main/webapp/mealForm.jsp @@ -4,24 +4,7 @@ Meal - +
    From be06f29afd5c6c60162f283e88ca1a481c91d513 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:45:19 +0300 Subject: [PATCH 34/44] 3 05 HW2 optional select user --- src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java | 6 +++++- src/main/java/ru/javawebinar/topjava/web/UserServlet.java | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index e78a4b284a9a..0f41ca38cf79 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -5,7 +5,11 @@ public class SecurityUtil { public static int authUserId() { - return 1; + return id; + } + + public static void setAuthUserId(int id) { + SecurityUtil.id = id; } public static int authUserCaloriesPerDay() { diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index f6cf12e69976..226023400c70 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -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"); From 96d1c7e014afcb64498c098a444c2954a29ce6da Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:45:57 +0300 Subject: [PATCH 35/44] 3 07 add junit --- .../mock/InMemoryMealRepositoryImpl.java | 46 --------- .../ru/javawebinar/topjava/UserTestData.java | 29 ++++++ .../mock/InMemoryMealRepositoryImpl.java | 95 +++++++++++++++++++ .../mock/InMemoryUserRepositoryImpl.java | 64 +++++++++++++ .../web/InMemoryAdminRestControllerTest.java | 52 ++++++++++ 5 files changed, 240 insertions(+), 46 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/UserTestData.java create mode 100644 src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java deleted file mode 100644 index 21caea61c151..000000000000 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.javawebinar.topjava.repository.mock; - -import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.util.MealsUtil; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -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 void delete(int id) { - repository.remove(id); - } - - @Override - public Meal get(int id) { - return repository.get(id); - } - - @Override - public Collection getAll() { - return repository.values(); - } -} - diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java new file mode 100644 index 000000000000..3cc466c53754 --- /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.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 = START_SEQ; + public static final int ADMIN_ID = START_SEQ + 1; + + public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); + public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); + + public static void assertMatch(User actual, User expected) { + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles"); + } + + public static void assertMatch(Iterable actual, User... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java new file mode 100644 index 000000000000..d9b9aaa66afe --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -0,0 +1,95 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import org.springframework.util.CollectionUtils; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.util.DateTimeUtil; +import ru.javawebinar.topjava.util.MealsUtil; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +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.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.USER_ID; + +@Repository +public class InMemoryMealRepositoryImpl implements MealRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepositoryImpl.class); + + // Map userId -> (mealId-> meal) + private Map> repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + { + MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); + + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); + } + + + @Override + public Meal save(Meal meal, int userId) { + Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); + if (meal.isNew()) { + meal.setId(counter.incrementAndGet()); + meals.put(meal.getId(), meal); + return meal; + } + return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + } + + @PostConstruct + public void postConstruct() { + log.info("+++ PostConstruct"); + } + + @PreDestroy + public void preDestroy() { + log.info("+++ PreDestroy"); + } + + @Override + public boolean delete(int id, int userId) { + Map meals = repository.get(userId); + return meals != null && meals.remove(id) != null; + } + + @Override + public Meal get(int id, int userId) { + Map meals = repository.get(userId); + return meals == null ? null : meals.get(id); + } + + @Override + public List getAll(int userId) { + return getAllFiltered(userId, meal -> true); + } + + @Override + public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return getAllFiltered(userId, meal -> DateTimeUtil.isBetween(meal.getDateTime(), startDateTime, endDateTime)); + } + + private List getAllFiltered(int userId, Predicate filter) { + Map meals = repository.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 diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java new file mode 100644 index 000000000000..c5aa4b098217 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -0,0 +1,64 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; +import static ru.javawebinar.topjava.UserTestData.USER; + +@Repository +public class InMemoryUserRepositoryImpl implements UserRepository { + + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(100); + + public void init() { + repository.clear(); + repository.put(UserTestData.USER_ID, USER); + repository.put(UserTestData.ADMIN_ID, ADMIN); + } + + @Override + public User save(User user) { + if (user.isNew()) { + user.setId(counter.incrementAndGet()); + repository.put(user.getId(), user); + return user; + } + return repository.computeIfPresent(user.getId(), (id, oldUser) -> user); + } + + @Override + public boolean delete(int id) { + return repository.remove(id) != null; + } + + @Override + public User get(int id) { + return repository.get(id); + } + + @Override + public List getAll() { + return repository.values().stream() + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { + return repository.values().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() + .orElse(null); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java new file mode 100644 index 000000000000..aaccf3c64f05 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -0,0 +1,52 @@ +package ru.javawebinar.topjava.web; + +import org.junit.*; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl; +import ru.javawebinar.topjava.util.exception.NotFoundException; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Arrays; +import java.util.Collection; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; + +public class InMemoryAdminRestControllerTest { + private static ConfigurableApplicationContext appCtx; + private static AdminRestController controller; + + @BeforeClass + public static void beforeClass() { + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); + controller = appCtx.getBean(AdminRestController.class); + } + + @AfterClass + public static void afterClass() { + appCtx.close(); + } + + @Before + public void setUp() throws Exception { + // re-initialize + InMemoryUserRepositoryImpl repository = appCtx.getBean(InMemoryUserRepositoryImpl.class); + repository.init(); + } + + @Test + public void testDelete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { + controller.delete(10); + } +} \ No newline at end of file From 1cdaa0dd2aa4d421a6857008d4543855f7803a04 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:46:18 +0300 Subject: [PATCH 36/44] 3 08 add spring test --- pom.xml | 33 +++++++++++++ ...InMemoryAdminRestControllerSpringTest.java | 47 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java diff --git a/pom.xml b/pom.xml index c95d888b5f4b..a47c7bb94a69 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,14 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + -Dfile.encoding=UTF-8 + + @@ -75,6 +83,31 @@ jstl 1.2 + + + + junit + junit + ${junit.version} + test + + + org.springframework + spring-test + ${spring.version} + test + + + org.postgresql + postgresql + 42.2.4.jre7 + + + org.springframework + spring-jdbc + ${spring.version} + + diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java new file mode 100644 index 000000000000..9e981439f044 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl; +import ru.javawebinar.topjava.util.exception.NotFoundException; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Collection; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; + +@ContextConfiguration("classpath:spring/spring-app.xml") +@RunWith(SpringRunner.class) +public class InMemoryAdminRestControllerSpringTest { + + @Autowired + private AdminRestController controller; + + @Autowired + private InMemoryUserRepositoryImpl repository; + + @Before + public void setUp() throws Exception { + repository.init(); + } + + @Test + public void testDelete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void testDeleteNotFound() throws Exception { + controller.delete(10); + } +} From 75c63ddda21395c7f34a97146149c9735b2ceba8 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:46:35 +0300 Subject: [PATCH 37/44] 3 09 add postgresql --- pom.xml | 17 +++++++++++++++++ src/main/resources/db/postgres.properties | 7 +++++++ 2 files changed, 24 insertions(+) create mode 100644 src/main/resources/db/postgres.properties diff --git a/pom.xml b/pom.xml index a47c7bb94a69..5d15386d796b 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,11 @@ 1.2.3 1.7.25 + + + 42.2.2 + + 4.12 @@ -69,6 +74,18 @@ spring-context ${spring.version} + + org.springframework + spring-jdbc + ${spring.version} + + + + + org.postgresql + postgresql + ${postgresql.version} + diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties new file mode 100644 index 000000000000..fd8fe56209e0 --- /dev/null +++ b/src/main/resources/db/postgres.properties @@ -0,0 +1,7 @@ +#database.url=jdbc:postgresql://ec2-54-247-74-197.eu-west-1.compute.amazonaws.com:5432/de4fjsqhdvl7ld?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory +#database.username=anbxkjtzukqacj +#database.password=da1f25b2a38784fb0d46858e5b8fc168e08c9e1e9c72faea5bbac9c0e1f9c24f + +database.url=jdbc:postgresql://localhost:5432/topjava +database.username=user +database.password=password From 38467048097568d6f1a08e1bf3cebe575f2914ce Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:46:52 +0300 Subject: [PATCH 38/44] 3 10 db implementation --- .../jdbc/JdbcUserRepositoryImpl.java | 82 +++++++++++++++++++ src/main/resources/db/initDB.sql | 25 ++++++ src/main/resources/db/populateDB.sql | 11 +++ src/main/resources/spring/spring-db.xml | 25 ++++++ 4 files changed, 143 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java create mode 100644 src/main/resources/db/initDB.sql create mode 100644 src/main/resources/db/populateDB.sql create mode 100644 src/main/resources/spring/spring-db.xml diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java new file mode 100644 index 000000000000..8e7d32a087e9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -0,0 +1,82 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import javax.sql.DataSource; +import java.util.List; + +@Repository +public class JdbcUserRepositoryImpl implements UserRepository { + + private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); + + private final JdbcTemplate jdbcTemplate; + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private final SimpleJdbcInsert insertUser; + + @Autowired + public JdbcUserRepositoryImpl(DataSource dataSource, JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertUser = new SimpleJdbcInsert(dataSource) + .withTableName("users") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + @Override + public User save(User user) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", user.getId()) + .addValue("name", user.getName()) + .addValue("email", user.getEmail()) + .addValue("password", user.getPassword()) + .addValue("registered", user.getRegistered()) + .addValue("enabled", user.isEnabled()) + .addValue("caloriesPerDay", user.getCaloriesPerDay()); + + if (user.isNew()) { + Number newKey = insertUser.executeAndReturnKey(map); + user.setId(newKey.intValue()); + } else 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); + } +} diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql new file mode 100644 index 000000000000..fd40c64f5121 --- /dev/null +++ b/src/main/resources/db/initDB.sql @@ -0,0 +1,25 @@ +DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS users; +DROP SEQUENCE IF EXISTS global_seq; + +CREATE SEQUENCE global_seq START 100000; + +CREATE TABLE users +( + id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), + name VARCHAR NOT NULL, + email VARCHAR NOT NULL, + password VARCHAR NOT NULL, + registered TIMESTAMP DEFAULT now() NOT NULL, + enabled BOOL DEFAULT TRUE NOT NULL, + calories_per_day INTEGER DEFAULT 2000 NOT NULL +); +CREATE UNIQUE INDEX users_unique_email_idx ON users (email); + +CREATE TABLE user_roles +( + user_id INTEGER NOT NULL, + role VARCHAR, + CONSTRAINT user_roles_idx UNIQUE (user_id, role), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql new file mode 100644 index 000000000000..ccf86e060a68 --- /dev/null +++ b/src/main/resources/db/populateDB.sql @@ -0,0 +1,11 @@ +DELETE FROM user_roles; +DELETE FROM users; +ALTER SEQUENCE global_seq RESTART WITH 100000; + +INSERT INTO users (name, email, password) VALUES + ('User', 'user@yandex.ru', 'password'), + ('Admin', 'admin@gmail.com', 'admin'); + +INSERT INTO user_roles (role, user_id) VALUES + ('ROLE_USER', 100000), + ('ROLE_ADMIN', 100001); diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml new file mode 100644 index 000000000000..30a3733ba72d --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 024c4570105e4e69e299e1c25c4a676160e0dfec Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:47:11 +0300 Subject: [PATCH 39/44] 3 11 test UserService --- .../topjava/model/AbstractBaseEntity.java | 23 +++++ .../topjava/model/AbstractNamedEntity.java | 3 + .../ru/javawebinar/topjava/model/User.java | 24 +++-- .../jdbc/JdbcMealRepositoryImpl.java | 37 ++++++++ src/main/resources/spring/spring-app.xml | 2 +- .../topjava/service/UserServiceTest.java | 94 +++++++++++++++++++ 6 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index a3d71fcb46ed..5cd722231795 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,8 +1,13 @@ package ru.javawebinar.topjava.model; public abstract class AbstractBaseEntity { + public static final int START_SEQ = 100000; + protected Integer id; + public AbstractBaseEntity() { + } + protected AbstractBaseEntity(Integer id) { this.id = id; } @@ -23,4 +28,22 @@ public boolean isNew() { public String toString() { return String.format("Entity %s (%s)", getClass().getName(), id); } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractBaseEntity that = (AbstractBaseEntity) o; + return id != null && id.equals(that.id); + } + + @Override + public int hashCode() { + return id == null ? 0 : id; + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index 259511dd0b65..0e07e37b6ef1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -4,6 +4,9 @@ public abstract class AbstractNamedEntity extends AbstractBaseEntity { protected String name; + public AbstractNamedEntity() { + } + protected AbstractNamedEntity(Integer id, String name) { super(id); this.name = name; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index d88e381945d8..c7ec91a1ed00 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,8 +1,8 @@ package ru.javawebinar.topjava.model; -import java.util.Date; -import java.util.EnumSet; -import java.util.Set; +import org.springframework.util.CollectionUtils; + +import java.util.*; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; @@ -20,17 +20,25 @@ public class User extends AbstractNamedEntity { private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + public User() { + } + + public User(User u) { + this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.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,6 +85,10 @@ public String getPassword() { return password; } + public void setRoles(Collection roles) { + this.roles = CollectionUtils.isEmpty(roles) ? Collections.emptySet() : EnumSet.copyOf(roles); + } + @Override public String toString() { return "User (" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java new file mode 100644 index 000000000000..3e48b40b46bb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -0,0 +1,37 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class JdbcMealRepositoryImpl implements MealRepository { + + @Override + public Meal save(Meal meal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public Meal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 306726024f3c..5ae45a114f8d 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -15,7 +15,7 @@ - + diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java new file mode 100644 index 000000000000..d3b287e57091 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -0,0 +1,94 @@ +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.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 { + + static { + // Only for postgres driver logging + // It uses java.util.logging and logged via jul-to-slf4j bridge + SLF4JBridgeHandler.install(); + } + + @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 notFoundDelete() throws Exception { + service.delete(1); + } + + @Test + public void get() throws Exception { + User user = service.get(USER_ID); + assertMatch(user, USER); + } + + @Test(expected = NotFoundException.class) + public void getNotFound() throws Exception { + service.get(1); + } + + @Test + public void getByEmail() throws Exception { + User user = service.getByEmail("user@yandex.ru"); + assertMatch(user, USER); + } + + @Test + public void update() throws Exception { + User updated = new User(USER); + updated.setName("UpdatedName"); + updated.setCaloriesPerDay(330); + service.update(updated); + assertMatch(service.get(USER_ID), updated); + } + + @Test + public void getAll() throws Exception { + List all = service.getAll(); + assertMatch(all, ADMIN, USER); + } +} \ No newline at end of file From 7eb7572cfa6f63e2589e6df0861ec082a09588f7 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:47:31 +0300 Subject: [PATCH 40/44] 3 12 test logging --- pom.xml | 7 +++++++ src/main/resources/logback.xml | 4 ++-- src/test/resources/logback-test.xml | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/logback-test.xml diff --git a/pom.xml b/pom.xml index 5d15386d796b..e6c8d3e7f885 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,13 @@ compile + + org.slf4j + jul-to-slf4j + ${slf4j.version} + runtime + + ch.qos.logback logback-classic diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index e9b900b26669..c7bffc3a958c 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -9,14 +9,14 @@ UTF-8 - %date %-5level %logger{0} [%file:%line] %msg%n + %date %-5level %logger{50}.%M:%L - %msg%n UTF-8 - %-5level %logger{0} [%file:%line] %msg%n + %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 000000000000..63a3f3019bbe --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + true + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + + + + + + + + + + + + \ No newline at end of file From 0e259dfb8b1e18145eae4962aaccb97d316d97c8 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 15 Aug 2018 21:47:52 +0300 Subject: [PATCH 41/44] 3 13 fix servlet --- .../java/ru/javawebinar/topjava/web/MealServlet.java | 9 ++++++++- .../ru/javawebinar/topjava/web/SecurityUtil.java | 4 ++++ src/main/webapp/index.html | 12 ++++++++---- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index e378f9129f4d..6e936f48a34e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -28,7 +28,14 @@ public class MealServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { super.init(config); - repository = new InMemoryMealRepositoryImpl(); + springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-db.xml"); + mealController = springContext.getBean(MealRestController.class); + } + + @Override + public void destroy() { + springContext.close(); + super.destroy(); } @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index 0f41ca38cf79..588217547e60 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -1,9 +1,13 @@ 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; + public static int authUserId() { return id; } diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index cd88b335a454..886449733a86 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -6,9 +6,13 @@

    Проект Java Enterprise (Topjava)


    - +
    + Meals of  + + +
    From f0a82aaf19770753b1b0bed393bff1702b5cce28 Mon Sep 17 00:00:00 2001 From: Alb Date: Tue, 1 Jan 2019 12:21:04 +0300 Subject: [PATCH 42/44] IC-2016.1.4 + + + \ No newline at end of file diff --git a/github_settings.xml b/github_settings.xml new file mode 100644 index 000000000000..e7c0c82df80f --- /dev/null +++ b/github_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/gradle.run.settings.xml b/gradle.run.settings.xml new file mode 100644 index 000000000000..98883fd09d96 --- /dev/null +++ b/gradle.run.settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/vcs.xml b/vcs.xml new file mode 100644 index 000000000000..c8668ecab96a --- /dev/null +++ b/vcs.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/web-browsers.xml b/web-browsers.xml new file mode 100644 index 000000000000..10814a3f0fc0 --- /dev/null +++ b/web-browsers.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file From 6342ae3d9aaf960958f1c520a50eef81cf46ef01 Mon Sep 17 00:00:00 2001 From: Alb Date: Thu, 3 Jan 2019 01:08:52 +0300 Subject: [PATCH 43/44] IC-2016.1.4 + + + \ No newline at end of file diff --git a/javaeeExternalResources.xml b/javaeeExternalResources.xml new file mode 100644 index 000000000000..ed5c639d71b8 --- /dev/null +++ b/javaeeExternalResources.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 367440d8e6b58a36ffd53165e652d68b6f48c85b Mon Sep 17 00:00:00 2001 From: AlbertoJava Date: Mon, 12 Aug 2019 21:35:11 +0300 Subject: [PATCH 44/44] Delete README.md --- README.md | 195 ------------------------------------------------------ 1 file changed, 195 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 073ddf2a0e09..000000000000 --- a/README.md +++ /dev/null @@ -1,195 +0,0 @@ -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 - - [Основы 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: -- должны возвращаться только записи между startTime и endTime -- поле UserMealWithExceed.exceed должно показывать, - превышает ли сумма калорий за весь день параметра метода caloriesPerDay - -Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. - -- Проверьте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) -- Оцените Time complexity вашего алгоритма, если он O(N*N)- попробуйте сделать O(N). -``` -- Java 8 Date and Time API -- Алгоритмы и структуры данных для начинающих: сложность алгоритмов -- Time complexity -- Временная сложность алгоритма -- Вычислительная сложность - -#### Optional (Java 8 Stream API) -``` -Сделать реализацию через Java 8 Stream API. -``` -- Видео: Доступно о Java 8 Lambda -- Java 8: Lambda выражения -- Java 8: Потоки -- Pуководство по Java 8 Stream -- [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 -``` -#### Замечания по использованию 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: Не полагайтесь в решении на то, что список будет подаваться отсортированным. Такого условия нет. ------ - -### Полезные ресурсы -> ВНИМАНИЕ: -> - ДЗ первого урока будет связано с созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах. Введение будет, но предварительное знакомство не помешает. -> - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! - -Все остальное - опционально. - -#### HTML, JavaScript, CSS -- [Справочник по WEB](https://developer.mozilla.org/ru/) -- [Видео по WEB технологиям](https://www.youtube.com/user/WebMagistersRu/playlists) -- [Изучение JavaScript в одном видео уроке за час](https://www.youtube.com/watch?v=QBWWplFkdzw) -- HTML, CSS, JAVASCRIPT, SQL, JQUERY, BOOTSTRAP -- Введение в программирование на JavaScript -- Стандарты кодирования для HTML, CSS и JavaScript’a -- Основы работы с HTML/CSS/JavaScript -- JavaScript - Основы -- Основы JavaScript -- Bootstrap 3 - Основы -- jQuery для начинающих - -#### Java (базовые вещи) -- Интуит. Программирование на Java -- 1й урок MasterJava: Многопоточность -- Основы Java garbage collection -- Размер Java объектов -- Введение в Java Reflection API -- Структуры данных в картинках -- Обзор java.util.concurrent.* -- Синхронизация потоков -- String literal pool -- Маленькие хитрости Java -- A Guide to Java 8 - -### Туториалы, разное -[Что нужно знать о бэкенде новичку в веб-разработке](https://tproger.ru/translations/backend-web-development) -[Туториалы: Spring Framework, Hibernate, Java Core, JDBC](http://proselyte.net/tutorials/) - -#### Сервлеты -- Как создать Servlet? Полное руководство. - -#### 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