From 87c4880a074c3cf874371070be1a6ced3fbb4bc6 Mon Sep 17 00:00:00 2001 From: JavaOPs Date: Mon, 14 May 2018 18:22:49 +0300 Subject: [PATCH 01/55] 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/55] 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/55] 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/55] 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/55] 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/55] 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/55] 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 8aaa38499761bcf58114a4d60f045f29f4f10a6d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 20 Aug 2018 19:39:54 +0300 Subject: [PATCH 08/55] Update ReleaseNotes.md --- ReleaseNotes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 2e8fc212c740..04347c90404e 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,9 @@ # TopJava Release Notes +### Topjava 14 +- [Миграция на JUnit 5](https://www.youtube.com/watch?v=YmLzT-j1hU4) +- `SimpleJdbcInsert` и `NamedParameterJdbcTemplate` конструируются (и берут настройки) из `jdbcTemplate` +- `AuthorizedUser` зарефакторился в `SecurityUtil` + ### Topjava 13 - [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) - Добавил [Responsive behaviors](https://getbootstrap.com/docs/4.1/components/navbar/#responsive-behaviors) - при уменшении ширины экрана навигация сворачивается в кнопку From bf7e38608929b65989d5630539b9396cd3ce6d3e Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 30 Aug 2018 02:58:56 +0300 Subject: [PATCH 09/55] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 073ddf2a0e09..3b9c5d2882bc 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Java Enterprise Online Project Все остальное - опционально. #### HTML, JavaScript, CSS +- [Basic HTML and HTML5](https://learn.freecodecamp.org/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements/) - [Справочник по WEB](https://developer.mozilla.org/ru/) - [Видео по WEB технологиям](https://www.youtube.com/user/WebMagistersRu/playlists) - [Изучение JavaScript в одном видео уроке за час](https://www.youtube.com/watch?v=QBWWplFkdzw) From aecf64ed856b5ef3dac0e96c67c2feaab4b2bb65 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 31 Aug 2018 18:50:44 +0300 Subject: [PATCH 10/55] Update ReleaseNotes.md --- ReleaseNotes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 04347c90404e..27412755456e 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -3,6 +3,8 @@ - [Миграция на JUnit 5](https://www.youtube.com/watch?v=YmLzT-j1hU4) - `SimpleJdbcInsert` и `NamedParameterJdbcTemplate` конструируются (и берут настройки) из `jdbcTemplate` - `AuthorizedUser` зарефакторился в `SecurityUtil` +- В javascript [заменил `var` на `let/const`](https://learn.javascript.ru/let-const). [Поддержка 95% браузеров](https://caniuse.com/#feat=const) +- Подправил UI фильтрации и заголовка страниц ### Topjava 13 - [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) From 09f8849eaf0510031341bde3c5036ddbd07ec66e Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 6 Sep 2018 01:59:26 +0300 Subject: [PATCH 11/55] Update ReleaseNotes.md --- ReleaseNotes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 27412755456e..30e4a5655602 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,10 +1,13 @@ # TopJava Release Notes ### Topjava 14 - [Миграция на JUnit 5](https://www.youtube.com/watch?v=YmLzT-j1hU4) +- Для измерения времени в тестах использую [Spring StopWatch](https://www.logicbig.com/how-to/code-snippets/jcode-spring-framework-stopwatch.html) - `SimpleJdbcInsert` и `NamedParameterJdbcTemplate` конструируются (и берут настройки) из `jdbcTemplate` - `AuthorizedUser` зарефакторился в `SecurityUtil` - В javascript [заменил `var` на `let/const`](https://learn.javascript.ru/let-const). [Поддержка 95% браузеров](https://caniuse.com/#feat=const) - Подправил UI фильтрации и заголовка страниц +- Починил [баг в FireFox](https://bugzilla.mozilla.org/show_bug.cgi?id=884693): пустой ответ по ajax +- Сделал вход в приложение при нажании кнопок `Зайти как ...` ### Topjava 13 - [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) From 042d9adbd4b79ca7f42a08e0b2262abfd522fde4 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 11 Sep 2018 23:39:20 +0300 Subject: [PATCH 12/55] Update ReleaseNotes.md --- ReleaseNotes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 30e4a5655602..56c9b680998f 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -5,9 +5,11 @@ - `SimpleJdbcInsert` и `NamedParameterJdbcTemplate` конструируются (и берут настройки) из `jdbcTemplate` - `AuthorizedUser` зарефакторился в `SecurityUtil` - В javascript [заменил `var` на `let/const`](https://learn.javascript.ru/let-const). [Поддержка 95% браузеров](https://caniuse.com/#feat=const) -- Подправил UI фильтрации и заголовка страниц +- Подправил UI фильтрации и заголовка страниц, добавилась кнопка `Cancel` в профиль - Починил [баг в FireFox](https://bugzilla.mozilla.org/show_bug.cgi?id=884693): пустой ответ по ajax - Сделал вход в приложение при нажании кнопок `Зайти как ...` +- Добавил регистрацию пользователя по REST +- Преименовал js файлы согласно [javascript filename naming convention](https://stackoverflow.com/questions/7273316/what-is-the-javascript-filename-naming-convention) ### Topjava 13 - [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) From 0f2e401b9fad7c5835767f82c4b8a81e53de6357 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 12 Sep 2018 16:03:15 +0300 Subject: [PATCH 13/55] Update ReleaseNotes.md --- ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 56c9b680998f..c38eddb1fda2 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -10,6 +10,7 @@ - Сделал вход в приложение при нажании кнопок `Зайти как ...` - Добавил регистрацию пользователя по REST - Преименовал js файлы согласно [javascript filename naming convention](https://stackoverflow.com/questions/7273316/what-is-the-javascript-filename-naming-convention) +- Сделал проверку startTime/endTime на фильтре времени (после обновления datetimepicker до 2.5.20) ### Topjava 13 - [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) From d800d8adf7424a9616555649b975690ed0b1b917 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 19 Sep 2018 13:07:00 +0300 Subject: [PATCH 14/55] Update cv.md --- cv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cv.md b/cv.md index 7046f27aac99..e78b7faedc2b 100644 --- a/cv.md +++ b/cv.md @@ -60,7 +60,7 @@ - удобно иметь резюме где то в инете (hh, linkedin, google doc, чтобы им было удобно делиться). ### Позиционирование проекта Topjava: -- Обязательно убери из резюме **любое упоминание Junior**. Количество обращений возрастет на порядок. Ссылку на стажировку можно поставить: http://javaops.ru (в linkedin: https://www.linkedin.com/company/java-online-projects). +- Обязательно убери из резюме **любое упоминание Junior**. Количество обращений возрастет на порядок. Ссылку на стажировку можно поставить: http://javaops.ru/view/topjava (в linkedin: https://www.linkedin.com/company/java-online-projects). - После завершения проекта ты освошь все заявленные в нем технологии - вставь их в квалификацию (включая java 8 Stream and Time API). - В разделе опыт работы (если нет коммерческого опыта) вставь: From b449f9337645a6be5043a0e719e30d4465cd6052 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 26 Sep 2018 17:07:40 +0300 Subject: [PATCH 15/55] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3b9c5d2882bc..7f9ae7ef93f0 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ Java Enterprise Online Project - Курс "Введение в базы данных" #### Разное +- Вопросы по собеседованию, ресурсы для подготовки - Эффективная работа с кодом в IntelliJ IDEA - Quizful- тесты онлайн - Введение в Linux From 0a3e251daf12709e2952aa7436adf43b18a3f1d0 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 26 Sep 2018 17:52:32 +0300 Subject: [PATCH 16/55] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7f9ae7ef93f0..38557b68c8ea 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ Java Enterprise Online Project - 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) From 98e873bacb1cf88fa65df465f761b13b1f730bfc Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 27 Sep 2018 01:27:04 +0300 Subject: [PATCH 17/55] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38557b68c8ea..040b1995e86e 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Java Enterprise Online Project ``` Сделать реализацию со сложностью O(N): - циклом за 1 проход по List. Обратите внимание на п.13 замечаний -- через Stream API за 1 проход по полному списку Stream +- через Stream API за 1 проход по полному списку Stream + возможно дополнительные проходы по частям списка ``` #### Замечания по использованию Stream API: - Когда встречаешь что-то непривычное, приходится перестраивать мозги. Например, переход с процедурного на ООП программирование дается непросто. Те, кто не знает шаблонов (и не хотят учить) также их встречают плохо. Хорошая новость в том, что если это принять и начать использовать, то начинаешь получать от этого удовольствие. И тут главное не впасть в другую крайность: From 84d25ac593eeff7dd6a910ffad3b895be19eaf17 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 27 Sep 2018 10:03:23 +0300 Subject: [PATCH 18/55] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index c2b0bcb1d3b8..a6333bcb6dd6 100644 --- a/graduation.md +++ b/graduation.md @@ -1,4 +1,4 @@ -## Тестовое задание на оплачиваемую стажировку +## Тестовое задание Design and implement a REST API using Hibernate/Spring/SpringMVC (or Spring-Boot) **without frontend**. From d398d3f64ea68c7872f90afbec3723d0d8385891 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 27 Sep 2018 10:03:37 +0300 Subject: [PATCH 19/55] Update graduation.md --- graduation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graduation.md b/graduation.md index a6333bcb6dd6..1a38bb78e347 100644 --- a/graduation.md +++ b/graduation.md @@ -1,5 +1,4 @@ -## Тестовое задание - +## Выпускной проект Design and implement a REST API using Hibernate/Spring/SpringMVC (or Spring-Boot) **without frontend**. The task is: From 99b8f979a2199c278407bd33f818737455100e67 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 30 Sep 2018 23:12:45 +0300 Subject: [PATCH 20/55] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 040b1995e86e..4183a09d949b 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,9 @@ Java Enterprise Online Project ``` Сделать реализацию со сложностью O(N): - циклом за 1 проход по List. Обратите внимание на п.13 замечаний -- через Stream API за 1 проход по полному списку Stream + возможно дополнительные проходы по частям списка +- через Stream API за 1 проход по исходному списку Stream meals + - возможно дополнительные проходы по частям списка + - нельзя использовать внешние коллекции, не являющиеся частью коллектора или результатами работы stream ``` #### Замечания по использованию Stream API: - Когда встречаешь что-то непривычное, приходится перестраивать мозги. Например, переход с процедурного на ООП программирование дается непросто. Те, кто не знает шаблонов (и не хотят учить) также их встречают плохо. Хорошая новость в том, что если это принять и начать использовать, то начинаешь получать от этого удовольствие. И тут главное не впасть в другую крайность: From 61e46651bc6a5eb6acf28ca8a6361a2e99800e7b Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Fri, 5 Oct 2018 20:17:12 +0700 Subject: [PATCH 21/55] Prepare to HW0 --- pom.xml | 2 +- .../java/ru/javawebinar/topjava/Main.java | 7 ++--- .../javawebinar/topjava/model/UserMeal.java | 29 +++++++++++++++++ .../topjava/model/UserMealWithExceed.java | 20 ++++++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 9 ++++++ .../topjava/util/UserMealsUtil.java | 31 +++++++++++++++++++ 6 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMeal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/pom.xml b/pom.xml index c8a1c78f3b29..e3b8143e3e63 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.7.0 ${java.version} ${java.version} diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java index b23a2f0961fc..cb7e35af6afa 100644 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ b/src/main/java/ru/javawebinar/topjava/Main.java @@ -1,11 +1,8 @@ package ru.javawebinar.topjava; /** - * User: gkislin - * Date: 05.08.2015 - * - * @link http://caloriesmng.herokuapp.com/ - * @link https://github.com/JavaOPs/topjava + * @see Demo + * @see Initial project */ public class Main { public static void main(String[] args) { diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java new file mode 100644 index 000000000000..d8f91b127f6a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -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; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java new file mode 100644 index 000000000000..3fe62373ac11 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java @@ -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; + } +} 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; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java new file mode 100644 index 000000000000..6ba2f4e47935 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -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; + } +} From be39428ebb71df7ee18d5258b09726e459e1b371 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:10:01 +0700 Subject: [PATCH 22/55] 1_0_fix --- .../model/{UserMeal.java => Meal.java} | 4 +-- ...ealWithExceed.java => MealWithExceed.java} | 4 +-- .../javawebinar/topjava/util/MealsUtil.java | 31 +++++++++++++++++++ .../topjava/util/UserMealsUtil.java | 31 ------------------- 4 files changed, 35 insertions(+), 35 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{UserMeal.java => Meal.java} (83%) rename src/main/java/ru/javawebinar/topjava/model/{UserMealWithExceed.java => MealWithExceed.java} (72%) create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java similarity index 83% rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java rename to src/main/java/ru/javawebinar/topjava/model/Meal.java index d8f91b127f6a..f546cef0f74a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -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/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java similarity index 72% rename from src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java rename to src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index 3fe62373ac11..c8e0d713c217 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -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 @@ public class UserMealWithExceed { private final boolean exceed; - public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + public MealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { this.dateTime = dateTime; this.description = description; this.calories = calories; diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java new file mode 100644 index 000000000000..7517a65dd834 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,31 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealWithExceed; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +public class MealsUtil { + public static void main(String[] args) { + List mealList = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) + ); + getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +// .toLocalDate(); +// .toLocalTime(); + } + + public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO return filtered list with correctly exceeded field + return null; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java deleted file mode 100644 index 6ba2f4e47935..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ /dev/null @@ -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; - } -} From 22a4e838f2cc4b455f9f6960e9acdb9ea1f4c21f Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:10:34 +0700 Subject: [PATCH 23/55] 1_1_HW0 --- .../ru/javawebinar/topjava/model/Meal.java | 10 +++++ .../topjava/model/MealWithExceed.java | 12 +++++- .../javawebinar/topjava/util/MealsUtil.java | 38 ++++++++++++------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index f546cef0f74a..943ff5cd59fa 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; public class Meal { private final LocalDateTime dateTime; @@ -26,4 +28,12 @@ public String getDescription() { public int getCalories() { return calories; } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java index c8e0d713c217..4751c9e4fd69 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java @@ -17,4 +17,14 @@ public MealWithExceed(LocalDateTime dateTime, String description, int calories, this.calories = calories; this.exceed = exceed; } -} + + @Override + public String toString() { + return "UserMealWithExceed{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", exceed=" + exceed + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 7517a65dd834..2143f7f49faa 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -3,29 +3,41 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.model.MealWithExceed; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class MealsUtil { public static void main(String[] args) { - List mealList = Arrays.asList( - new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) + List meals = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) ); - getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); -// .toLocalDate(); -// .toLocalTime(); + List mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExceeded.forEach(System.out::println); } - public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with correctly exceeded field - return null; + public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) + ); + + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> + new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), + caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); } } \ No newline at end of file From 812cf4c6744481a5b72d9470a786ee10e6e6d537 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:11:59 +0700 Subject: [PATCH 24/55] 1_2_HW0 --- .../javawebinar/topjava/util/MealsUtil.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 2143f7f49faa..2ef2768e90bc 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -7,9 +7,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class MealsUtil { @@ -24,6 +22,8 @@ public static void main(String[] args) { ); List mealsWithExceeded = getFilteredWithExceeded(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); mealsWithExceeded.forEach(System.out::println); + + System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -35,9 +35,25 @@ public static List getFilteredWithExceeded(List meals, Loc return meals.stream() .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) - .map(meal -> - new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), - caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) .collect(Collectors.toList()); } + + public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + + final Map caloriesSumByDate = new HashMap<>(); + meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); + + final List mealsWithExceeded = new ArrayList<>(); + meals.forEach(meal -> { + if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { + mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + }); + return mealsWithExceeded; + } + + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { + return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); + } } \ No newline at end of file From 1a9a73f6abc1c7db31ce58473e2cf231e1b82d45 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:12:23 +0700 Subject: [PATCH 25/55] 1_3_HW0 --- .../javawebinar/topjava/util/MealsUtil.java | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 2ef2768e90bc..209284416ff6 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -8,7 +8,12 @@ import java.time.LocalTime; import java.time.Month; import java.util.*; +import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; public class MealsUtil { public static void main(String[] args) { @@ -24,6 +29,8 @@ public static void main(String[] args) { mealsWithExceeded.forEach(System.out::println); System.out.println(getFilteredWithExceededByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExceededInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -36,7 +43,7 @@ public static List getFilteredWithExceeded(List meals, Loc return meals.stream() .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) - .collect(Collectors.toList()); + .collect(toList()); } public static List getFilteredWithExceededByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -53,6 +60,51 @@ public static List getFilteredWithExceededByCycle(List mea return mealsWithExceeded; } + public static List getFilteredWithExceededInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Collection> list = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate)).values(); + + return list.stream().flatMap(dayMeals -> { + boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; + return dayMeals.stream().filter(meal -> + TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExceed(meal, exceed)); + }).collect(toList()); + } + + public static List getFilteredWithExceededInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final class Aggregate { + private final List dailyMeals = new ArrayList<>(); + private int dailySumOfCalories; + + private void accumulate(Meal meal) { + dailySumOfCalories += meal.getCalories(); + if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { + dailyMeals.add(meal); + } + } + + // never invoked if the upstream is sequential + private Aggregate combine(Aggregate that) { + this.dailySumOfCalories += that.dailySumOfCalories; + this.dailyMeals.addAll(that.dailyMeals); + return this; + } + + private Stream finisher() { + final boolean exceed = dailySumOfCalories > caloriesPerDay; + return dailyMeals.stream().map(meal -> createWithExceed(meal, exceed)); + } + } + + Collection> values = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate, + Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) + ).values(); + + return values.stream().flatMap(identity()).collect(toList()); + } + public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) { return new MealWithExceed(meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } From fe423596fefc5b24da4d536eb669bcbfa1210383 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:12:41 +0700 Subject: [PATCH 26/55] 1_4 --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 19 +++++++++++++++++++ src/main/webapp/index.html | 13 +++++++++++++ src/main/webapp/users.jsp | 10 ++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index e3b8143e3e63..2e87eaf4e859 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..1c91ffe2cbaf --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + Topjava + + + userServlet + ru.javawebinar.topjava.web.UserServlet + 0 + + + userServlet + /users + + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..6253517f8b84 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,13 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

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

Home

+

Users

+ + \ No newline at end of file From ce16b26aa79d8d7a61f2c8d3ab2fddfd8b96e614 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:13:04 +0700 Subject: [PATCH 27/55] 1_5 --- pom.xml | 8 ++++++++ .../ru/javawebinar/topjava/web/UserServlet.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/pom.xml b/pom.xml index 2e87eaf4e859..abd5d2a6b91e 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,14 @@ + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java new file mode 100644 index 000000000000..76056e06c019 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -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); + } +} From 68997ad08d3b35dcdd2f2315492aea9553e3acb7 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:13:31 +0700 Subject: [PATCH 28/55] 1_6 --- pom.xml | 2 +- src/main/java/ru/javawebinar/topjava/web/UserServlet.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index abd5d2a6b91e..010a0c933e95 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ topjava - install + package org.apache.maven.plugins diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 76056e06c019..11f282bac482 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -10,6 +10,7 @@ public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - request.getRequestDispatcher("/users.jsp").forward(request, response); +// request.getRequestDispatcher("/users.jsp").forward(request, response); + response.sendRedirect("users.jsp"); } } From c51278a7da27d0efa09a775c89d920ed2b5e533a Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Thu, 18 Oct 2018 20:13:50 +0700 Subject: [PATCH 29/55] 1_7 --- pom.xml | 25 ++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 7 +++++ src/main/resources/logback.xml | 29 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 010a0c933e95..d2ac57ffd6f5 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ 1.8 UTF-8 UTF-8 + + + 1.2.3 + 1.7.25 @@ -34,6 +38,27 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 11f282bac482..ef52d67576c0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,15 +1,22 @@ package ru.javawebinar.topjava.web; +import org.slf4j.Logger; + import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import static org.slf4j.LoggerFactory.getLogger; + public class UserServlet extends HttpServlet { + private static final Logger log = getLogger(UserServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("redirect to users"); + // request.getRequestDispatcher("/users.jsp").forward(request, response); response.sendRedirect("users.jsp"); } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..e9b900b26669 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From e67975c2499acc4ffbb53b3d899df20a61067061 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Sun, 21 Oct 2018 18:04:06 +0700 Subject: [PATCH 30/55] 2_1_HW1.patch 2_2_HW1_optional.patch --- pom.xml | 6 ++ .../java/ru/javawebinar/topjava/Main.java | 11 --- .../ru/javawebinar/topjava/model/Meal.java | 29 ++++++ .../topjava/model/MealWithExceed.java | 30 +++++- .../InMemoryMealRepositoryImpl.java | 45 +++++++++ .../topjava/repository/MealRepository.java | 15 +++ .../topjava/util/DateTimeUtil.java | 17 ++++ .../javawebinar/topjava/util/MealsUtil.java | 99 ++++--------------- .../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/mealForm.jsp | 51 ++++++++++ src/main/webapp/meals.jsp | 49 +++++++++ 16 files changed, 367 insertions(+), 106 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/MealRepository.java create mode 100644 src/main/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/mealForm.jsp 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/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/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java new file mode 100644 index 000000000000..9c63d2aac7f7 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java @@ -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(); + } +} + 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/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..46112186ed89 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -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; + + public static List getWithExceeded(Collection meals, int caloriesPerDay) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> true); + } - 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(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExceeded(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); } - public static List getFilteredWithExceeded(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + private static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -41,71 +41,12 @@ 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); + return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java 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..c68ab837aa89 --- /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.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 { + 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/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:
    +
    +
    + + +
    +
    + + diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 000000000000..1ad145136ceb --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,49 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> + + + Meal list + + + +
    +

    Home

    +

    Meals

    + Add Meal +
    + + + + + + + + + + + + + + + + + + + + +
    DateDescriptionCalories
    + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}UpdateDelete
    +
    + + \ No newline at end of file From 96ac2f845d36c5569772e103cb8764ee767cc477 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Sun, 21 Oct 2018 18:04:42 +0700 Subject: [PATCH 31/55] 2_3_app_layer.patch --- .../topjava/model/AbstractBaseEntity.java | 26 ++++++ .../topjava/model/AbstractNamedEntity.java | 24 +++++ .../ru/javawebinar/topjava/model/Role.java | 6 ++ .../ru/javawebinar/topjava/model/User.java | 91 +++++++++++++++++++ .../topjava/repository/UserRepository.java | 20 ++++ .../topjava/service/MealService.java | 4 + .../topjava/service/MealServiceImpl.java | 9 ++ .../topjava/service/UserService.java | 22 +++++ .../topjava/service/UserServiceImpl.java | 45 +++++++++ .../topjava/util/ValidationUtil.java | 42 +++++++++ .../util/exception/NotFoundException.java | 7 ++ .../javawebinar/topjava/web/SecurityUtil.java | 14 +++ .../topjava/web/meal/MealRestController.java | 8 ++ .../web/user/AbstractUserController.java | 49 ++++++++++ .../topjava/web/user/AdminRestController.java | 38 ++++++++ .../web/user/ProfileRestController.java | 20 ++++ 16 files changed, 425 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..a6740fcb0f64 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +public class UserServiceImpl implements UserService { + + private UserRepository repository; + + @Override + public User create(User user) { + return repository.save(user); + } + + @Override + public void delete(int id) throws NotFoundException { + checkNotFoundWithId(repository.delete(id), id); + } + + @Override + public User get(int id) throws NotFoundException { + return checkNotFoundWithId(repository.get(id), id); + } + + @Override + public User getByEmail(String email) throws NotFoundException { + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + + @Override + public List getAll() { + return repository.getAll(); + } + + @Override + public void update(User user) { + 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..16a68259ec20 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,49 @@ +package ru.javawebinar.topjava.web.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.UserService; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +public abstract class AbstractUserController { + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private UserService service; + + public List getAll() { + log.info("getAll"); + return service.getAll(); + } + + public User get(int id) { + log.info("get {}", id); + return service.get(id); + } + + public User create(User user) { + log.info("create {}", user); + checkNew(user); + return service.create(user); + } + + public void delete(int id) { + log.info("delete {}", id); + service.delete(id); + } + + public void update(User user, int id) { + log.info("update {} with id={}", user, id); + assureIdConsistent(user, id); + service.update(user); + } + + public User getByMail(String email) { + log.info("getByEmail {}", email); + return service.getByEmail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java new file mode 100644 index 000000000000..ae3374468e41 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +public class AdminRestController extends AbstractUserController { + + @Override + public List getAll() { + return super.getAll(); + } + + @Override + public User get(int id) { + return super.get(id); + } + + @Override + public User create(User user) { + return super.create(user); + } + + @Override + public void delete(int id) { + super.delete(id); + } + + @Override + public void update(User user, int id) { + super.update(user, id); + } + + @Override + public User getByMail(String email) { + return super.getByMail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java new file mode 100644 index 000000000000..b5062e20bc90 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -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 From 3794d55b79748e414575d2483fa8b03967c9de79 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Sun, 21 Oct 2018 18:36:26 +0700 Subject: [PATCH 32/55] 2_4_add_spring_context.patch --- pom.xml | 16 ++++--- .../ru/javawebinar/topjava/SpringMain.java | 19 ++++++++ .../InMemoryMealRepositoryImpl.java | 3 +- .../mock/MockUserRepositoryImpl.java | 43 +++++++++++++++++++ .../javawebinar/topjava/web/MealServlet.java | 2 +- src/main/resources/spring/spring-app.xml | 7 +++ 6 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.java rename src/main/java/ru/javawebinar/topjava/repository/{ => mock}/InMemoryMealRepositoryImpl.java (91%) 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..57c6d0770d00 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 5.1.0.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..bb4a84fa88b4 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Arrays; + +public class SpringMain { + public static void main(String[] args) { + ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + +// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); + UserRepository userRepository = appCtx.getBean(UserRepository.class); + userRepository.getAll(); + appCtx.close(); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java similarity index 91% rename from src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java rename to src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 9c63d2aac7f7..21caea61c151 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -1,6 +1,7 @@ -package ru.javawebinar.topjava.repository; +package ru.javawebinar.topjava.repository.mock; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; import java.util.Collection; diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java new file mode 100644 index 000000000000..0825c7b69afa --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Collections; +import java.util.List; + +public class MockUserRepositoryImpl implements UserRepository { + private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); + + @Override + public boolean delete(int id) { + log.info("delete {}", id); + return true; + } + + @Override + public User save(User user) { + log.info("save {}", user); + return user; + } + + @Override + public User get(int id) { + log.info("get {}", id); + return null; + } + + @Override + public List getAll() { + log.info("getAll"); + return Collections.emptyList(); + } + + @Override + public User getByEmail(String email) { + log.info("getByEmail {}", email); + return null; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index c68ab837aa89..dc509a1061d6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -3,8 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.mock.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.ServletConfig; diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 000000000000..e2e1f0314c0d --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file From ab3fc968179ef2458071a49a0b9f0b8bd9e3ba26 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Sun, 21 Oct 2018 18:45:17 +0700 Subject: [PATCH 33/55] 2_5_dependency_injection_context.patch --- src/main/java/ru/javawebinar/topjava/SpringMain.java | 7 +++++++ .../ru/javawebinar/topjava/service/UserServiceImpl.java | 4 ++++ src/main/resources/spring/spring-app.xml | 3 +++ 3 files changed, 14 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index bb4a84fa88b4..cb9b82e63160 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -2,7 +2,10 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.service.UserService; import java.util.Arrays; @@ -14,6 +17,10 @@ public static void main(String[] args) { // UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); UserRepository userRepository = appCtx.getBean(UserRepository.class); userRepository.getAll(); + + UserService userService = appCtx.getBean(UserService.class); + userService.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); + appCtx.close(); } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index a6740fcb0f64..ca2fd85c883b 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -13,6 +13,10 @@ public class UserServiceImpl implements UserService { private UserRepository repository; + public void setRepository(UserRepository repository) { + this.repository = repository; + } + @Override public User create(User user) { return repository.save(user); diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index e2e1f0314c0d..7b4fa1689425 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -4,4 +4,7 @@ + + + \ No newline at end of file From dc05c3cf9eaad1163a39016af5716237485103e6 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Sun, 21 Oct 2018 18:46:27 +0700 Subject: [PATCH 34/55] 2_6_annotation_processing.patch --- .../ru/javawebinar/topjava/SpringMain.java | 20 ++++++---------- .../mock/MockUserRepositoryImpl.java | 2 ++ .../topjava/service/UserServiceImpl.java | 8 +++---- .../web/user/AbstractUserController.java | 2 ++ .../topjava/web/user/AdminRestController.java | 2 ++ .../web/user/ProfileRestController.java | 2 ++ src/main/resources/spring/spring-app.xml | 24 +++++++++++++++---- 7 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index cb9b82e63160..6000def7f1c8 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -4,23 +4,17 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.UserRepository; -import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.web.user.AdminRestController; import java.util.Arrays; public class SpringMain { public static void main(String[] args) { - ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); - System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); - -// UserRepository userRepository = (UserRepository) appCtx.getBean("mockUserRepository"); - UserRepository userRepository = appCtx.getBean(UserRepository.class); - userRepository.getAll(); - - UserService userService = appCtx.getBean(UserService.class); - userService.create(new User(null, "userName", "email@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)); + } } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java index 0825c7b69afa..3825d9a48654 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java @@ -2,12 +2,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import java.util.Collections; import java.util.List; +@Repository public class MockUserRepositoryImpl implements UserRepository { private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index ca2fd85c883b..b3acc32fe671 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -9,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); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index 16a68259ec20..0000f1c1e02f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; @@ -13,6 +14,7 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); + @Autowired private UserService service; public List getAll() { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index ae3374468e41..b37a8ed6c8a5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,9 +1,11 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.model.User; import java.util.List; +@Controller public class AdminRestController extends AbstractUserController { @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index b5062e20bc90..7d3702c31c46 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -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() { diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 7b4fa1689425..fbce9605d464 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -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 From 2ee65ad4d6fb54274c73cde4b172abb46ebf817a Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Sun, 21 Oct 2018 18:48:09 +0700 Subject: [PATCH 35/55] 2_7_constructor_injection.patch --- .../ru/javawebinar/topjava/service/UserServiceImpl.java | 6 +++++- src/main/resources/spring/spring-app.xml | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index b3acc32fe671..c651c41d3fb9 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -14,8 +14,12 @@ @Service public class UserServiceImpl implements UserService { + private final UserRepository repository; + @Autowired - private UserRepository repository; + public UserServiceImpl(UserRepository repository) { + this.repository = repository; + } @Override public User create(User user) { diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index fbce9605d464..306726024f3c 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -5,11 +5,11 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> From 639355b8a24000ce2dd9f0820a56d364f2ba287c Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Sun, 21 Oct 2018 20:59:21 +0700 Subject: [PATCH 36/55] 3_0_1_switch_servlet_4.patch --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 57c6d0770d00..791cfd20ee0f 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ javax.servlet javax.servlet-api - 3.1.0 + 4.0.0 provided diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index d2e475517f7e..bd98d3bf3f6a 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -1,8 +1,8 @@ + http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" + version="4.0"> Topjava From 10e29df836c2c6b1eb693f33fe21a6ae33943b0b Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:50:30 +0700 Subject: [PATCH 37/55] 3_0_2_correct_meal_exceed --- .../model/{MealWithExceed.java => MealTo.java} | 16 ++++++++-------- .../ru/javawebinar/topjava/util/MealsUtil.java | 18 +++++++++--------- .../javawebinar/topjava/web/MealServlet.java | 2 +- src/main/webapp/meals.jsp | 6 +++--- 4 files changed, 21 insertions(+), 21 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{MealWithExceed.java => MealTo.java} (72%) diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java similarity index 72% rename from src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java rename to src/main/java/ru/javawebinar/topjava/model/MealTo.java index 2b375e45eecc..3e502d41f3af 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -2,7 +2,7 @@ import java.time.LocalDateTime; -public class MealWithExceed { +public class MealTo { private final Integer id; private final LocalDateTime dateTime; @@ -11,14 +11,14 @@ public class MealWithExceed { private final int calories; - private final boolean exceed; + private final boolean excess; - public MealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) { + public MealTo(Integer id, LocalDateTime dateTime, String description, int calories, boolean excess) { this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; - this.exceed = exceed; + this.excess = excess; } public Integer getId() { @@ -37,18 +37,18 @@ public int getCalories() { return calories; } - public boolean isExceed() { - return exceed; + public boolean isExcess() { + return excess; } @Override public String toString() { - return "MealWithExceed{" + + return "MealTo{" + "id=" + id + ", dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + - ", exceed=" + exceed + + ", excess=" + excess + '}'; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 46112186ed89..c89d60f02716 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.model.MealTo; import java.time.LocalDate; import java.time.LocalDateTime; @@ -25,15 +25,15 @@ public class MealsUtil { 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 getWithExcess(Collection meals, int caloriesPerDay) { + return getFilteredWithExcess(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)); + public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); } - private static List getFilteredWithExceeded(Collection meals, int caloriesPerDay, Predicate filter) { + private static List getFilteredWithExcess(Collection meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -42,11 +42,11 @@ private static List getFilteredWithExceeded(Collection mea return meals.stream() .filter(filter) - .map(meal -> createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .map(meal -> createWithExcess(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); + public static MealTo createWithExcess(Meal meal, boolean Excess) { + return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), Excess); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index dc509a1061d6..94ac9d328bc9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -66,7 +66,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t default: log.info("getAll"); request.setAttribute("meals", - MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + MealsUtil.getWithExcess(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 1ad145136ceb..e198b6101606 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -10,7 +10,7 @@ color: green; } - .exceeded { + .excess { color: red; } @@ -32,8 +32,8 @@ - - + + ${fn:formatDateTime(meal.dateTime)} From 970a642222b2b7c60d887d2eaa6cbbad9edff4a5 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:51:07 +0700 Subject: [PATCH 38/55] 3_02_HW2_meal_layers --- .../ru/javawebinar/topjava/SpringMain.java | 14 ++++ .../ru/javawebinar/topjava/model/Meal.java | 18 +---- .../topjava/repository/MealRepository.java | 18 +++-- .../mock/InMemoryMealRepositoryImpl.java | 62 +++++++++++---- .../mock/InMemoryUserRepositoryImpl.java | 57 ++++++++++++++ .../mock/MockUserRepositoryImpl.java | 45 ----------- .../topjava/service/MealService.java | 23 ++++++ .../topjava/service/MealServiceImpl.java | 45 ++++++++++- .../topjava/{model => to}/MealTo.java | 2 +- .../topjava/util/DateTimeUtil.java | 8 +- .../javawebinar/topjava/util/MealsUtil.java | 4 +- .../ru/javawebinar/topjava/util/Util.java | 7 ++ .../javawebinar/topjava/web/MealServlet.java | 10 +-- .../topjava/web/meal/MealRestController.java | 77 ++++++++++++++++++- src/main/webapp/meals.jsp | 2 +- 15 files changed, 297 insertions(+), 95 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java delete mode 100644 src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java rename src/main/java/ru/javawebinar/topjava/{model => to}/MealTo.java (96%) create mode 100644 src/main/java/ru/javawebinar/topjava/util/Util.java diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 6000def7f1c8..a8ac8cb4ad53 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -4,9 +4,15 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.web.meal.MealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; import java.util.Arrays; +import java.util.List; public class SpringMain { public static void main(String[] args) { @@ -15,6 +21,14 @@ 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 filteredMealsWithExcess = + mealController.getBetween( + LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), + LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); + filteredMealsWithExcess.forEach(System.out::println); } } } diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 3abbee42511e..9eed15f706be 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -4,9 +4,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; -public class Meal { - private Integer id; - +public class Meal extends AbstractBaseEntity { private final LocalDateTime dateTime; private final String description; @@ -18,20 +16,12 @@ public Meal(LocalDateTime dateTime, String description, int calories) { } public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { - this.id = id; + super(id); this.dateTime = dateTime; this.description = description; this.calories = calories; } - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - public LocalDateTime getDateTime() { return dateTime; } @@ -52,10 +42,6 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } - public boolean isNew() { - return id == null; - } - @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index e249a885c8bf..f0578ff40457 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -2,14 +2,22 @@ import ru.javawebinar.topjava.model.Meal; -import java.util.Collection; +import java.time.LocalDateTime; +import java.util.List; public interface MealRepository { - Meal save(Meal meal); + // null if updated meal do not belong to userId + Meal save(Meal meal, int userId); - void delete(int id); + // false if meal do not belong to userId + boolean delete(int id, int userId); - Meal get(int id); + // null if meal do not belong to userId + Meal get(int id, int userId); - Collection getAll(); + // ORDERED dateTime desc + List getAll(int userId); + + // ORDERED dateTime desc + List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 21caea61c151..69a0b6973ed5 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -1,46 +1,80 @@ 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; import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.Util; -import java.util.Collection; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.ADMIN_ID; +import static ru.javawebinar.topjava.repository.mock.InMemoryUserRepositoryImpl.USER_ID; + +@Repository 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 -> Util.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/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java new file mode 100644 index 000000000000..d413d56e2d59 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java @@ -0,0 +1,57 @@ +package ru.javawebinar.topjava.repository.mock; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Repository +public class InMemoryUserRepositoryImpl implements UserRepository { + + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + @Override + public User save(User user) { + if (user.isNew()) { + user.setId(counter.incrementAndGet()); + repository.put(user.getId(), user); + return user; + } + return repository.computeIfPresent(user.getId(), (id, oldUser) -> user); + } + + @Override + public boolean delete(int id) { + return repository.remove(id) != null; + } + + @Override + public User get(int id) { + return repository.get(id); + } + + @Override + public List getAll() { + return repository.values().stream() + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); + } + + @Override + public User getByEmail(String email) { + return repository.values().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() + .orElse(null); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java deleted file mode 100644 index 3825d9a48654..000000000000 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/MockUserRepositoryImpl.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.javawebinar.topjava.repository.mock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Repository; -import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.UserRepository; - -import java.util.Collections; -import java.util.List; - -@Repository -public class MockUserRepositoryImpl implements UserRepository { - private static final Logger log = LoggerFactory.getLogger(MockUserRepositoryImpl.class); - - @Override - public boolean delete(int id) { - log.info("delete {}", id); - return true; - } - - @Override - public User save(User user) { - log.info("save {}", user); - return user; - } - - @Override - public User get(int id) { - log.info("get {}", id); - return null; - } - - @Override - public List getAll() { - log.info("getAll"); - return Collections.emptyList(); - } - - @Override - public User getByEmail(String email) { - log.info("getByEmail {}", email); - return null; - } -} diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index b63fc9b1df00..31e36c5d291c 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); + + void 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..66371879e614 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -1,9 +1,52 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; +import java.time.LocalDateTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +@Service public class MealServiceImpl implements MealService { - private MealRepository repository; + private final MealRepository repository; + + @Autowired + public MealServiceImpl(MealRepository repository) { + this.repository = repository; + } + + @Override + public Meal get(int id, int userId) { + return checkNotFoundWithId(repository.get(id, userId), id); + } + + @Override + public void delete(int id, int userId) { + checkNotFoundWithId(repository.delete(id, userId), id); + } + + @Override + public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return repository.getBetween(startDateTime, endDateTime, userId); + } + + @Override + public List getAll(int userId) { + return repository.getAll(userId); + } + + @Override + public void update(Meal meal, int userId) { + 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/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java similarity index 96% rename from src/main/java/ru/javawebinar/topjava/model/MealTo.java rename to src/main/java/ru/javawebinar/topjava/to/MealTo.java index 3e502d41f3af..eedce0f2f75c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -1,4 +1,4 @@ -package ru.javawebinar.topjava.model; +package ru.javawebinar.topjava.to; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 5de28849657a..4f2bcb8f5d80 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,15 +1,15 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class DateTimeUtil { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - 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 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 c89d60f02716..b73f33b68298 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.MealTo; +import ru.javawebinar.topjava.to.MealTo; import java.time.LocalDate; import java.time.LocalDateTime; @@ -30,7 +30,7 @@ public static List getWithExcess(Collection meals, int caloriesPer } public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { - return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); + return getFilteredWithExcess(meals, caloriesPerDay, meal -> Util.isBetween(meal.getTime(), startTime, endTime)); } private static List getFilteredWithExcess(Collection meals, int caloriesPerDay, Predicate filter) { diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java new file mode 100644 index 000000000000..7ea89146f056 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.util; + +public class Util { + public static > boolean isBetween(T value, T start, T end) { + return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 94ac9d328bc9..637e59633c03 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -39,7 +39,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Integer.parseInt(request.getParameter("calories"))); log.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal); + repository.save(meal, SecurityUtil.authUserId()); response.sendRedirect("meals"); } @@ -50,15 +50,15 @@ 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); + 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 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t default: log.info("getAll"); request.setAttribute("meals", - MealsUtil.getWithExcess(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + MealsUtil.getWithExcess(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } 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..5bf56b31d407 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.MealTo; +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.getWithExcess(service.getAll(userId), SecurityUtil.authUserCaloriesPerDay()); + } + + public Meal create(Meal meal) { + int userId = SecurityUtil.authUserId(); + checkNew(meal); + log.info("create {} for user {}", meal, userId); + return service.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = SecurityUtil.authUserId(); + assureIdConsistent(meal, id); + log.info("update {} for user {}", meal, userId); + service.update(meal, userId); + } + + /** + *
      Filter separately + *
    1. by date
    2. + *
    3. by time for every date
    4. + *
    + */ + public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { + int userId = SecurityUtil.authUserId(); + log.info("getBetween dates({} - {}) time({} - {}) for user {}", startDate, endDate, startTime, endTime, userId); + + List mealsDateFiltered = service.getBetweenDates( + startDate != null ? startDate : DateTimeUtil.MIN_DATE, + endDate != null ? endDate : DateTimeUtil.MAX_DATE, userId); + return MealsUtil.getFilteredWithExcess(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 e198b6101606..a5d04fe7b63c 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -32,7 +32,7 @@ - + ${fn:formatDateTime(meal.dateTime)} From 638a4968ca5e0a591b70e680af9bb9cfd509e517 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:52:03 +0700 Subject: [PATCH 39/55] 3_03 --- .../javawebinar/topjava/web/MealServlet.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 637e59633c03..b8d0865eb19f 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; @@ -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 @@ 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, SecurityUtil.authUserId()); + mealController.delete(id); response.sendRedirect("meals"); break; case "create": case "update": final Meal meal = "create".equals(action) ? new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : - repository.get(getId(request), SecurityUtil.authUserId()); + mealController.get(getId(request)); request.setAttribute("meal", meal); request.getRequestDispatcher("/mealForm.jsp").forward(request, response); break; case "all": default: - log.info("getAll"); - request.setAttribute("meals", - MealsUtil.getWithExcess(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.setAttribute("meals", mealController.getAll()); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } From 2baba8d048f02d14dab54fa39494d470327f4de6 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:52:16 +0700 Subject: [PATCH 40/55] 3_04_HW2_optional_filter --- .../topjava/util/DateTimeUtil.java | 11 ++++++ .../javawebinar/topjava/web/MealServlet.java | 34 ++++++++++++++----- src/main/webapp/css/style.css | 24 +++++++++++++ src/main/webapp/mealForm.jsp | 19 +---------- src/main/webapp/meals.jsp | 32 +++++++++++------ 5 files changed, 83 insertions(+), 37 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 4f2bcb8f5d80..9c317b0f47fb 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,7 +1,10 @@ package ru.javawebinar.topjava.util; +import org.springframework.util.StringUtils; + import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class DateTimeUtil { @@ -14,4 +17,12 @@ public class DateTimeUtil { public static String toString(LocalDateTime ldt) { return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); } + + public static LocalDate parseLocalDate(String str) { + return StringUtils.isEmpty(str) ? null : LocalDate.parse(str); + } + + public static LocalTime parseLocalTime(String str) { + return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index b8d0865eb19f..1392da7ab89d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -11,10 +11,15 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.Objects; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; + public class MealServlet extends HttpServlet { private ConfigurableApplicationContext springContext; @@ -36,17 +41,28 @@ public void destroy() { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); - Meal meal = new Meal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.parseInt(request.getParameter("calories"))); + String action = request.getParameter("action"); + if (action == null) { + Meal meal = new Meal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); + + if (request.getParameter("id").isEmpty()) { + mealController.create(meal); + } else { + mealController.update(meal, getId(request)); + } + response.sendRedirect("meals"); - if (request.getParameter("id").isEmpty()) { - mealController.create(meal); - } else { - mealController.update(meal, getId(request)); + } else if ("filter".equals(action)) { + LocalDate startDate = parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = parseLocalTime(request.getParameter("endTime")); + request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); } - response.sendRedirect("meals"); } @Override diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css new file mode 100644 index 000000000000..26a14ce43e6d --- /dev/null +++ b/src/main/webapp/css/style.css @@ -0,0 +1,24 @@ +dl { + background: none repeat scroll 0 0 #FAFAFA; + margin: 8px 0; + padding: 0; +} + +dt { + display: inline-block; + width: 170px; +} + +dd { + display: inline-block; + margin-left: 8px; + vertical-align: top; +} + +tr[data-mealExcess="false"] { + color: green; +} + +tr[data-mealExcess="true"] { + color: red; +} diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp index ddc71d3c0a7a..d4509bb3417a 100644 --- a/src/main/webapp/mealForm.jsp +++ b/src/main/webapp/mealForm.jsp @@ -4,24 +4,7 @@ Meal - +
    diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index a5d04fe7b63c..a2e6291eca3d 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -5,20 +5,32 @@ Meal list - +

    Home

    Meals

    +
    +
    +
    From Date:
    +
    +
    +
    +
    To Date:
    +
    +
    +
    +
    From Time:
    +
    +
    +
    +
    To Time:
    +
    +
    + +
    +
    Add Meal
    @@ -33,7 +45,7 @@ - + From ccc694fed88e6b9bf65af87ec861a44257efc391 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:52:28 +0700 Subject: [PATCH 41/55] 3_05_HW2_optional_select_user --- .../ru/javawebinar/topjava/web/SecurityUtil.java | 8 +++++++- .../java/ru/javawebinar/topjava/web/UserServlet.java | 7 +++++++ src/main/webapp/index.html | 12 ++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index e78a4b284a9a..b9639bf1b9ea 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -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() { 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"); diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index cd88b335a454..e68aaa192cd3 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -6,9 +6,13 @@

    Проект Java Enterprise (Topjava)


    - +
    + Meals of  + + + From 60b46353bac520ab9b9f529ea9d185e0e6b3d82d Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:52:43 +0700 Subject: [PATCH 42/55] 3_06_bean_life_cycle --- .../mock/InMemoryMealRepositoryImpl.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java index 69a0b6973ed5..062afe62c302 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java @@ -1,5 +1,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.MealsUtil; import ru.javawebinar.topjava.util.Util; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.time.LocalDateTime; import java.time.Month; import java.util.Collections; @@ -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 @@ public Meal save(Meal meal, int userId) { return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); } + @PostConstruct + public void postConstruct() { + log.info("+++ PostConstruct"); + } + + @PreDestroy + public void preDestroy() { + log.info("+++ PreDestroy"); + } + @Override public boolean delete(int id, int userId) { Map meals = repository.get(userId); From 1e9b1e8593d94de36ef87416c25f03745118db7b Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:52:58 +0700 Subject: [PATCH 43/55] 3_07_add_junit --- pom.xml | 19 +++++++ .../ru/javawebinar/topjava/UserTestData.java | 12 +++++ .../web/InMemoryAdminRestControllerTest.java | 52 +++++++++++++++++++ .../ru/javawebinar/topjava/SpringMain.java | 0 .../inmemory}/InMemoryMealRepositoryImpl.java | 6 +-- .../inmemory}/InMemoryUserRepositoryImpl.java | 17 ++++-- 6 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/UserTestData.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java rename src/{main => test}/java/ru/javawebinar/topjava/SpringMain.java (100%) rename src/{main/java/ru/javawebinar/topjava/repository/mock => test/java/ru/javawebinar/topjava/repository/inmemory}/InMemoryMealRepositoryImpl.java (93%) rename src/{main/java/ru/javawebinar/topjava/repository/mock => test/java/ru/javawebinar/topjava/repository/inmemory}/InMemoryUserRepositoryImpl.java (77%) diff --git a/pom.xml b/pom.xml index 791cfd20ee0f..8bf24b9f57e8 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ 1.2.3 1.7.25 + + + 4.12 @@ -36,6 +39,14 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + -Dfile.encoding=UTF-8 + + @@ -75,6 +86,14 @@ jstl 1.2 + + + + junit + junit + ${junit.version} + test + diff --git a/src/main/java/ru/javawebinar/topjava/UserTestData.java b/src/main/java/ru/javawebinar/topjava/UserTestData.java new file mode 100644 index 000000000000..196b09362719 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/UserTestData.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +public class UserTestData { + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); + public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); +} diff --git a/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java new file mode 100644 index 000000000000..fdfdb1b4cab2 --- /dev/null +++ b/src/main/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.inmemory.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 delete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void deleteNotFound() throws Exception { + controller.delete(10); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java similarity index 100% rename from src/main/java/ru/javawebinar/topjava/SpringMain.java rename to src/test/java/ru/javawebinar/topjava/SpringMain.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java similarity index 93% rename from src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 062afe62c302..f7b4048a3c3e 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -1,4 +1,4 @@ -package ru.javawebinar.topjava.repository.mock; +package ru.javawebinar.topjava.repository.inmemory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -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/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java similarity index 77% rename from src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java index d413d56e2d59..138181b8526d 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -1,6 +1,7 @@ -package ru.javawebinar.topjava.repository.mock; +package ru.javawebinar.topjava.repository.inmemory; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; @@ -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) { From cb919009ee53006331f1c2e69e39d9b933f39949 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:58:31 +0700 Subject: [PATCH 44/55] 3_08_add_spring_test --- pom.xml | 6 ++++++ .../topjava/web/InMemoryAdminRestControllerTest.java | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8bf24b9f57e8..8cb03547de6f 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,12 @@ ${junit.version} test + + org.testng + testng + RELEASE + compile + diff --git a/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java index fdfdb1b4cab2..89c840b9fe81 100644 --- a/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ b/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java @@ -5,7 +5,6 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl; import ru.javawebinar.topjava.util.exception.NotFoundException; import ru.javawebinar.topjava.web.user.AdminRestController; From 33ca1c3337d602255ad4b8361d0dcd3025e8af4d Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:58:46 +0700 Subject: [PATCH 45/55] 3_09_add_postgresql --- pom.xml | 9 +++++++++ src/main/resources/db/postgres.properties | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 src/main/resources/db/postgres.properties diff --git a/pom.xml b/pom.xml index 8cb03547de6f..9594941650e2 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,8 @@ 1.2.3 1.7.25 + + 42.2.5 4.12 @@ -73,6 +75,13 @@ ${spring.version} + + + org.postgresql + postgresql + ${postgresql.version} + + javax.servlet diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties new file mode 100644 index 000000000000..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 b75a7b26b5950842af7ba819a3c86fd691989789 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:58:59 +0700 Subject: [PATCH 46/55] 3_10_db_implementation --- pom.xml | 5 ++ .../jdbc/JdbcUserRepositoryImpl.java | 81 +++++++++++++++++++ src/main/resources/db/initDB.sql | 25 ++++++ src/main/resources/db/populateDB.sql | 11 +++ src/main/resources/spring/spring-db.xml | 25 ++++++ 5 files changed, 147 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java create mode 100644 src/main/resources/db/initDB.sql create mode 100644 src/main/resources/db/populateDB.sql create mode 100644 src/main/resources/spring/spring-db.xml diff --git a/pom.xml b/pom.xml index 9594941650e2..d8e9a3197aab 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,11 @@ spring-context ${spring.version} + + org.springframework + spring-jdbc + ${spring.version} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java new file mode 100644 index 000000000000..47b50564fb7f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -0,0 +1,81 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.List; + +@Repository +public class 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(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertUser = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("users") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + @Override + public User save(User user) { + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", user.getId()) + .addValue("name", user.getName()) + .addValue("email", user.getEmail()) + .addValue("password", user.getPassword()) + .addValue("registered", user.getRegistered()) + .addValue("enabled", user.isEnabled()) + .addValue("caloriesPerDay", user.getCaloriesPerDay()); + + if (user.isNew()) { + Number newKey = insertUser.executeAndReturnKey(map); + user.setId(newKey.intValue()); + } else if (namedParameterJdbcTemplate.update( + "UPDATE users SET name=:name, email=:email, password=:password, " + + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map) == 0) { + return null; + } + return user; + } + + @Override + public boolean delete(int id) { + return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0; + } + + @Override + public User get(int id) { + List users = jdbcTemplate.query("SELECT * FROM users WHERE id=?", ROW_MAPPER, id); + return DataAccessUtils.singleResult(users); + } + + @Override + public User getByEmail(String email) { +// return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + return DataAccessUtils.singleResult(users); + } + + @Override + public List getAll() { + return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + } +} 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..8ec56c9e63f0 --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From db0e1981655b6831d9c47a638cdd903011e2e75a Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:59:12 +0700 Subject: [PATCH 47/55] 3_11_test_UserService --- pom.xml | 6 ++ .../ru/javawebinar/topjava/UserTestData.java | 21 ++++- .../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 | 87 +++++++++++++++++++ 8 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java diff --git a/pom.xml b/pom.xml index d8e9a3197aab..7f3d51d4d18f 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,12 @@ RELEASE compile + + org.assertj + assertj-core + 3.11.1 + test + diff --git a/src/main/java/ru/javawebinar/topjava/UserTestData.java b/src/main/java/ru/javawebinar/topjava/UserTestData.java index 196b09362719..3cc466c53754 100644 --- a/src/main/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/main/java/ru/javawebinar/topjava/UserTestData.java @@ -3,10 +3,27 @@ import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + public class UserTestData { - public static final int USER_ID = 1; - public static final int ADMIN_ID = 2; + public static final int USER_ID = START_SEQ; + public static final int ADMIN_ID = START_SEQ + 1; public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); + + public static void assertMatch(User actual, User expected) { + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles"); + } + + public static void assertMatch(Iterable actual, User... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); + } } diff --git a/src/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..dc9ad49482f3 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -0,0 +1,87 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static ru.javawebinar.topjava.UserTestData.*; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +public class UserServiceTest { + + @Autowired + private UserService service; + + @Test + public void create() throws Exception { + User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.ROLE_USER)); + User created = service.create(newUser); + newUser.setId(created.getId()); + assertMatch(service.getAll(), ADMIN, newUser, USER); + } + + @Test(expected = DataAccessException.class) + public void duplicateMailCreate() throws Exception { + service.create(new User(null, "Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER)); + } + + @Test + public void delete() throws Exception { + service.delete(USER_ID); + assertMatch(service.getAll(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void deletedNotFound() throws Exception { + service.delete(1); + } + + @Test + public void get() throws Exception { + User user = service.get(USER_ID); + assertMatch(user, USER); + } + + @Test(expected = NotFoundException.class) + public void getNotFound() throws Exception { + service.get(1); + } + + @Test + public void getByEmail() throws Exception { + User user = service.getByEmail("user@yandex.ru"); + assertMatch(user, USER); + } + + @Test + public void update() throws Exception { + User updated = new User(USER); + updated.setName("UpdatedName"); + updated.setCaloriesPerDay(330); + service.update(updated); + assertMatch(service.get(USER_ID), updated); + } + + @Test + public void getAll() throws Exception { + List all = service.getAll(); + assertMatch(all, ADMIN, USER); + } +} \ No newline at end of file From 4cdaacea41910aefe1e06672e0236b219cb555d8 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:59:24 +0700 Subject: [PATCH 48/55] 3_12_test_logging --- pom.xml | 7 ++++++ src/main/resources/logback.xml | 4 ++-- .../topjava/service/UserServiceTest.java | 7 ++++++ src/test/resources/logback-test.xml | 22 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/logback-test.xml diff --git a/pom.xml b/pom.xml index 7f3d51d4d18f..96dccc03ad69 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/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index dc9ad49482f3..03bfc5f2e6ac 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -2,6 +2,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ContextConfiguration; @@ -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; 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 2ae5b5a5816674d010e72f43dac40d09875300d4 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 19:59:36 +0700 Subject: [PATCH 49/55] 3_13_fix_servlet --- src/main/java/ru/javawebinar/topjava/web/MealServlet.java | 2 +- src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java | 4 +++- src/main/webapp/index.html | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 1392da7ab89d..6e936f48a34e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -28,7 +28,7 @@ public class MealServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { super.init(config); - springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-db.xml"); mealController = springContext.getBean(MealRestController.class); } diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index b9639bf1b9ea..588217547e60 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -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; diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index e68aaa192cd3..886449733a86 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -9,8 +9,8 @@

    Проект Meals of  From 51875df68ba442cb0f182150a3da4f46b2e5eec3 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 20:02:07 +0700 Subject: [PATCH 50/55] 3_13_fix_servlet --- .../web/InMemoryAdminRestControllerTest.java | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java deleted file mode 100644 index 89c840b9fe81..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ /dev/null @@ -1,51 +0,0 @@ -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.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 delete() throws Exception { - controller.delete(UserTestData.USER_ID); - Collection users = controller.getAll(); - Assert.assertEquals(users.size(), 1); - Assert.assertEquals(users.iterator().next(), ADMIN); - } - - @Test(expected = NotFoundException.class) - public void deleteNotFound() throws Exception { - controller.delete(10); - } -} \ No newline at end of file From 970e1aa28af6bb6115122ccafa87a9e4932088ce Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 20:12:56 +0700 Subject: [PATCH 51/55] 4_1_HW3.patch --- .../ru/javawebinar/topjava/model/Meal.java | 21 ++++++- .../jdbc/JdbcMealRepositoryImpl.java | 60 +++++++++++++++++-- .../jdbc/JdbcUserRepositoryImpl.java | 15 ++--- src/main/resources/db/initDB.sql | 14 ++++- src/main/resources/db/populateDB.sql | 11 ++++ 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 9eed15f706be..1c1e67b15a3b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,11 +5,14 @@ import java.time.LocalTime; public class Meal extends AbstractBaseEntity { - private final LocalDateTime dateTime; + private LocalDateTime dateTime; - private final String description; + private String description; - private final int calories; + private int calories; + + public Meal() { + } public Meal(LocalDateTime dateTime, String description, int calories) { this(null, dateTime, description, calories); @@ -42,6 +45,18 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } + public void setDateTime(LocalDateTime dateTime) { + this.dateTime = dateTime; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setCalories(int calories) { + this.calories = calories; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index 3e48b40b46bb..80bbdb581de1 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -1,5 +1,13 @@ package ru.javawebinar.topjava.repository.jdbc; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -10,28 +18,70 @@ @Repository public class JdbcMealRepositoryImpl implements MealRepository { + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); + + private final JdbcTemplate jdbcTemplate; + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private final SimpleJdbcInsert insertMeal; + + @Autowired + public JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + @Override public Meal save(Meal meal, int userId) { - return null; + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", meal.getId()) + .addValue("description", meal.getDescription()) + .addValue("calories", meal.getCalories()) + .addValue("date_time", meal.getDateTime()) + .addValue("user_id", userId); + + if (meal.isNew()) { + Number newId = insertMeal.executeAndReturnKey(map); + meal.setId(newId.intValue()); + } else { + if (namedParameterJdbcTemplate.update("" + + "UPDATE meals " + + " SET description=:description, calories=:calories, date_time=:date_time " + + " WHERE id=:id AND user_id=:user_id" + , map) == 0) { + return null; + } + } + return meal; } @Override public boolean delete(int id, int userId) { - return false; + return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; } @Override public Meal get(int id, int userId) { - return null; + List meals = jdbcTemplate.query( + "SELECT * FROM meals WHERE id = ? AND user_id = ?", ROW_MAPPER, id, userId); + return DataAccessUtils.singleResult(meals); } @Override public List getAll(int userId) { - return null; + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? ORDER BY date_time DESC", ROW_MAPPER, userId); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC", + ROW_MAPPER, userId, startDate, endDate); } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java index 47b50564fb7f..fde6581c6be4 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -4,7 +4,7 @@ import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; @@ -36,21 +36,14 @@ public JdbcUserRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTempl @Override public User save(User user) { - MapSqlParameterSource map = new MapSqlParameterSource() - .addValue("id", user.getId()) - .addValue("name", user.getName()) - .addValue("email", user.getEmail()) - .addValue("password", user.getPassword()) - .addValue("registered", user.getRegistered()) - .addValue("enabled", user.isEnabled()) - .addValue("caloriesPerDay", user.getCaloriesPerDay()); + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); if (user.isNew()) { - Number newKey = insertUser.executeAndReturnKey(map); + Number newKey = insertUser.executeAndReturnKey(parameterSource); user.setId(newKey.intValue()); } else if (namedParameterJdbcTemplate.update( "UPDATE users SET name=:name, email=:email, password=:password, " + - "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map) == 0) { + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { return null; } return user; diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index fd40c64f5121..f87f5b274e85 100644 --- a/src/main/resources/db/initDB.sql +++ b/src/main/resources/db/initDB.sql @@ -1,4 +1,5 @@ DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS meals; DROP TABLE IF EXISTS users; DROP SEQUENCE IF EXISTS global_seq; @@ -22,4 +23,15 @@ CREATE TABLE user_roles role VARCHAR, CONSTRAINT user_roles_idx UNIQUE (user_id, role), FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE -); \ No newline at end of file +); + +CREATE TABLE meals ( + id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), + user_id INTEGER NOT NULL, + date_time TIMESTAMP NOT NULL, + description TEXT NOT NULL, + calories INT NOT NULL, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx + ON meals (user_id, date_time); \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index ccf86e060a68..29a97efdc2b8 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -1,4 +1,5 @@ DELETE FROM user_roles; +DELETE FROM meals; DELETE FROM users; ALTER SEQUENCE global_seq RESTART WITH 100000; @@ -9,3 +10,13 @@ INSERT INTO users (name, email, password) VALUES INSERT INTO user_roles (role, user_id) VALUES ('ROLE_USER', 100000), ('ROLE_ADMIN', 100001); + +INSERT INTO meals (date_time, description, calories, user_id) +VALUES ('2015-05-30 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-30 13:00:00', 'Обед', 1000, 100000), + ('2015-05-30 20:00:00', 'Ужин', 500, 100000), + ('2015-05-31 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-31 13:00:00', 'Обед', 1000, 100000), + ('2015-05-31 20:00:00', 'Ужин', 510, 100000), + ('2015-06-01 14:00:00', 'Админ ланч', 510, 100001), + ('2015-06-01 21:00:00', 'Админ ужин', 1500, 100001); From 02a29d5e169539adeab84ebd9a5b50efea61cdc5 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 20:13:15 +0700 Subject: [PATCH 52/55] 4_2_HW3_optional.patch --- .../javawebinar/topjava/util/MealsUtil.java | 14 +-- .../ru/javawebinar/topjava/MealTestData.java | 47 ++++++++++ .../inmemory/InMemoryMealRepositoryImpl.java | 13 --- .../topjava/service/MealServiceTest.java | 88 +++++++++++++++++++ 4 files changed, 138 insertions(+), 24 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/MealTestData.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index b73f33b68298..c9967c3bed63 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -4,24 +4,16 @@ import ru.javawebinar.topjava.to.MealTo; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.Month; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; public class MealsUtil { - public static final List MEALS = Arrays.asList( - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) - ); public static final int DEFAULT_CALORIES_PER_DAY = 2000; diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java new file mode 100644 index 000000000000..d94faebff1c4 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Meal; + +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +import static java.time.LocalDateTime.of; +import static org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class MealTestData { + public static final int MEAL1_ID = START_SEQ + 2; + public static final int ADMIN_MEAL_ID = START_SEQ + 8; + + public static final Meal MEAL1 = new Meal(MEAL1_ID, of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500); + public static final Meal MEAL2 = new Meal(MEAL1_ID + 1, of(2015, Month.MAY, 30, 13, 0), "Обед", 1000); + public static final Meal MEAL3 = new Meal(MEAL1_ID + 2, of(2015, Month.MAY, 30, 20, 0), "Ужин", 500); + public static final Meal MEAL4 = new Meal(MEAL1_ID + 3, of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500); + public static final Meal MEAL5 = new Meal(MEAL1_ID + 4, of(2015, Month.MAY, 31, 13, 0), "Обед", 1000); + public static final Meal MEAL6 = new Meal(MEAL1_ID + 5, of(2015, Month.MAY, 31, 20, 0), "Ужин", 510); + public static final Meal ADMIN_MEAL1 = new Meal(ADMIN_MEAL_ID, of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510); + public static final Meal ADMIN_MEAL2 = new Meal(ADMIN_MEAL_ID + 1, of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500); + + public static final List MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + + public static Meal getCreated() { + return new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Созданный ужин", 300); + } + + public static Meal getUpdated() { + return new Meal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); + } + + public static void assertMatch(Meal actual, Meal expected) { + assertThat(actual).isEqualToComparingFieldByField(expected); + } + + public static void assertMatch(Iterable actual, Meal... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingFieldByFieldElementComparator().isEqualTo(expected); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index f7b4048a3c3e..5a49c3c92816 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -6,13 +6,11 @@ import org.springframework.util.CollectionUtils; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.util.MealsUtil; import ru.javawebinar.topjava.util.Util; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDateTime; -import java.time.Month; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -22,9 +20,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; -import static ru.javawebinar.topjava.UserTestData.USER_ID; - @Repository public class InMemoryMealRepositoryImpl implements MealRepository { private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepositoryImpl.class); @@ -33,14 +28,6 @@ public class InMemoryMealRepositoryImpl implements MealRepository { private Map> repository = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); - { - MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); - - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); - } - - @Override public Meal save(Meal meal, int userId) { Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java new file mode 100644 index 000000000000..425e4a3f0c24 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -0,0 +1,88 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.Month; + +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.USER_ID; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +public class MealServiceTest { + + static { + SLF4JBridgeHandler.install(); + } + + @Autowired + private MealService service; + + @Test + public void delete() throws Exception { + service.delete(MEAL1_ID, USER_ID); + assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); + } + + @Test(expected = NotFoundException.class) + public void deleteNotFound() throws Exception { + service.delete(MEAL1_ID, 1); + } + + @Test + public void create() throws Exception { + Meal created = getCreated(); + service.create(created, USER_ID); + assertMatch(service.getAll(USER_ID), created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + } + + @Test + public void get() throws Exception { + Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); + assertMatch(actual, ADMIN_MEAL1); + } + + @Test(expected = NotFoundException.class) + public void getNotFound() throws Exception { + service.get(MEAL1_ID, ADMIN_ID); + } + + @Test + public void update() throws Exception { + Meal updated = getUpdated(); + service.update(updated, USER_ID); + assertMatch(service.get(MEAL1_ID, USER_ID), updated); + } + + @Test(expected = NotFoundException.class) + public void updateNotFound() throws Exception { + service.update(MEAL1, ADMIN_ID); + } + + @Test + public void getAll() throws Exception { + assertMatch(service.getAll(USER_ID), MEALS); + } + + @Test + public void getBetween() throws Exception { + assertMatch(service.getBetweenDates( + LocalDate.of(2015, Month.MAY, 30), + LocalDate.of(2015, Month.MAY, 30), USER_ID), MEAL3, MEAL2, MEAL1); + } +} \ No newline at end of file From 50f79f6b422a8428c6ed85881ac15b91c7b41a52 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 20:13:31 +0700 Subject: [PATCH 53/55] 4_3_improve_code.patch --- .travis.yml | 22 +++++++++++++++++++ .../topjava/service/MealServiceImpl.java | 4 ++++ .../topjava/service/UserServiceImpl.java | 4 ++++ .../topjava/util/DateTimeUtil.java | 3 +++ .../javawebinar/topjava/util/MealsUtil.java | 3 +++ .../ru/javawebinar/topjava/util/Util.java | 8 +++++++ .../topjava/util/ValidationUtil.java | 3 +++ .../javawebinar/topjava/web/SecurityUtil.java | 3 +++ .../topjava/web/meal/MealRestController.java | 7 +++--- .../inmemory/InMemoryMealRepositoryImpl.java | 8 +++---- .../inmemory/InMemoryUserRepositoryImpl.java | 3 +++ 11 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..68ab0a037ad1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +# https://docs.travis-ci.com/user/languages/java/ +language: java +jdk: oraclejdk8 + +#https://dzone.com/articles/travis-ci-tutorial-java-projects +cache: + directories: + - $HOME/.m2 + +# https://docs.travis-ci.com/user/database-setup/#PostgreSQL +before_script: +- psql -c 'create database topjava' -U postgres +- psql -c 'create user "user"; grant all privileges on database topjava to "user"' -U postgres + +# https://docs.travis-ci.com/user/customizing-the-build#Building-Specific-Branches +branches: + only: + - master + +# https://docs.travis-ci.com/user/notifications#Configuring-email-notifications +#notifications: +# email: false \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 66371879e614..b083eaf84c74 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -32,6 +33,8 @@ public void delete(int id, int userId) { @Override public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Assert.notNull(startDateTime, "startDateTime must not be null"); + Assert.notNull(endDateTime, "endDateTime must not be null"); return repository.getBetween(startDateTime, endDateTime, userId); } @@ -47,6 +50,7 @@ public void update(Meal meal, int userId) { @Override public Meal create(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index c651c41d3fb9..a3f090177a3a 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -23,6 +24,7 @@ public UserServiceImpl(UserRepository repository) { @Override public User create(User user) { + Assert.notNull(user, "user must not be null"); return repository.save(user); } @@ -38,6 +40,7 @@ public User get(int id) throws NotFoundException { @Override public User getByEmail(String email) throws NotFoundException { + Assert.notNull(email, "email must not be null"); return checkNotFound(repository.getByEmail(email), "email=" + email); } @@ -48,6 +51,7 @@ public List getAll() { @Override public void update(User user) { + Assert.notNull(user, "user must not be null"); checkNotFoundWithId(repository.save(user), user.getId()); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 9c317b0f47fb..a1b5bfda4928 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -14,6 +14,9 @@ public class DateTimeUtil { public static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + private DateTimeUtil() { + } + public static 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 c9967c3bed63..aa96354ba773 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -17,6 +17,9 @@ public class MealsUtil { public static final int DEFAULT_CALORIES_PER_DAY = 2000; + private MealsUtil() { + } + public static List getWithExcess(Collection meals, int caloriesPerDay) { return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); } diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java index 7ea89146f056..22d3896971e2 100644 --- a/src/main/java/ru/javawebinar/topjava/util/Util.java +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -1,7 +1,15 @@ package ru.javawebinar.topjava.util; public class Util { + + private Util() { + } + public static > boolean isBetween(T value, T start, T end) { return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; } + + public static T orElse(T value, T defaultValue) { + return value == null ? defaultValue : value; + } } diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index cd0eec397a48..536408bb59c0 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -10,6 +10,9 @@ public static T checkNotFoundWithId(T object, int id) { return checkNotFound(object, "id=" + id); } + private ValidationUtil() { + } + public static void checkNotFoundWithId(boolean found, int id) { checkNotFound(found, "id=" + id); } diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index 588217547e60..4bad5863e3c6 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -8,6 +8,9 @@ public class SecurityUtil { private static int id = AbstractBaseEntity.START_SEQ; + private SecurityUtil() { + } + public static int authUserId() { return id; } 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 5bf56b31d407..0c5d07e464f8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -15,6 +15,7 @@ import java.time.LocalTime; import java.util.List; +import static ru.javawebinar.topjava.util.Util.orElse; import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; @@ -72,12 +73,10 @@ public List getBetween(LocalDate startDate, LocalTime startTime, LocalDa 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); + orElse(startDate, DateTimeUtil.MIN_DATE), orElse(endDate, DateTimeUtil.MAX_DATE), userId); return MealsUtil.getFilteredWithExcess(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), - startTime != null ? startTime : LocalTime.MIN, - endTime != null ? endTime : LocalTime.MAX + orElse(startTime, LocalTime.MIN), orElse(endTime, LocalTime.MAX) ); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 5a49c3c92816..d428a789b132 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -11,10 +11,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; @@ -30,6 +27,7 @@ public class InMemoryMealRepositoryImpl implements MealRepository { @Override public Meal save(Meal meal, int userId) { + Objects.requireNonNull(meal, "meal must not be null"); Map meals = repository.computeIfAbsent(userId, ConcurrentHashMap::new); if (meal.isNew()) { meal.setId(counter.incrementAndGet()); @@ -68,6 +66,8 @@ public List getAll(int userId) { @Override public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Objects.requireNonNull(startDateTime, "startDateTime must not be null"); + Objects.requireNonNull(endDateTime, "endDateTime must not be null"); return getAllFiltered(userId, meal -> Util.isBetween(meal.getDateTime(), startDateTime, endDateTime)); } diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java index 138181b8526d..823b8c5097c1 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -8,6 +8,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -29,6 +30,7 @@ public void init() { @Override public User save(User user) { + Objects.requireNonNull(user, "user must not be null"); if (user.isNew()) { user.setId(counter.incrementAndGet()); repository.put(user.getId(), user); @@ -56,6 +58,7 @@ public List getAll() { @Override public User getByEmail(String email) { + Objects.requireNonNull(email, "email must not be null"); return repository.values().stream() .filter(u -> email.equals(u.getEmail())) .findFirst() From 7aa7dff913f860a5f4fe5b5a01bd02c051395de4 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 20:13:44 +0700 Subject: [PATCH 54/55] 4_4_init_and_populate_db.patch --- src/main/resources/db/postgres.properties | 1 + src/main/resources/spring/spring-db.xml | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index fd8fe56209e0..829098063729 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,3 +5,4 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password +database.init=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 8ec56c9e63f0..d433866a78b8 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,11 +1,18 @@ + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> + + + + + From 39663374d32755612fc0e7aebf6cf16e8f5337b3 Mon Sep 17 00:00:00 2001 From: Egor Belkov Date: Wed, 31 Oct 2018 20:25:17 +0700 Subject: [PATCH 55/55] 4 --- pom.xml | 41 ++++++++++++-- .../ru/javawebinar/topjava/UserTestData.java | 29 ---------- .../topjava/model/AbstractBaseEntity.java | 10 +++- .../topjava/model/AbstractNamedEntity.java | 12 ++++- .../ru/javawebinar/topjava/model/Meal.java | 13 +++++ .../ru/javawebinar/topjava/model/User.java | 37 ++++++++++++- .../web/InMemoryAdminRestControllerTest.java | 51 ------------------ src/main/resources/db/postgres.properties | 8 ++- src/main/resources/spring/spring-app.xml | 2 - src/main/resources/spring/spring-db.xml | 54 ++++++++++++++++--- src/main/webapp/meals.jsp | 3 ++ .../ru/javawebinar/topjava/SpringMain.java | 2 +- 12 files changed, 161 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/UserTestData.java delete mode 100644 src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index 96dccc03ad69..d3da38319f77 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,11 @@ 42.2.5 4.12 + + + 5.3.7.Final + 6.0.13.Final + 3.0.1-b10 @@ -83,7 +88,7 @@ org.springframework - spring-jdbc + spring-orm ${spring.version} @@ -93,6 +98,32 @@ postgresql ${postgresql.version} + + org.hsqldb + hsqldb + 2.3.4 + + + + + + org.hibernate + hibernate-core + ${hibernate.version} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + + + org.glassfish + javax.el + ${javax-el.version} + provided + @@ -116,10 +147,10 @@ test - org.testng - testng - RELEASE - compile + org.springframework + spring-test + ${spring.version} + test org.assertj diff --git a/src/main/java/ru/javawebinar/topjava/UserTestData.java b/src/main/java/ru/javawebinar/topjava/UserTestData.java deleted file mode 100644 index 3cc466c53754..000000000000 --- a/src/main/java/ru/javawebinar/topjava/UserTestData.java +++ /dev/null @@ -1,29 +0,0 @@ -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/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 5cd722231795..91fbfca426cc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,11 +1,19 @@ package ru.javawebinar.topjava.model; +import javax.persistence.*; + +@MappedSuperclass +// http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access +@Access(AccessType.FIELD) public abstract class AbstractBaseEntity { public static final int START_SEQ = 100000; + @Id + @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") protected Integer id; - public AbstractBaseEntity() { + protected AbstractBaseEntity() { } protected AbstractBaseEntity(Integer id) { diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index 0e07e37b6ef1..e0b51ebfa8fe 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -1,10 +1,20 @@ package ru.javawebinar.topjava.model; +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + + +@MappedSuperclass public abstract class AbstractNamedEntity extends AbstractBaseEntity { + @NotBlank + @Size(min = 2, max = 100) + @Column(name = "name", nullable = false) protected String name; - public AbstractNamedEntity() { + protected AbstractNamedEntity() { } protected AbstractNamedEntity(Integer id, String name) { diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 1c1e67b15a3b..788ae8f6fe5b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -11,6 +13,9 @@ public class Meal extends AbstractBaseEntity { private int calories; + @ManyToOne(fetch = FetchType.LAZY) + private User user; + public Meal() { } @@ -57,6 +62,14 @@ public void setCalories(int calories) { this.calories = calories; } + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index c7ec91a1ed00..cc2f0a36fe95 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,23 +1,56 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.util.*; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; +@NamedQueries({ + @NamedQuery(name = User.DELETE, query = "DELETE FROM User u WHERE u.id=:id"), + @NamedQuery(name = User.BY_EMAIL, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email=?1"), + @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"), +}) +@Entity +@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) public class User extends AbstractNamedEntity { + public static final String DELETE = "User.delete"; + public static final String BY_EMAIL = "User.getByEmail"; + public static final String ALL_SORTED = "User.getAllSorted"; + + @Column(name = "email", nullable = false, unique = true) + @Email + @NotBlank + @Size(max = 100) private String email; + @Column(name = "password", nullable = false) + @NotBlank + @Size(min = 5, max = 100) private String password; + @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") private boolean enabled = true; + @Column(name = "registered", columnDefinition = "timestamp default now()") + @NotNull private Date registered = new Date(); + @Enumerated(EnumType.STRING) + @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) + @Column(name = "role") + @ElementCollection(fetch = FetchType.EAGER) private Set roles; + @Column(name = "calories_per_day", columnDefinition = "int default 2000") + @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; public User() { @@ -91,13 +124,13 @@ public void setRoles(Collection roles) { @Override public String toString() { - return "User (" + + return "User{" + "id=" + id + ", email=" + email + ", name=" + name + ", enabled=" + enabled + ", roles=" + roles + ", caloriesPerDay=" + caloriesPerDay + - ')'; + '}'; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java deleted file mode 100644 index 89c840b9fe81..000000000000 --- a/src/main/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java +++ /dev/null @@ -1,51 +0,0 @@ -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.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 delete() throws Exception { - controller.delete(UserTestData.USER_ID); - Collection users = controller.getAll(); - Assert.assertEquals(users.size(), 1); - Assert.assertEquals(users.iterator().next(), ADMIN); - } - - @Test(expected = NotFoundException.class) - public void deleteNotFound() throws Exception { - controller.delete(10); - } -} \ No newline at end of file diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 829098063729..9942a03036fe 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,4 +5,10 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password -database.init=true \ No newline at end of file +database.driverClassName=org.postgresql.Driver + +database.init=true +jdbc.initLocation=initDB.sql +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 5ae45a114f8d..4b239a370a1b 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -15,8 +15,6 @@ - - diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index d433866a78b8..ef29e6a69d0f 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,32 +1,70 @@ + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + + + + + - + - + - - - + + + + + + + + + + - - + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index a2e6291eca3d..00f53cd01d90 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -47,6 +47,9 @@

    diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index a8ac8cb4ad53..80598adcc899 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -17,7 +17,7 @@ public class SpringMain { public static void main(String[] args) { // java 7 Automatic resource management - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml")) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN));
    ${fn:formatDateTime(meal.dateTime)}
    + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> ${fn:formatDateTime(meal.dateTime)} ${meal.description}