` тэги (`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..56d0393f09bb
--- /dev/null
+++ b/cv.md
@@ -0,0 +1,99 @@
+## Составление резюме, подготовка к интервью, поиск работы
+
+
+
+-
Научиться программировать сложнее, чем кажется
+
+### Составление резюме:
+-
Выбрать шаблон для резюме
+-
Как продать свое резюме в 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
+
+### Интервью:
+-
Михаил Портнов. Собеседование на работу: как продать себя грамотно
+-
Михаил Портнов. Какие вопросы мы задаем на собеседовании?
+-
Михаил Портнов. Собеседование на работу: жизненный путь
+-
Канал: Резюме, поиск работы, интервью
+-
Яков Файн: Как стать профессиональным Java разработчиком
+-
Ответы на вопросы на собеседовании Junior Java Developer
+-
Список вопросов с ответами для собеседования по Java
+-
Сборка по вопросам на интервью
+-
Сборка вопросов-ответов от JavaStudy
+-
Тест на знание SQL
+-
Вопросы на собеседовании Java Junior Developer
+-
Java вопросы с собеседований на Android
+-
Сборка вопросов от JavaRush
+> про clone и finalize объязательно прочтите
Джошуа Блох: Java. Эффективное программирование (второе издание)
+
+-
Cracking the Coding Interview
+> Особенно обратите внимание на раздел:
Часть VIII. Вопросы собеседования
+
+
+### От себя:
+- email, skype - очень желательно, чтобы по ним вы были узнаваемы. Заведите рабочие, если не так.
+- написать ВЕСЬ IT опыт (исключая опыт пользователя: Windows, MS Word, Photophop, Yandex disk, Google docs, ..): технологии, какие задачи решали (конкретные), какие инструменты использовали, VCS, DB, инструменты сборки, ... включая опыт в ВУЗе.
+- на English иметь желательно. Если вакансия опублинована на Englsih - шлите на нем. Часто могут на нем попросить, если работодатель иностранный.
+- удобно иметь резюме где то в инете (hh, linkedin, google doc, чтобы им было удобно делиться).
+
+### Позиционирование
проекта Topjava:
+- После завершения проекта вы освоите все заявленные в нем технологии - вставьте их в квалификацию (включая java 8 Stream and Time API). В разделе опыт работы (если у вас нет коммерческого опыта) вставьте:
+
+ Участие в разработке Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей
+ на стеке Maven/ Spring MVC/ Security/ REST(Jackson)/ Java 8 Stream API:
+ - реализация сохранения в базы Postgres и HSQLDB на основе Spring JBDC, JPA(Hibernate) и Spring-Data-JPA
+ - реализация и тестирование REST и AJAX контроллеров
+ - реализация клиента на Bootstrap (css/js), datatables, jQuery + plugins.
+ - отлично будет, если вы его еще как то сами доработаете
+
+
+Делайте упор не на обучение, а на *участие в проекте*. Выполнение домашних заданий - это полноценное участие с написанием функционала по всем этим технологиям. На собеседовании меньше смотрят на то, что вы заканчивали, больше - на ваш опыт и то что вы знаете.
+ОБЯЗАТЕЛЬНО УБЕРИТЕ любое упоминание Junior из резюме, количество обращений к вам возрастет на порядок.
+
+- Ссылку на стажировку можно поставить: http://javaops.ru (или в linkedin: https://www.linkedin.com/company/java-online-projects)
+
+Если рассмотриваете предложения по работе, **поднимите в своем профиле этот флаг и обязательно заполните ссылку на резюме: я запустил интеграцию с HR (более 75 контактов). Также проверьте содержание "Обо мне": по нему будут принимать решение открывать резюме или нет.**
+
+Ближе к концу курса я вышлю приглашение в нашу общую группу slack: общение на все темы IT, обсуждение вступительных заданий, вакансии, цены на рынке труда, события, помощь, интересные видео по теме и пр.
+
+###
Подпишитесь на рассылку вакансий под себя
+
+### Основные сайты поиска работы:
+-
Яндекс агрегатор
+-
HH
+-
LinkedIn
+-
djinni.co (более актуально для Украины)
+
+## [Тестовое собеседование, самые спрашиваемые темы](http://javaops.ru/interview/test.html)
+## Как выжить на испытательном сроке
+- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В
+ процессе формулирования очень часто приходит ее решение.
+- Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки
+ разработчика. В логах надо читать верх самого нижнего эксепшена - там причина всей портянки.
+- Грамотно уделяй время каждой проблеме. Две крайности - сразу бросаться за помощью и
+ бится нам ней часами.
+ Пробуй решить ее сам и в зависимости от проблемы выделяй на это разумное время.
+- Если тебе что-то объясняют по проекту - обязательно записывай.
+- Когда получаешь задачу - уточни все очень подробно.
+- Получай в процессе решения обратную связь - в том ли направлении ты идешь.
+- Не игнорируй совместные ланчи (курилки)
+- Готовься к стендапам/летучкам. Задавай на них вменяемые вопросы. Выказывай заинтересованность
+- Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи.
+- [**Советы новичкам**](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..62aa9c218baa
--- /dev/null
+++ b/description.md
@@ -0,0 +1,75 @@
+#### Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием
наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API
+- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения.
+- Каждая итерация проекта в закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий.
+- Большое внимание уделяется тестированию кода: в проекте 88 JUnit тестов.
+- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой
+Hibernate, делаем конверторы для типов LocalDateTime (Java 8 time API), которые еще не поддерживаются ни JPA/Hibernate, ни Jackson/json).
+ Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object.
+ Т.е на выходе получается не учебный проект, а хорошо маштабируемый шаблон для большого проекта на всех пройденных технологиях.
+- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST
+ контроллеров, насторойка EntityManagerFactory,
+ выбор реализации пула коннектов. Особое внимание уделяется работе с базой: через Spring JDBC, Spring ORM и
+ Spring Data Jpa.
+- Используются самые востребованные на сегодняшний момент фреймворки: Maven, Spring Security 4
+ вместе с Spring Security Test, наиболее удобный для работы с базой проект Spring Data Jpa, библиотека логирования logback, реализующая SLF4J, повсеместно используемый Bootstrap и jQuery.
+
+####
Демо разрабатываемого приложения
+
+## План проекта (ссылки на некоторые темы открыты для просмотра)
+### Архитектура проекта. Персистентность.
+-
Системы управления версиями
+-
Java 8: Lambda, Stream API
+- Обзор используемых в проекте технологий и инструментов.
+-
Инструмент сборки Maven.
+- WAR. Веб-контейнер Tomcat. Сервлеты.
+- Логирование.
+- Обзор стандартных библиотек. Apache Commons, Guava
+- Слои приложения. Создание каркаса приложения.
+- Обзор Spring Framework. Spring Context.
+- Тестирование через JUnit.
+-
Spring Test
+- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM.
+- Настройка Database в IDEA.
+- Скрипты инициализации базы. Spring Jdbc Template.
+-
Spring: инициализация и популирование DB
+- ORM. Hibernate. JPA.
+- Поддержка HSQLDB
+- Транзакции
+- Профили Maven и Spring
+-
Пул коннектов
+- Spring Data JPA
+- Кэш Hibernate
+
+### Разработка WEB
+- Spring кэш
+- Spring Web
+- JSP, JSTL, i18n
+- Tomcat maven plugin. JNDI
+- Spring Web MVC
+- Spring Internationalization
+- Тестирование Spring MVC
+- REST контроллеры
+- Тестирование REST контроллеров. Jackson.
+- jackson-datatype-hibernate. Тестирование через матчеры.
+- Тестирование через SoapUi. UTF-8
+- WebJars.
+- Bootstrap. jQuery datatables.
+- AJAX. jQuery. Notifications.
+- Spring Security
+- Spring Binding/Validation
+- Работа с datatables через Ajax.
+- Spring Security Test
+- [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/open?id=0B9Ye2auQ_NsFRTFsTjVHR2dXczA)
+- Encoding password
+-
CSRF (добавление в проект защиты от межсайтовой подделки запроса)
+- form-login. Spring Security Taglib
+- Handler interceptor
+- Spring Exception Handling
+- Смена локали
+- Фильтрация JSON через @JsonView
+- Защита от XSS (Cross Site Scripting)
+-
Деплой в Heroku
+- Локализация datatables, ошибок валидации
+- Обработка ошибок 404 (NotFound)
+- Доступ к AuthorizedUser
+-
Собеседование. Разработка ПО
diff --git a/pom.xml b/pom.xml
index c8a1c78f3b29..d56f77e7227f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
ru.javawebinar
topjava
-
jar
+
war
1.0-SNAPSHOT
@@ -15,11 +15,15 @@
1.8
UTF-8
UTF-8
+
+
+
1.2.3
+
1.7.25
topjava
- install
+ package
org.apache.maven.plugins
@@ -34,6 +38,41 @@
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ compile
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+ ${slf4j.version}
+ runtime
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ runtime
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ provided
+
+
+
+ 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 b23a2f0961fc..000000000000
--- a/src/main/java/ru/javawebinar/topjava/Main.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package ru.javawebinar.topjava;
-
-/**
- * User: gkislin
- * Date: 05.08.2015
- *
- * @link http://caloriesmng.herokuapp.com/
- * @link https://github.com/JavaOPs/topjava
- */
-public class Main {
- public static void main(String[] args) {
- System.out.format("Hello Topjava Enterprise!");
- }
-}
diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java
new file mode 100644
index 000000000000..3abbee42511e
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java
@@ -0,0 +1,68 @@
+package ru.javawebinar.topjava.model;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+public class Meal {
+ private Integer id;
+
+ private final LocalDateTime dateTime;
+
+ private final String description;
+
+ private final int calories;
+
+ public Meal(LocalDateTime dateTime, String description, int calories) {
+ this(null, dateTime, description, calories);
+ }
+
+ public Meal(Integer id, LocalDateTime dateTime, String description, int calories) {
+ this.id = id;
+ this.dateTime = dateTime;
+ this.description = description;
+ this.calories = calories;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getCalories() {
+ return calories;
+ }
+
+ public LocalDate getDate() {
+ return dateTime.toLocalDate();
+ }
+
+ public LocalTime getTime() {
+ return dateTime.toLocalTime();
+ }
+
+ public boolean isNew() {
+ return id == null;
+ }
+
+ @Override
+ public String toString() {
+ return "Meal{" +
+ "id=" + id +
+ ", dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ '}';
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java
new file mode 100644
index 000000000000..2b375e45eecc
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/MealWithExceed.java
@@ -0,0 +1,54 @@
+package ru.javawebinar.topjava.model;
+
+import java.time.LocalDateTime;
+
+public class MealWithExceed {
+ private final Integer id;
+
+ private final LocalDateTime dateTime;
+
+ private final String description;
+
+ private final int calories;
+
+ private final boolean exceed;
+
+ public MealWithExceed(Integer id, LocalDateTime dateTime, String description, int calories, boolean exceed) {
+ this.id = id;
+ this.dateTime = dateTime;
+ this.description = description;
+ this.calories = calories;
+ this.exceed = exceed;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getCalories() {
+ return calories;
+ }
+
+ public boolean isExceed() {
+ return exceed;
+ }
+
+ @Override
+ public String toString() {
+ return "MealWithExceed{" +
+ "id=" + id +
+ ", dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ ", exceed=" + exceed +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java
new file mode 100644
index 000000000000..bcb9cd8894a0
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java
@@ -0,0 +1,43 @@
+package ru.javawebinar.topjava.repository;
+
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.util.MealsUtil;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class InMemoryMealRepositoryImpl implements MealRepository {
+ private Map repository = new ConcurrentHashMap<>();
+ private AtomicInteger counter = new AtomicInteger(0);
+
+ {
+ MealsUtil.MEALS.forEach(this::save);
+ }
+
+ @Override
+ public Meal save(Meal meal) {
+ if (meal.isNew()) {
+ meal.setId(counter.incrementAndGet());
+ }
+ repository.put(meal.getId(), meal);
+ return meal;
+ }
+
+ @Override
+ public void delete(int id) {
+ repository.remove(id);
+ }
+
+ @Override
+ public Meal get(int id) {
+ return repository.get(id);
+ }
+
+ @Override
+ public Collection getAll() {
+ return repository.values();
+ }
+}
+
diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java
new file mode 100644
index 000000000000..295f831d69e6
--- /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
new file mode 100644
index 000000000000..ace07c84f323
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java
@@ -0,0 +1,88 @@
+package ru.javawebinar.topjava.util;
+
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.model.MealWithExceed;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static java.util.function.Function.identity;
+
+public class MealsUtil {
+ public static final List MEALS = Arrays.asList(
+ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500),
+ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000),
+ new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500),
+ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000),
+ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500),
+ new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510)
+ );
+
+ public static final int DEFAULT_CALORIES_PER_DAY = 2000;
+
+ public static void main(String[] args) {
+ List mealsWithExceeded = getFilteredWithExceeded(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000);
+ mealsWithExceeded.forEach(System.out::println);
+
+ System.out.println(getFilteredWithExceededByCycle(MEALS, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY));
+ }
+
+ public static List getWithExceeded(Collection meals, int caloriesPerDay) {
+ return getFilteredWithExceeded(meals, LocalTime.MIN, LocalTime.MAX, caloriesPerDay);
+ }
+
+ public static List getFilteredWithExceeded(Collection meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
+ Map caloriesSumByDate = meals.stream()
+ .collect(
+ Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories))
+// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum)
+ );
+
+ return meals.stream()
+ .filter(meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime))
+ .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 (DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)) {
+ mealsWithExceeded.add(createWithExceed(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay));
+ }
+ });
+ return mealsWithExceeded;
+ }
+
+ /*
+ * Advanced solution in one return (listDayMeals can be inline).
+ * Streams are not multiplied, so complexity is still O(N)
+ * Execution time is increased as for every day we create 2 additional streams
+ */
+ public static List getFilteredWithExceededInOneReturn(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
+
+ Collection> listDayMeals = meals.stream()
+ .collect(Collectors.groupingBy(Meal::getDate)).values();
+
+ return listDayMeals
+ .stream().map(dayMeals -> {
+ boolean exceed = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay;
+ return dayMeals.stream().filter(meal ->
+ DateTimeUtil.isBetween(meal.getTime(), startTime, endTime))
+ .map(meal -> createWithExceed(meal, exceed));
+ }).flatMap(identity())
+ .collect(Collectors.toList());
+ }
+
+ public static MealWithExceed createWithExceed(Meal meal, boolean exceeded) {
+ return new MealWithExceed(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), exceeded);
+ }
+}
\ No newline at end of file
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..bb64aa951793
--- /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.valueOf(request.getParameter("calories")));
+
+ log.info(meal.isNew() ? "Create {}" : "Update {}", meal);
+ repository.save(meal);
+ response.sendRedirect("meals");
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ 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("/meal.jsp").forward(request, response);
+ break;
+ case "all":
+ default:
+ log.info("getAll");
+ request.setAttribute("meals",
+ MealsUtil.getWithExceeded(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY));
+ request.getRequestDispatcher("/meals.jsp").forward(request, response);
+ break;
+ }
+ }
+
+ private int getId(HttpServletRequest request) {
+ String paramId = Objects.requireNonNull(request.getParameter("id"));
+ return Integer.valueOf(paramId);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java
new file mode 100644
index 000000000000..f6cf12e69976
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java
@@ -0,0 +1,21 @@
+package ru.javawebinar.topjava.web;
+
+import org.slf4j.Logger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class UserServlet extends HttpServlet {
+ private static final Logger log = getLogger(UserServlet.class);
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ log.debug("forward to users");
+ request.getRequestDispatcher("/users.jsp").forward(request, response);
+ }
+}
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
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld
new file mode 100644
index 000000000000..d138fecdbfb5
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tld/functions.tld
@@ -0,0 +1,16 @@
+
+
+
+ 1.0
+ functions
+ http://topjava.javawebinar.ru/functions
+
+
+ formatDateTime
+ ru.javawebinar.topjava.util.DateTimeUtil
+ java.lang.String toString(java.time.LocalDateTime)
+
+
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000000..d2e475517f7e
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,29 @@
+
+
+ Topjava
+
+
+ userServlet
+ ru.javawebinar.topjava.web.UserServlet
+ 0
+
+
+ userServlet
+ /users
+
+
+
+ mealServlet
+ ru.javawebinar.topjava.web.MealServlet
+ 0
+
+
+ mealServlet
+ /meals
+
+
+
diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
new file mode 100644
index 000000000000..cd88b335a454
--- /dev/null
+++ b/src/main/webapp/index.html
@@ -0,0 +1,14 @@
+
+
+
+ Java Enterprise (Topjava)
+
+
+
+
+
+
+
diff --git a/src/main/webapp/meal.jsp b/src/main/webapp/meal.jsp
new file mode 100644
index 000000000000..ea6c31ce3ddf
--- /dev/null
+++ b/src/main/webapp/meal.jsp
@@ -0,0 +1,51 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+ Meal
+
+
+
+
+
+ ${param.action == 'create' ? 'Create meal' : 'Edit meal'}
+
+
+
+
+
+
diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp
new file mode 100644
index 000000000000..24794f7b44c7
--- /dev/null
+++ b/src/main/webapp/meals.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" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %>
+
+
+ Meal list
+
+
+
+
+
+ Meal list
+ Add Meal
+
+
+
+
+ | Date |
+ Description |
+ Calories |
+ |
+ |
+
+
+
+
+
+ |
+ <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%>
+ <%--<%=TimeUtil.toString(meal.getDateTime())%>--%>
+ ${fn:formatDateTime(meal.dateTime)}
+ |
+ ${meal.description} |
+ ${meal.calories} |
+ Update |
+ Delete |
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp
new file mode 100644
index 000000000000..acf19d7adf09
--- /dev/null
+++ b/src/main/webapp/users.jsp
@@ -0,0 +1,10 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Users
+
+
+
+Users
+
+
\ No newline at end of file