diff --git a/README.md b/README.md
new file mode 100644
index 000000000000..0a1d444eb137
--- /dev/null
+++ b/README.md
@@ -0,0 +1,110 @@
+Java Enterprise Online Project
+===============================
+
+[](https://www.codacy.com/app/javawebinar/topjava06)
+
+
+Наиболее востребованные технологии /инструменты / фреймворки Java Enterprise:
+Maven/ Spring/ Security/ JPA(Hibernate)/ REST(Jackson)/ Bootstrap(CSS)/ jQuery + plugins.
+
+ Когда вы слышите что-то, вы забываете это.
+ Когда вы видите что-то, вы запоминаете это.
+ Но только когда вы начинаете делать это,
+ вы начинаете понимать это
+
+ Старинная китайская поговорка
+
+## Описание и план проекта
+### Демо разрабатываемого приложения
+### Требования к участникам, Wiki
+
+Вводное занятие
+===============
+##  Системы управления версиями. Git.
+
+- Система управления версиями. VCS/DVSC.
+- Ресурсы:
+ - Интерактивная Git обучалка
+ - Основы Git
+ - Working with remote repositories
+ - Видео по обучению Git
+ - Как начать работать с GitHub: быстрый старт
+ - Справочник в графическом виде
+ - Git Overview
+ - Видеокурс по Git
+
+##  Работа с проектом (выполнять инструкции)
+- **Prepare_ to_ HW0.patch (скачать и положить в каталог вашего проекта)**
+- Доступно о Java 8 Lambda
+
+## Инструкция по шагам (из видео):
+- Установить ПО (git, JDK8, IntelliJ IDEA, Maven)
+- Создать аккаунт на GitHub
+- Сделать Fork ЭТОГО проекта (https://github.com/JavaOPs/topjava)
+- Сделать локальный репозиторий проекта:
+
git clone https://github.com/[Ваш аккаунт]/topjava.git
+- Открыть и настроить проект в IDEA
+- По ходу видео сделать Apply Patch... скаченного патча Prepare_ to_ HW0.patch
+- Закоммитить и запушить изменения (commit + push)
+- Сделать ветку домашнего задания
+- Выполнить задание и залить на GitHub (commit + push)
+- Переключиться в основную ветку проекта master.
+
+## Домашнее задание HW0
+
+- Java 8 Date and Time API
+- Time complexity
+- Временная сложность алгоритма
+- Вычислительная сложность
+```
+Реализовать UserMealsUtil.getFilteredMealsWithExceeded:
+- должны возвращаться только записи между startTime и endTime
+- поле UserMealWithExceed.exceed должно показывать,
+ превышает ли сумма калорий за весь день параметра метода caloriesPerDay
+
+Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день.
+
+- Проверте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды)
+- Оцените Time complexity вашего алгоритма (если он O(N*N)- попробуйте сделать O(N).
+```
+Optional
+
+- Java 8: Lambda выражения
+- Java 8: Потоки
+- Pуководство по Java 8 Stream
+- Лямбда-выражения в Java 8
+- Шпаргалка Java Stream API
+- stream.map vs forEach
+
+```
+Сделать реализация через Java 8 Stream API. Заменяйте forEach оператором stream.map(..)
+```
+### Составление резюме, подготовка к интервью, поиск работы
+### Ресурсы для подготовки к проекту
+#### (желательно иметь представление для лучшего усвоения материала)
+
+#### Java Web (Servlets, JSP)
+- Введение в сетевое программирование
+- Основы Java на реальном примере (Servlets, JSP)
+- Как создать Servlet? Полное руководство.
+- Технологии Java для разработки веб-приложений
+
+#### HTML, JavaScript, CSS
+- Основы работы с HTML/CSS/JavaScript
+- jQuery для начинающих
+- Уроки Bootstrap 3
+
+#### Java Core (Reflection API, JUnit)
+- Введение в Java Reflection API
+- Java JUnit
+
+#### JDBC, SQL
+- Уроки по JDBC
+- Learn SQL
+- Try SQL
+
+#### Разное
+- Интуит. Программирование на Java
+- Oracle Java tutorial на русском.
+- Эффективная работа с кодом в IntelliJ IDEA
diff --git a/config/messages/app.properties b/config/messages/app.properties
new file mode 100644
index 000000000000..c6cc565f0ab9
--- /dev/null
+++ b/config/messages/app.properties
@@ -0,0 +1,11 @@
+app.title=Calories management
+app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application
+app.login=Login as
+
+users.title=User list
+users.edit=Edit user
+
+meals.title=Meals list
+meals.edit=Edit meal
+
+common.select=Select
\ No newline at end of file
diff --git a/config/messages/app_ru.properties b/config/messages/app_ru.properties
new file mode 100644
index 000000000000..7a7d45b8fb06
--- /dev/null
+++ b/config/messages/app_ru.properties
@@ -0,0 +1,11 @@
+app.title=Подсчет калорий
+app.footer=Приложение по вебинару Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery
+app.login=Зайти как
+
+users.title=Список пользователей
+users.edit=Редактировать пользователя
+
+meals.title=Список еды
+meals.edit=Редактировать еду
+
+common.select=Выбрать
\ No newline at end of file
diff --git a/config/setenv.bat b/config/setenv.bat
new file mode 100644
index 000000000000..ebbd659386f6
--- /dev/null
+++ b/config/setenv.bat
@@ -0,0 +1,4 @@
+rem run tomcat with JMX ability
+rem Run Tomcat as admin
+rem for remote connection add -Djava.rmi.server.hostname=TomcatServer_IP
+set CATALINA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
diff --git a/cv.md b/cv.md
new file mode 100644
index 000000000000..95a53c28f61a
--- /dev/null
+++ b/cv.md
@@ -0,0 +1,55 @@
+## Составление резюме, подготовка к интервью, поиск работы
+
+
+
+- Научиться программировать сложнее, чем кажется
+
+### Составление резюме:
+- Идеальное резюме Junior’а
+- Как продать свое резюме в 2 раза дороже
+- Как правильно составить резюме
+- Резюме программистов. Часть 1 (плохие)
+- Резюме программистов. Часть 2 (хорошие)
+- Как составить резюме на английском
+
+### Интервью:
+- Михаил Портнов. Собеседование на работу: как продать себя грамотно
+- Михаил Портнов. Какие вопросы мы задаем на собеседовании?
+- Канал: Резюме, поиск работы, интервью
+- Яков Файн: Как стать профессиональным Java разработчиком
+- Quizful- тесты онлайн
+- Сборка по вопросам на интервью
+- Тест на знание SQL
+- Сборка вопросов от 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.
+ - отлично будет, если вы его еще как то сами доработаете
+
+- По поводу обучения на курсах- можно упомянуть в разделе образования. Но на собеседовании меньше смотрят на то, что вы заканчивали, больше - на ваш опыт и то что вы знаете.
+
+### В течении проекта заполните форму соискателя "Разработчик Java"
+Я ее рассылаю всем знакомым HR, уже есть пинги и трудоустройство.
+
+### Основные сайты поиска работы:
+- HH
+- LinkedIn
+- djinni.co (более актуально для Украины)
+- moikrug
diff --git a/description.md b/description.md
new file mode 100644
index 000000000000..cbb102fa72aa
--- /dev/null
+++ b/description.md
@@ -0,0 +1,67 @@
+#### Разработка полнофункционального 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, настраиваем Jenkins для работы с ленивой загрузкой
+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. Datatables.
+- AJAX. jQuery. Notifications.
+- Spring Security
+- Spring Binding/Validation
+- Работа с Datatables через Ajax.
+- Spring Security Test
+- Encoding password
+- CSRF (добавление в проект защиты от межсайтовой подделки запроса)
+- form-login. Spring Security Taglib
+- Handler interceptor
+- Spring Exception Handling
+- Деплой в Heroku
diff --git a/pom.xml b/pom.xml
index c8a1c78f3b29..397dec903686 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
ru.javawebinar
topjava
- jar
+ war
1.0-SNAPSHOT
@@ -15,11 +15,32 @@
1.8
UTF-8
UTF-8
+
+ 1.9.4.RELEASE
+ 4.2.5.RELEASE
+ 7.0.57
+
+
+ 1.1.6
+ 1.7.18
+
+
+ 9.4.1208
+
+ 4.12
+
+
+ 5.1.0.Final
+ 5.2.4.Final
+
+
+ 1.3
+ 2.10.1
topjava
- install
+ package
org.apache.maven.plugins
@@ -30,15 +51,219 @@
${java.version}
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.19.1
+
+ -Dfile.encoding=UTF-8
+
+
+
+
+ org.apache.tomcat.maven
+ tomcat7-maven-plugin
+ 2.2
+
+ src/main/resources/tomcat/context.xml
+
+ UTF-8
+ tomcat,datajpa
+
+ org.apache.coyote.http11.Http11NioProtocol
+
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ compile
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+ ${slf4j.version}
+ runtime
+
+
+
+ org.slf4j
+ jul-to-slf4j
+ ${slf4j.version}
+ runtime
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ runtime
+
+
+
+
+ org.springframework
+ spring-context-support
+ ${spring.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ hamcrest-core
+ org.hamcrest
+
+
+
+
+
+ org.hamcrest
+ hamcrest-all
+ ${hamcrest.version}
+ test
+
+
+ javax.servlet
+ jstl
+ 1.2
+
+
+
+ org.apache.tomcat
+ tomcat-servlet-api
+ ${tomcat.version}
+ provided
+
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.springframework
+ spring-test
+ ${spring.version}
+ test
+
+
+
+ org.springframework.data
+ spring-data-jpa
+ ${spring-data-jpa.version}
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.version}
+
+
+ org.hibernate
+ hibernate-java8
+ ${hibernate.version}
+
+
+ javax.transaction
+ jta
+ 1.1
+ runtime
+
+
+ org.hibernate
+ hibernate-validator
+ ${hibernate-validator.version}
+
+
+ org.hibernate
+ hibernate-ehcache
+ ${hibernate.version}
+
+
+
+ net.sf.ehcache
+ ehcache
+ ${ehcache.version}
+
+
+
+ javax.el
+ javax.el-api
+ 2.2.5
+ provided
+
+
+ hsqldb
+
+
+ org.hsqldb
+ hsqldb
+ 2.3.2
+
+
+
+
+ postgres
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+
+
+ org.apache.tomcat
+ tomcat-jdbc
+ ${tomcat.version}
+
+
+
+ true
+
+
+
+
+ org.springframework
+ spring-framework-bom
+ ${spring.version}
+ pom
+ import
+
+
diff --git a/src/main/java/ru/javawebinar/topjava/LoggedUser.java b/src/main/java/ru/javawebinar/topjava/LoggedUser.java
new file mode 100644
index 000000000000..56947911d53b
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java
@@ -0,0 +1,28 @@
+package ru.javawebinar.topjava;
+
+import ru.javawebinar.topjava.util.UserMealsUtil;
+
+import ru.javawebinar.topjava.model.BaseEntity;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+public class LoggedUser {
+ private static int id = BaseEntity.START_SEQ;
+
+ private LoggedUser() {
+ }
+
+ public static int id() {
+ return id;
+ }
+
+ public static void setId(int id) {
+ LoggedUser.id = id;
+ }
+
+ public static int getCaloriesPerDay() {
+ return UserMealsUtil.DEFAULT_CALORIES_PER_DAY;
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java
new file mode 100644
index 000000000000..4ce31d6f690a
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/Profiles.java
@@ -0,0 +1,17 @@
+package ru.javawebinar.topjava;
+
+/**
+ * User: gkislin
+ * Date: 19.08.2014
+ */
+public class Profiles {
+ public static final String
+ POSTGRES = "postgres",
+ HSQLDB = "hsqldb",
+ JDBC = "jdbc",
+ JPA = "jpa",
+ DATAJPA = "datajpa";
+
+ public static final String ACTIVE_DB = POSTGRES;
+ public static final String DB_IMPLEMENTATION = DATAJPA;
+}
diff --git a/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java
new file mode 100644
index 000000000000..007e260dcb68
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java
@@ -0,0 +1,58 @@
+package ru.javawebinar.topjava.model;
+
+import javax.persistence.*;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+@MappedSuperclass
+@Access(AccessType.FIELD)
+public class BaseEntity {
+ public static final int START_SEQ = 100000;
+
+ @Id
+ @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1)
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq")
+ protected Integer id;
+
+ public BaseEntity() {
+ }
+
+ protected BaseEntity(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 boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BaseEntity that = (BaseEntity) o;
+ if (id == null || that.id == null) {
+ return false;
+ }
+ return id.equals(that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return (id == null) ? 0 : id;
+ }
+
+}
diff --git a/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java
new file mode 100644
index 000000000000..e593d37981e7
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/NamedEntity.java
@@ -0,0 +1,39 @@
+package ru.javawebinar.topjava.model;
+
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.persistence.Column;
+import javax.persistence.MappedSuperclass;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+@MappedSuperclass
+public class NamedEntity extends BaseEntity {
+
+ @NotEmpty
+ @Column(name = "name", nullable = false)
+ protected String name;
+
+ public NamedEntity() {
+ }
+
+ protected NamedEntity(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 name;
+ }
+}
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..f0de2b4c4a81
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/Role.java
@@ -0,0 +1,10 @@
+package ru.javawebinar.topjava.model;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+public enum Role {
+ ROLE_USER,
+ ROLE_ADMIN
+}
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..0671e7cb15d8
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/User.java
@@ -0,0 +1,141 @@
+package ru.javawebinar.topjava.model;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.validator.constraints.Email;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.NotEmpty;
+import ru.javawebinar.topjava.util.UserMealsUtil;
+
+import javax.persistence.*;
+import javax.validation.constraints.Digits;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+@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 DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"),
+})
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+@Entity
+@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")})
+public class User extends NamedEntity {
+
+ public static final String DELETE = "User.delete";
+ public static final String ALL_SORTED = "User.getAllSorted";
+ public static final String BY_EMAIL = "User.getByEmail";
+
+ @Column(name = "email", nullable = false, unique = true)
+ @Email
+ @NotEmpty
+ protected String email;
+
+ @Column(name = "password", nullable = false)
+ @NotEmpty
+ @Length(min = 5)
+ protected String password;
+
+ @Column(name = "enabled", nullable = false)
+ protected boolean enabled = true;
+
+ @Column(name = "registered", columnDefinition = "timestamp default now()")
+ protected Date registered = new Date();
+
+ @Enumerated(EnumType.STRING)
+ @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
+ @Column(name = "role")
+ @ElementCollection(fetch = FetchType.EAGER)
+ @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+ protected Set roles;
+
+ @Column(name = "calories_per_day", columnDefinition = "default 2000")
+ @Digits(fraction = 0, integer = 4)
+ protected int caloriesPerDay = UserMealsUtil.DEFAULT_CALORIES_PER_DAY;
+
+ public User() {
+ }
+
+ public User(User u) {
+ this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRoles());
+ }
+
+ public User(Integer id, String name, String email, String password, Role role, Role... roles) {
+ this(id, name, email, password, UserMealsUtil.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;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = EnumSet.copyOf(roles);
+ }
+
+ @Override
+ public String toString() {
+ return "User (" +
+ "id=" + id +
+ ", email=" + email +
+ ", name=" + name +
+ ", enabled=" + enabled +
+ ", roles=" + roles +
+ ", caloriesPerDay=" + caloriesPerDay +
+ ')';
+ }
+}
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..50dfb34f4259
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java
@@ -0,0 +1,104 @@
+package ru.javawebinar.topjava.model;
+
+import org.hibernate.validator.constraints.NotEmpty;
+import org.hibernate.validator.constraints.Range;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+/**
+ * GKislin
+ * 11.01.2015.
+ */
+@SuppressWarnings("JpaQlInspection")
+@NamedQueries({
+ @NamedQuery(name = UserMeal.GET, query = "SELECT m FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId"),
+ @NamedQuery(name = UserMeal.ALL_SORTED, query = "SELECT m FROM UserMeal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC"),
+ @NamedQuery(name = UserMeal.DELETE, query = "DELETE FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId"),
+ @NamedQuery(name = UserMeal.GET_BETWEEN, query = "SELECT m FROM UserMeal m "+
+ "WHERE m.user.id=:userId AND m.dateTime BETWEEN :startDate AND :endDate ORDER BY m.dateTime DESC"),
+
+// @NamedQuery(name = UserMeal.UPDATE, query = "UPDATE UserMeal m SET m.dateTime = :datetime, m.calories= :calories," +
+// "m.description=:desc where m.id=:id and m.user.id=:userId")
+})
+@Entity
+@Table(name = "meals", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "date_time"}, name = "meals_unique_user_datetime_idx")})
+public class UserMeal extends BaseEntity {
+ public static final String GET = "UserMeal.get";
+ public static final String ALL_SORTED = "UserMeal.getAll";
+ public static final String DELETE = "UserMeal.delete";
+ public static final String GET_BETWEEN = "UserMeal.getBetween";
+
+ @Column(name = "date_time", nullable = false)
+ @NotNull
+ private LocalDateTime dateTime;
+
+ @Column(name = "description", nullable = false)
+ @NotEmpty
+ private String description;
+
+ @Column(name = "calories", nullable = false)
+ @Range(min = 10, max = 5000)
+ protected int calories;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
+ private User user;
+
+ public UserMeal() {
+ }
+
+ public UserMeal(LocalDateTime dateTime, String description, int calories) {
+ this(null, dateTime, description, calories);
+ }
+
+ public UserMeal(Integer id, LocalDateTime dateTime, String description, int calories) {
+ super(id);
+ this.dateTime = dateTime;
+ this.description = description;
+ this.calories = calories;
+ }
+
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getCalories() {
+ return calories;
+ }
+
+ public void setDateTime(LocalDateTime dateTime) {
+ this.dateTime = dateTime;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setCalories(int calories) {
+ this.calories = calories;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public String toString() {
+ return "UserMeal{" +
+ "id=" + id +
+ ", dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java b/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java
new file mode 100644
index 000000000000..75b3d82ba3cb
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java
@@ -0,0 +1,25 @@
+package ru.javawebinar.topjava.repository;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+public class JpaUtil {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ public void clear2ndLevelHibernateCache() {
+ Session s = (Session) em.getDelegate();
+ SessionFactory sf = s.getSessionFactory();
+// sf.evict(User.class);
+// sf.getCache().evictEntity(User.class, BaseEntity.START_SEQ);
+// sf.getCache().evictEntityRegion(User.class);
+ sf.getCache().evictQueryRegions();
+ sf.getCache().evictDefaultQueryRegion();
+ sf.getCache().evictCollectionRegions();
+ sf.getCache().evictEntityRegions();
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java
new file mode 100644
index 000000000000..8a7687fb85cb
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java
@@ -0,0 +1,27 @@
+package ru.javawebinar.topjava.repository;
+
+import ru.javawebinar.topjava.model.UserMeal;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+public interface UserMealRepository {
+ // null if updated meal do not belong to userId
+ UserMeal save(UserMeal userMeal, int userId);
+
+ // false if meal do not belong to userId
+ boolean delete(int id, int userId);
+
+ // null if meal do not belong to userId
+ UserMeal get(int id, int userId);
+
+ // ORDERED dateTime
+ Collection getAll(int userId);
+
+ // ORDERED dateTime
+ Collection getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId);
+}
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..24c1c45beca6
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java
@@ -0,0 +1,24 @@
+package ru.javawebinar.topjava.repository;
+
+import ru.javawebinar.topjava.model.User;
+
+import java.util.List;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+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();
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java
new file mode 100644
index 000000000000..520325efc76a
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java
@@ -0,0 +1,53 @@
+package ru.javawebinar.topjava.repository.datajpa;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.repository.UserMealRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * GKislin
+ * 27.03.2015.
+ */
+@Repository
+public class DataJpaUserMealRepositoryImpl implements UserMealRepository {
+ @Autowired
+ private ProxyUserMealRepository proxy;
+
+ @Autowired
+ private ProxyUserRepository userProxy;
+
+ @Override
+ @Transactional
+ public UserMeal save(UserMeal userMeal, int userId) {
+ if (!userMeal.isNew() && get(userMeal.getId(), userId) == null) {
+ return null;
+ }
+ userMeal.setUser(userProxy.getOne(userId));
+ return proxy.save(userMeal);
+ }
+
+ @Override
+ public boolean delete(int id, int userId) {
+ return proxy.delete(id, userId) != 0;
+ }
+
+ @Override
+ public UserMeal get(int id, int userId) {
+ return proxy.get(id, userId);
+ }
+
+ @Override
+ public List getAll(int userId) {
+ return proxy.getAll(userId);
+ }
+
+ @Override
+ public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) {
+ return proxy.getBetween(startDate, endDate, userId);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java
new file mode 100644
index 000000000000..139946a33baa
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java
@@ -0,0 +1,47 @@
+package ru.javawebinar.topjava.repository.datajpa;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Repository;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+
+import java.util.List;
+
+/**
+ * GKislin
+ * 27.03.2015.
+ */
+
+@Repository
+public class DataJpaUserRepositoryImpl implements UserRepository {
+ private static final Sort SORT_NAME_EMAIL = new Sort("name", "email");
+
+ @Autowired
+ private ProxyUserRepository proxy;
+
+ @Override
+ public User save(User user) {
+ return proxy.save(user);
+ }
+
+ @Override
+ public boolean delete(int id) {
+ return proxy.delete(id) != 0;
+ }
+
+ @Override
+ public User get(int id) {
+ return proxy.findOne(id);
+ }
+
+ @Override
+ public User getByEmail(String email) {
+ return proxy.getByEmail(email);
+ }
+
+ @Override
+ public List getAll() {
+ return proxy.findAll(SORT_NAME_EMAIL);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java
new file mode 100644
index 000000000000..6aa835840e6c
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java
@@ -0,0 +1,33 @@
+package ru.javawebinar.topjava.repository.datajpa;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.UserMeal;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Transactional(readOnly = true)
+public interface ProxyUserMealRepository extends JpaRepository {
+
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId")
+ int delete(@Param("id") int id, @Param("userId") int userId);
+
+ @Override
+ UserMeal save(UserMeal item);
+
+ @Query("SELECT m FROM UserMeal m WHERE m.id=:id AND m.user.id=:userId")
+ UserMeal get(@Param("id") int id, @Param("userId") int userId);
+
+ @Query("SELECT m FROM UserMeal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC")
+ List getAll(@Param("userId") int userId);
+
+ @SuppressWarnings("JpaQlInspection")
+ @Query("SELECT m from UserMeal m WHERE m.user.id=:userId AND m.dateTime BETWEEN :startDate AND :endDate ORDER BY m.dateTime DESC")
+ List getBetween(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate, @Param("userId") int userId);
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java
new file mode 100644
index 000000000000..092d847f2bb0
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java
@@ -0,0 +1,37 @@
+package ru.javawebinar.topjava.repository.datajpa;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.User;
+
+import java.util.List;
+
+/**
+ * GKislin
+ * 27.03.2015.
+ */
+@Transactional(readOnly = true)
+public interface ProxyUserRepository extends JpaRepository {
+
+ @Transactional
+ @Modifying
+// @Query(name = User.DELETE)
+ @Query("DELETE FROM User u WHERE u.id=:id")
+ int delete(@Param("id") int id);
+
+ @Override
+ @Transactional
+ User save(User user);
+
+ @Override
+ User findOne(Integer id);
+
+ @Override
+ List findAll(Sort sort);
+
+ User getByEmail(String email);
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java
new file mode 100644
index 000000000000..66d4524c50a7
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/AbstractJdbcUserMealRepositoryImpl.java
@@ -0,0 +1,91 @@
+package ru.javawebinar.topjava.repository.jdbc;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.support.DataAccessUtils;
+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.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.repository.UserMealRepository;
+
+import javax.sql.DataSource;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * User: gkislin
+ * Date: 26.08.2014
+ */
+@Transactional(readOnly = true)
+public abstract class AbstractJdbcUserMealRepositoryImpl implements UserMealRepository {
+
+ private final SimpleJdbcInsert insertUserMeal;
+
+ private final RowMapper rowMapper;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ public AbstractJdbcUserMealRepositoryImpl(RowMapper rowMapper, DataSource dataSource) {
+ this.rowMapper = rowMapper;
+ this.insertUserMeal = new SimpleJdbcInsert(dataSource)
+ .withTableName("meals")
+ .usingGeneratedKeyColumns("id");
+ }
+
+ protected abstract T toDbValue(LocalDateTime ldt);
+
+ @Override
+ @Transactional
+ public UserMeal save(UserMeal userMeal, int userId) {
+ MapSqlParameterSource map = new MapSqlParameterSource()
+ .addValue("id", userMeal.getId())
+ .addValue("description", userMeal.getDescription())
+ .addValue("calories", userMeal.getCalories())
+ .addValue("date_time", toDbValue(userMeal.getDateTime()))
+ .addValue("user_id", userId);
+
+ if (userMeal.isNew()) {
+ Number newId = insertUserMeal.executeAndReturnKey(map);
+ userMeal.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 userMeal;
+ }
+
+ @Override
+ @Transactional
+ public boolean delete(int id, int userId) {
+ return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0;
+ }
+
+ @Override
+ public UserMeal get(int id, int userId) {
+ List userMeals = jdbcTemplate.query(
+ "SELECT * FROM meals WHERE id = ? AND user_id = ?", rowMapper, id, userId);
+ return DataAccessUtils.singleResult(userMeals);
+ }
+
+ public List getAll(int userId) {
+ return jdbcTemplate.query(
+ "SELECT * FROM meals WHERE user_id=? ORDER BY date_time DESC", rowMapper, userId);
+ }
+
+ @Override
+ public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) {
+ return jdbcTemplate.query(
+ "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC",
+ rowMapper, userId, toDbValue(startDate), toDbValue(endDate));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/Java8JdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/Java8JdbcUserMealRepositoryImpl.java
new file mode 100644
index 000000000000..7bbb7ae02d08
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/Java8JdbcUserMealRepositoryImpl.java
@@ -0,0 +1,31 @@
+package ru.javawebinar.topjava.repository.jdbc;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.stereotype.Repository;
+import ru.javawebinar.topjava.Profiles;
+import ru.javawebinar.topjava.model.UserMeal;
+
+import javax.sql.DataSource;
+import java.time.LocalDateTime;
+
+/**
+ * User: gkislin
+ * Date: 26.08.2014
+ */
+
+@Repository
+@Profile(Profiles.POSTGRES)
+public class Java8JdbcUserMealRepositoryImpl extends AbstractJdbcUserMealRepositoryImpl {
+
+ @Autowired
+ public Java8JdbcUserMealRepositoryImpl(DataSource dataSource) {
+ super(BeanPropertyRowMapper.newInstance(UserMeal.class), dataSource);
+ }
+
+ @Override
+ protected LocalDateTime toDbValue(LocalDateTime ldt) {
+ return ldt;
+ }
+}
\ No newline at end of file
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..86accdf3146d
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java
@@ -0,0 +1,160 @@
+package ru.javawebinar.topjava.repository.jdbc;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.support.DataAccessUtils;
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
+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 org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.Role;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+
+import javax.sql.DataSource;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * User: gkislin
+ * Date: 26.08.2014
+ */
+
+@Repository
+@Transactional(readOnly = true)
+public class JdbcUserRepositoryImpl implements UserRepository {
+
+ private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class);
+ private static final RowMapper ROLE_ROW_MAPPER = (rs, rowNum) -> Role.valueOf(rs.getString("role"));
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ private SimpleJdbcInsert insertUser;
+
+ @Autowired
+ public JdbcUserRepositoryImpl(DataSource dataSource) {
+ this.insertUser = new SimpleJdbcInsert(dataSource)
+ .withTableName("USERS")
+ .usingGeneratedKeyColumns("id");
+ }
+
+ @Override
+ @Transactional
+ 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());
+ insertRoles(user);
+ } else {
+ deleteRoles(user);
+ insertRoles(user);
+ namedParameterJdbcTemplate.update(
+ "UPDATE users SET name=:name, email=:email, password=:password, " +
+ "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map);
+ }
+ return user;
+ }
+
+ @Override
+ @Transactional
+ 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 setRoles(DataAccessUtils.singleResult(users));
+ }
+
+ @Override
+ public User getByEmail(String email) {
+ return setRoles(jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email));
+ }
+
+ @Override
+ public List getAll() {
+ List users = jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER);
+
+ class UserRole {
+ UserRole(Role role, int userId) {
+ this.role = role;
+ this.userId = userId;
+ }
+
+ final private Role role;
+ final private int userId;
+
+ public Role getRole() {
+ return role;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+ }
+
+ Map> userRolesMap = jdbcTemplate.query("SELECT role, user_id FROM user_roles",
+ (rs, rowNum) -> new UserRole(Role.valueOf(rs.getString("role")), rs.getInt("user_id"))).stream()
+ .collect(
+ Collectors.groupingBy(UserRole::getUserId, Collectors.mapping(UserRole::getRole, Collectors.toList()))
+ );
+
+ users.forEach(u -> u.setRoles(userRolesMap.get(u.getId())));
+ return users;
+ }
+
+ private void insertRoles(User u) {
+ Set roles = u.getRoles();
+ Iterator iterator = roles.iterator();
+
+ jdbcTemplate.batchUpdate("INSERT INTO user_roles (user_id, role) VALUES (?, ?)",
+ new BatchPreparedStatementSetter() {
+ @Override
+ public void setValues(PreparedStatement ps, int i) throws SQLException {
+ ps.setInt(1, u.getId());
+ ps.setString(2, iterator.next().name());
+ }
+
+ @Override
+ public int getBatchSize() {
+ return roles.size();
+ }
+ });
+ }
+
+ private void deleteRoles(User u) {
+ jdbcTemplate.update("DELETE FROM user_roles WHERE user_id=?", u.getId());
+ }
+
+ private User setRoles(User u) {
+ if (u != null) {
+ List roles = jdbcTemplate.query("SELECT role FROM user_roles WHERE user_id=?",
+ ROLE_ROW_MAPPER, u.getId());
+ u.setRoles(roles);
+ }
+ return u;
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/TimestampJdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/TimestampJdbcUserMealRepositoryImpl.java
new file mode 100644
index 000000000000..559bd51d5169
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/TimestampJdbcUserMealRepositoryImpl.java
@@ -0,0 +1,34 @@
+package ru.javawebinar.topjava.repository.jdbc;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Repository;
+import ru.javawebinar.topjava.Profiles;
+import ru.javawebinar.topjava.model.UserMeal;
+
+import javax.sql.DataSource;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+/**
+ * User: gkislin
+ * Date: 26.08.2014
+ */
+
+@Repository
+@Profile(Profiles.HSQLDB)
+public class TimestampJdbcUserMealRepositoryImpl extends AbstractJdbcUserMealRepositoryImpl {
+
+ @Autowired
+ public TimestampJdbcUserMealRepositoryImpl(DataSource dataSource) {
+ super((rs, rowNum) ->
+ new UserMeal(rs.getInt("id"), rs.getTimestamp("date_time").toLocalDateTime(),
+ rs.getString("description"), rs.getInt("calories")), dataSource
+ );
+ }
+
+ @Override
+ protected Timestamp toDbValue(LocalDateTime ldt) {
+ return Timestamp.valueOf(ldt);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java
new file mode 100644
index 000000000000..3e4ead5e0cda
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java
@@ -0,0 +1,74 @@
+package ru.javawebinar.topjava.repository.jpa;
+
+import org.springframework.dao.support.DataAccessUtils;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.repository.UserMealRepository;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * User: gkisline
+ * Date: 26.08.2014
+ */
+
+@Repository
+@Transactional(readOnly = true)
+public class JpaUserMealRepositoryImpl implements UserMealRepository {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ @Transactional
+ public UserMeal save(UserMeal userMeal, int userId) {
+ if (!userMeal.isNew() && get(userMeal.getId(), userId) == null) {
+ return null;
+ }
+ userMeal.setUser(em.getReference(User.class, userId));
+ if (userMeal.isNew()) {
+ em.persist(userMeal);
+ return userMeal;
+ } else {
+ return em.merge(userMeal);
+ }
+ }
+
+ @Override
+ @Transactional
+ public boolean delete(int id, int userId) {
+ return em.createNamedQuery(UserMeal.DELETE)
+ .setParameter("id", id)
+ .setParameter("userId", userId)
+ .executeUpdate() != 0;
+ }
+
+ @Override
+ public UserMeal get(int id, int userId) {
+ List userMeals = em.createNamedQuery(UserMeal.GET, UserMeal.class)
+ .setParameter("id", id)
+ .setParameter("userId", userId)
+ .getResultList();
+ return DataAccessUtils.singleResult(userMeals);
+ }
+
+ @Override
+ public List getAll(int userId) {
+ return em.createNamedQuery(UserMeal.ALL_SORTED, UserMeal.class)
+ .setParameter("userId", userId)
+ .getResultList();
+ }
+
+ @Override
+ public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) {
+ return em.createNamedQuery(UserMeal.GET_BETWEEN, UserMeal.class)
+ .setParameter("userId", userId)
+ .setParameter("startDate", startDate)
+ .setParameter("endDate", endDate).getResultList();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java
new file mode 100644
index 000000000000..2b4057f3a4b4
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java
@@ -0,0 +1,70 @@
+package ru.javawebinar.topjava.repository.jpa;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.List;
+
+/**
+ * User: gkislin
+ * Date: 29.08.2014
+ */
+@Repository
+@Transactional(readOnly = true)
+public class JpaUserRepositoryImpl implements UserRepository {
+
+/*
+ @Autowired
+ private SessionFactory sessionFactory;
+
+ private Session openSession() {
+ return sessionFactory.getCurrentSession();
+ }
+*/
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ @Transactional
+ public User save(User user) {
+ if (user.isNew()) {
+ em.persist(user);
+ return user;
+ } else {
+ return em.merge(user);
+ }
+ }
+
+ @Override
+ public User get(int id) {
+ return em.find(User.class, id);
+ }
+
+ @Override
+ @Transactional
+ public boolean delete(int id) {
+
+/* User ref = em.getReference(User.class, id);
+ em.remove(ref);
+
+ Query query = em.createQuery("DELETE FROM User u WHERE u.id=:id");
+ return query.setParameter("id", id).executeUpdate() != 0;
+*/
+ return em.createNamedQuery(User.DELETE).setParameter("id", id).executeUpdate() != 0;
+ }
+
+ @Override
+ public User getByEmail(String email) {
+ return em.createNamedQuery(User.BY_EMAIL, User.class).setParameter(1, email).getSingleResult();
+ }
+
+ @Override
+ public List getAll() {
+ return em.createNamedQuery(User.ALL_SORTED, User.class).getResultList();
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealService.java b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java
new file mode 100644
index 000000000000..9e4ea6413504
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java
@@ -0,0 +1,31 @@
+package ru.javawebinar.topjava.service;
+
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Collection;
+
+/**
+ * GKislin
+ * 15.06.2015.
+ */
+public interface UserMealService {
+ UserMeal get(int id, int userId) throws NotFoundException;
+
+ void delete(int id, int userId) throws NotFoundException;
+
+ default Collection getBetweenDates(LocalDate startDate, LocalDate endDate, int userId) {
+ return getBetweenDateTimes(LocalDateTime.of(startDate, LocalTime.MIN), LocalDateTime.of(endDate, LocalTime.MAX), userId);
+ }
+
+ Collection getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId);
+
+ Collection getAll(int userId);
+
+ UserMeal update(UserMeal meal, int userId) throws NotFoundException;
+
+ UserMeal save(UserMeal meal, int userId);
+}
diff --git a/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java
new file mode 100644
index 000000000000..fe2aa873de5c
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java
@@ -0,0 +1,51 @@
+package ru.javawebinar.topjava.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.repository.UserMealRepository;
+import ru.javawebinar.topjava.util.exception.ExceptionUtil;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@Service
+public class UserMealServiceImpl implements UserMealService {
+
+ @Autowired
+ private UserMealRepository repository;
+
+ @Override
+ public UserMeal get(int id, int userId) {
+ return ExceptionUtil.check(repository.get(id, userId), id);
+ }
+
+ @Override
+ public void delete(int id, int userId) {
+ ExceptionUtil.check(repository.delete(id, userId), id);
+ }
+
+ @Override
+ public Collection getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) {
+ return repository.getBetween(startDateTime, endDateTime, userId);
+ }
+
+ @Override
+ public Collection getAll(int userId) {
+ return repository.getAll(userId);
+ }
+
+ @Override
+ public UserMeal update(UserMeal meal, int userId) {
+ return ExceptionUtil.check(repository.save(meal, userId), meal.getId());
+ }
+
+ @Override
+ public UserMeal save(UserMeal meal, int userId) {
+ return repository.save(meal, userId);
+ }
+}
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..080b94b93c93
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java
@@ -0,0 +1,28 @@
+package ru.javawebinar.topjava.service;
+
+
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+import java.util.List;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+public interface UserService {
+
+ User save(User user);
+
+ void delete(int id) throws NotFoundException;
+
+ User get(int id) throws NotFoundException;
+
+ User getByEmail(String email) throws NotFoundException;
+
+ List getAll();
+
+ void update(User user);
+
+ void evictCache();
+}
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..a5e150e635f0
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java
@@ -0,0 +1,59 @@
+package ru.javawebinar.topjava.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+import ru.javawebinar.topjava.util.exception.ExceptionUtil;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.List;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@Service
+public class UserServiceImpl implements UserService {
+
+ @Autowired
+ private UserRepository repository;
+
+ @CacheEvict(value = "users", allEntries = true)
+ public User save(User user) {
+ return repository.save(user);
+ }
+
+ @CacheEvict(value = "users", allEntries = true)
+ public void delete(int id) {
+ ExceptionUtil.check(repository.delete(id), id);
+ }
+
+ public User get(int id) throws NotFoundException {
+ return ExceptionUtil.check(repository.get(id), id);
+ }
+
+ public User getByEmail(String email) throws NotFoundException {
+ Objects.requireNonNull(email, "Email must not be empty");
+ return ExceptionUtil.check(repository.getByEmail(email), "email=" + email);
+ }
+
+ @Cacheable("users")
+ public List getAll() {
+ return repository.getAll();
+ }
+
+ @CacheEvict(value = "users", allEntries = true)
+ public void update(User user) {
+ repository.save(user);
+ }
+
+ @CacheEvict(value = "users", allEntries = true)
+ @Override
+ public void evictCache() {
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java
new file mode 100644
index 000000000000..3dcc17d30b2f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java
@@ -0,0 +1,62 @@
+package ru.javawebinar.topjava.to;
+
+import java.time.LocalDateTime;
+
+/**
+ * GKislin
+ * 11.01.2015.
+ */
+public class UserMealWithExceed {
+ protected final Integer id;
+
+ 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(null, dateTime, description, calories, exceed);
+ }
+
+ public UserMealWithExceed(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{" +
+ "id=" + id +
+ ", dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ ", 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..c3a10cd27642
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java
@@ -0,0 +1,37 @@
+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;
+
+/**
+ * GKislin
+ * 07.01.2015.
+ */
+public class TimeUtil {
+ public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+ public static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1);
+ public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1);
+
+ private TimeUtil() {
+ }
+
+ public static > boolean isBetween(T value, T start, T end) {
+ return value.compareTo(start) >= 0 && value.compareTo(end) <= 0;
+ }
+
+ public static String toString(LocalDateTime ldt) {
+ 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/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java
new file mode 100644
index 000000000000..dea298e4cf4c
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java
@@ -0,0 +1,71 @@
+package ru.javawebinar.topjava.util;
+
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.to.UserMealWithExceed;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * GKislin
+ * 31.05.2015.
+ */
+public class UserMealsUtil {
+ public static final List MEAL_LIST = 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)
+ );
+
+ public static final int DEFAULT_CALORIES_PER_DAY = 2000;
+
+ public static void main(String[] args) {
+ List filteredMealsWithExceeded = getFilteredWithExceeded(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY);
+ filteredMealsWithExceeded.forEach(System.out::println);
+
+ System.out.println(getFilteredWithExceededByCycle(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), DEFAULT_CALORIES_PER_DAY));
+ }
+
+ public static List getWithExceeded(Collection mealList, int caloriesPerDay) {
+ return getFilteredWithExceeded(mealList, LocalTime.MIN, LocalTime.MAX, caloriesPerDay);
+ }
+
+ public static List getFilteredWithExceeded(Collection mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
+ Map caloriesSumByDate = mealList.stream()
+ .collect(
+ Collectors.groupingBy(um -> um.getDateTime().toLocalDate(),
+ Collectors.summingInt(UserMeal::getCalories))
+ );
+
+ return mealList.stream()
+ .filter(um -> TimeUtil.isBetween(um.getDateTime().toLocalTime(), startTime, endTime))
+ .map(um -> createWithExceed(um, caloriesSumByDate.get(um.getDateTime().toLocalDate()) > caloriesPerDay))
+ .collect(Collectors.toList());
+ }
+
+ public static List getFilteredWithExceededByCycle(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
+
+ final Map caloriesSumPerDate = new HashMap<>();
+ mealList.forEach(meal -> caloriesSumPerDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum));
+
+ final List mealExceeded = new ArrayList<>();
+ mealList.forEach(meal -> {
+ final LocalDateTime dateTime = meal.getDateTime();
+ if (TimeUtil.isBetween(dateTime.toLocalTime(), startTime, endTime)) {
+ mealExceeded.add(createWithExceed(meal, caloriesSumPerDate.get(dateTime.toLocalDate()) > caloriesPerDay));
+ }
+ });
+ return mealExceeded;
+ }
+
+ public static UserMealWithExceed createWithExceed(UserMeal um, boolean exceeded) {
+ return new UserMealWithExceed(um.getId(), um.getDateTime(), um.getDescription(), um.getCalories(), exceeded);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java
new file mode 100644
index 000000000000..9fd18938830f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java
@@ -0,0 +1,30 @@
+package ru.javawebinar.topjava.util.exception;
+
+
+/**
+ * User: gkislin
+ * Date: 14.05.2014
+ */
+public class ExceptionUtil {
+ private ExceptionUtil() {
+ }
+
+ public static void check(boolean found, int id) {
+ check(found, "id=" + id);
+ }
+
+ public static T check(T object, int id) {
+ return check(object, "id=" + id);
+ }
+
+ public static T check(T object, String msg) {
+ check(object != null, msg);
+ return object;
+ }
+
+ public static void check(boolean found, String msg) {
+ if (!found) {
+ throw new NotFoundException("Not found entity with " + msg);
+ }
+ }
+}
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..7a770f0db834
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java
@@ -0,0 +1,11 @@
+package ru.javawebinar.topjava.util.exception;
+
+/**
+ * User: gkislin
+ * Date: 19.08.2014
+ */
+public class NotFoundException extends RuntimeException {
+ public NotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java
new file mode 100644
index 000000000000..0046617052f6
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java
@@ -0,0 +1,43 @@
+package ru.javawebinar.topjava.web;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import ru.javawebinar.topjava.LoggedUser;
+import ru.javawebinar.topjava.service.UserMealService;
+import ru.javawebinar.topjava.service.UserService;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+@Controller
+public class RootController {
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private UserMealService mealService;
+
+ @RequestMapping(value = "/", method = RequestMethod.GET)
+ public String root() {
+ return "index";
+ }
+
+ @RequestMapping(value = "/users", method = RequestMethod.GET)
+ public String userList(Model model) {
+ model.addAttribute("userList", userService.getAll());
+ return "userList";
+ }
+
+ @RequestMapping(value = "/users", method = RequestMethod.POST)
+ public String setUser(HttpServletRequest request) {
+ int userId = Integer.valueOf(request.getParameter("userId"));
+ LoggedUser.setId(userId);
+ return "redirect:meals";
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java
new file mode 100644
index 000000000000..c045bc7e6428
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractUserMealController.java
@@ -0,0 +1,70 @@
+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.LoggedUser;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.service.UserMealService;
+import ru.javawebinar.topjava.to.UserMealWithExceed;
+import ru.javawebinar.topjava.util.TimeUtil;
+import ru.javawebinar.topjava.util.UserMealsUtil;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.List;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@Controller
+public class AbstractUserMealController {
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractUserMealController.class);
+
+ @Autowired
+ private UserMealService service;
+
+ public UserMeal get(int id) {
+ int userId = LoggedUser.id();
+ LOG.info("get meal {} for User {}", id, userId);
+ return service.get(id, userId);
+ }
+
+ public void delete(int id) {
+ int userId = LoggedUser.id();
+ LOG.info("delete meal {} for User {}", id, userId);
+ service.delete(id, userId);
+ }
+
+ public List getAll() {
+ int userId = LoggedUser.id();
+ LOG.info("getAll for User {}", userId);
+ return UserMealsUtil.getWithExceeded(service.getAll(userId), LoggedUser.getCaloriesPerDay());
+ }
+
+ public void update(UserMeal meal, int id) {
+ meal.setId(id);
+ int userId = LoggedUser.id();
+ LOG.info("update {} for User {}", meal, userId);
+ service.update(meal, userId);
+ }
+
+ public UserMeal create(UserMeal meal) {
+ meal.setId(null);
+ int userId = LoggedUser.id();
+ LOG.info("create {} for User {}", meal, userId);
+ return service.save(meal, userId);
+ }
+
+ public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) {
+ int userId = LoggedUser.id();
+ LOG.info("getBetween dates {} - {} for time {} - {} for User {}", startDate, endDate, startTime, endTime, userId);
+ return UserMealsUtil.getFilteredWithExceeded(
+ service.getBetweenDates(
+ startDate != null ? startDate : TimeUtil.MIN_DATE, endDate != null ? endDate : TimeUtil.MAX_DATE, userId),
+ startTime != null ? startTime : LocalTime.MIN, endTime != null ? endTime : LocalTime.MAX, LoggedUser.getCaloriesPerDay()
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java
new file mode 100644
index 000000000000..148810c687db
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspUserMealController.java
@@ -0,0 +1,84 @@
+package ru.javawebinar.topjava.web.meal;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.util.TimeUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Objects;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@Controller
+@RequestMapping(value = "/meals")
+public class JspUserMealController extends AbstractUserMealController {
+
+ @RequestMapping(method = RequestMethod.GET)
+ public String mealList(Model model) {
+ model.addAttribute("mealList", super.getAll());
+ return "mealList";
+ }
+
+ @RequestMapping(value = "/delete", method = RequestMethod.GET)
+ public String delete(HttpServletRequest request) {
+ super.delete(getId(request));
+ return "redirect:/meals";
+ }
+
+ @RequestMapping(value = "/update", method = RequestMethod.GET)
+ public String editForUpdate(HttpServletRequest request, Model model) {
+ model.addAttribute("meal", super.get(getId(request)));
+ return "mealEdit";
+ }
+
+ @RequestMapping(value = "/create", method = RequestMethod.GET)
+ public String editForCreate(Model model) {
+ model.addAttribute("meal", new UserMeal(LocalDateTime.now(), "", 1000));
+ return "mealEdit";
+ }
+
+ @RequestMapping(method = RequestMethod.POST)
+ public String updateOrCreate(HttpServletRequest request) {
+ String id = request.getParameter("id");
+ UserMeal userMeal = new UserMeal(id.isEmpty() ? null : Integer.valueOf(id),
+ LocalDateTime.parse(request.getParameter("dateTime")),
+ request.getParameter("description"),
+ Integer.valueOf(request.getParameter("calories")));
+
+ if (userMeal.isNew()) {
+ super.create(userMeal);
+ } else {
+ super.update(userMeal, userMeal.getId());
+ }
+ return "redirect:/meals";
+ }
+
+ @RequestMapping(value = "/filter", method = RequestMethod.POST)
+ public String getBetween(HttpServletRequest request, Model model) {
+ LocalDate startDate = TimeUtil.parseLocalDate(resetParam("startDate", request));
+ LocalDate endDate = TimeUtil.parseLocalDate(resetParam("endDate", request));
+ LocalTime startTime = TimeUtil.parseLocalTime(resetParam("startTime", request));
+ LocalTime endTime = TimeUtil.parseLocalTime(resetParam("endTime", request));
+ model.addAttribute("mealList", super.getBetween(startDate, startTime, endDate, endTime));
+ return "mealList";
+ }
+
+ private String resetParam(String param, HttpServletRequest request) {
+ String value = request.getParameter(param);
+ request.setAttribute(param, value);
+ return value;
+ }
+
+ private int getId(HttpServletRequest request) {
+ String paramId = Objects.requireNonNull(request.getParameter("id"));
+ return Integer.valueOf(paramId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java
new file mode 100644
index 000000000000..0dcb0a604b9f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/meal/UserMealRestController.java
@@ -0,0 +1,11 @@
+package ru.javawebinar.topjava.web.meal;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@Controller
+public class UserMealRestController extends AbstractUserMealController {
+}
\ 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..c519e53e6bee
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java
@@ -0,0 +1,51 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.service.UserService;
+
+import java.util.List;
+
+/**
+ * User: gkislin
+ */
+public abstract class AbstractUserController {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Autowired
+ private UserService service;
+
+ public List getAll() {
+ log.info("getAll");
+ return service.getAll();
+ }
+
+ public User get(int id) {
+ log.info("get " + id);
+ return service.get(id);
+ }
+
+ public User create(User user) {
+ user.setId(null);
+ log.info("create " + user);
+ return service.save(user);
+ }
+
+ public void delete(int id) {
+ log.info("delete " + id);
+ service.delete(id);
+ }
+
+ public void update(User user, int id) {
+ user.setId(id);
+ log.info("update " + user);
+ service.update(user);
+ }
+
+ public User getByMail(String email) {
+ log.info("getByEmail " + email);
+ return service.getByEmail(email);
+ }
+}
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..58d9e779d64a
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java
@@ -0,0 +1,59 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import ru.javawebinar.topjava.model.User;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@RestController
+@RequestMapping(AdminRestController.REST_URL)
+public class AdminRestController extends AbstractUserController {
+ static final String REST_URL = "/rest/admin/users";
+
+ @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getAll() {
+ return super.getAll();
+ }
+
+ @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ public User get(@PathVariable("id") int id) {
+ return super.get(id);
+ }
+
+ @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity createWithLocation(@RequestBody User user) {
+ User created = super.create(user);
+ URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath()
+ .path(REST_URL + "/{id}")
+ .buildAndExpand(created.getId()).toUri();
+ HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.setLocation(uriOfNewResource);
+
+ return new ResponseEntity<>(created, httpHeaders, HttpStatus.CREATED);
+ }
+
+ @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
+ public void delete(@PathVariable("id") int id) {
+ super.delete(id);
+ }
+
+ @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
+ public void update(@RequestBody User user, @PathVariable("id") int id) {
+ super.update(user, id);
+ }
+
+ @RequestMapping(value = "/by", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ public User getByMail(@RequestParam("email") String email) {
+ return super.getByMail(email);
+ }
+}
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..ee3175f47334
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java
@@ -0,0 +1,34 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import ru.javawebinar.topjava.LoggedUser;
+import ru.javawebinar.topjava.model.User;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@RestController
+@RequestMapping(ProfileRestController.REST_URL)
+public class ProfileRestController extends AbstractUserController {
+ public static final String REST_URL = "/rest/profile";
+
+ @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ public User get() {
+ return super.get(LoggedUser.id());
+ }
+
+ @RequestMapping(method = RequestMethod.DELETE)
+ public void delete() {
+ super.delete(LoggedUser.id());
+ }
+
+ @RequestMapping(method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
+ public void update(@RequestBody User user) {
+ super.update(user, LoggedUser.id());
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml
new file mode 100644
index 000000000000..b0275e589a4a
--- /dev/null
+++ b/src/main/resources/cache/ehcache.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/cache/ehcache.xsd b/src/main/resources/cache/ehcache.xsd
new file mode 100644
index 000000000000..bfc19ddb1e7d
--- /dev/null
+++ b/src/main/resources/cache/ehcache.xsd
@@ -0,0 +1,419 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties
new file mode 100644
index 000000000000..a9449359016a
--- /dev/null
+++ b/src/main/resources/db/hsqldb.properties
@@ -0,0 +1,12 @@
+#database.url=jdbc:hsqldb:file:D:/temp/topjava
+database.url=jdbc:hsqldb:mem:topjava
+
+database.username=sa
+database.password=
+
+database.init=true
+
+jdbc.initLocation=initDB_hsql.sql
+jpa.showSql=true
+hibernate.format_sql=true
+hibernate.use_sql_comments=true
diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql
new file mode 100644
index 000000000000..aede3736c9d0
--- /dev/null
+++ b/src/main/resources/db/initDB.sql
@@ -0,0 +1,36 @@
+DROP TABLE IF EXISTS user_roles;
+DROP TABLE IF EXISTS meals;
+DROP TABLE IF EXISTS users;
+DROP SEQUENCE IF EXISTS global_seq;
+
+CREATE SEQUENCE global_seq START 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(),
+ enabled BOOL DEFAULT TRUE,
+ calories_per_day INTEGER DEFAULT 2000 NOT NULL
+);
+CREATE UNIQUE INDEX users_unique_email_idx ON users (email);
+
+CREATE TABLE user_roles
+(
+ user_id INTEGER NOT NULL,
+ role VARCHAR,
+ CONSTRAINT user_roles_idx UNIQUE (user_id, role),
+ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
+);
+
+CREATE TABLE meals (
+ id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
+ user_id INTEGER NOT NULL,
+ date_time TIMESTAMP NOT NULL,
+ description TEXT NOT NULL,
+ calories INT NOT NULL,
+ FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
+);
+CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals(user_id, date_time)
\ No newline at end of file
diff --git a/src/main/resources/db/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql
new file mode 100644
index 000000000000..56f978ac3b03
--- /dev/null
+++ b/src/main/resources/db/initDB_hsql.sql
@@ -0,0 +1,37 @@
+DROP TABLE user_roles IF EXISTS;
+DROP TABLE meals IF EXISTS;
+DROP TABLE users IF EXISTS;
+DROP SEQUENCE global_seq IF EXISTS;
+
+CREATE SEQUENCE GLOBAL_SEQ AS INTEGER START WITH 100000;
+
+CREATE TABLE users
+(
+ id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
+ name VARCHAR(255),
+ email VARCHAR(255) NOT NULL,
+ password VARCHAR(255) NOT NULL,
+ registered TIMESTAMP DEFAULT now(),
+ enabled BOOLEAN DEFAULT TRUE,
+ calories_per_day INTEGER DEFAULT 2000 NOT NULL
+);
+CREATE UNIQUE INDEX users_unique_email_idx ON USERS ( email );
+
+CREATE TABLE user_roles
+(
+ user_id INTEGER NOT NULL,
+ role VARCHAR(255),
+ CONSTRAINT user_roles_idx UNIQUE (user_id, role),
+ FOREIGN KEY ( user_id ) REFERENCES USERS ( id ) ON DELETE CASCADE
+);
+
+CREATE TABLE meals
+(
+ id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
+ date_time TIMESTAMP NOT NULL,
+ description VARCHAR(255) NOT NULL,
+ calories INT NOT NULL,
+ user_id INTEGER NOT NULL,
+ FOREIGN KEY ( user_id ) REFERENCES USERS ( id ) ON DELETE CASCADE
+);
+CREATE UNIQUE INDEX meals_unique_user_datetime_idx ON meals(user_id, date_time)
\ No newline at end of file
diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql
new file mode 100644
index 000000000000..cc816500ce36
--- /dev/null
+++ b/src/main/resources/db/populateDB.sql
@@ -0,0 +1,25 @@
+DELETE FROM user_roles;
+DELETE FROM meals;
+DELETE FROM users;
+ALTER SEQUENCE global_seq RESTART WITH 100000;
+
+INSERT INTO users (name, email, password)
+VALUES ('User', 'user@yandex.ru', 'password');
+
+INSERT INTO users (name, email, password)
+VALUES ('Admin', 'admin@gmail.com', 'admin');
+
+INSERT INTO user_roles (role, user_id) VALUES
+ ('ROLE_USER', 100000),
+ ('ROLE_ADMIN', 100001),
+ ('ROLE_USER', 100001);
+
+INSERT INTO meals (date_time, description, calories, user_id) VALUES
+ ('2015-05-30 10:00:00', 'Завтрак', 500, 100000),
+ ('2015-05-30 13:00:00', 'Обед', 1000, 100000),
+ ('2015-05-30 20:00:00', 'Ужин', 500, 100000),
+ ('2015-05-31 10:00:00', 'Завтрак', 500, 100000),
+ ('2015-05-31 13:00:00', 'Обед', 1000, 100000),
+ ('2015-05-31 20:00:00', 'Ужин', 510, 100000),
+ ('2015-06-01 14:00:00', 'Админ ланч', 510, 100001),
+ ('2015-06-01 21:00:00', 'Админ ужин', 1500, 100001);
diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties
new file mode 100644
index 000000000000..bd2c5ac08c2c
--- /dev/null
+++ b/src/main/resources/db/postgres.properties
@@ -0,0 +1,13 @@
+#database.url=jdbc:postgresql://ec2-54-217-202-110.eu-west-1.compute.amazonaws.com:5432/dehm6lvm8bink0?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory
+#database.username=wegxlfzjjgxaxy
+#database.password=SSQyKKE_e93kiUCR-ehzMcKCxZ
+
+database.init=true
+database.driverClassName=org.postgresql.Driver
+jdbc.initLocation=initDB.sql
+jpa.showSql=true
+hibernate.format_sql=true
+hibernate.use_sql_comments=true
+database.url=jdbc:postgresql://localhost:5432/postgres
+database.username=postgres
+database.password=1234
diff --git a/src/main/resources/db/tomcat.properties b/src/main/resources/db/tomcat.properties
new file mode 100644
index 000000000000..09ed9c2548ee
--- /dev/null
+++ b/src/main/resources/db/tomcat.properties
@@ -0,0 +1,7 @@
+database.init=false
+
+jdbc.initLocation=initDB.sql
+
+jpa.showSql=true
+hibernate.format_sql=true
+hibernate.use_sql_comments=true
\ No newline at end of file
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 000000000000..198d5663f852
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ true
+
+
+
+
+
+
+ ${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/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml
new file mode 100644
index 000000000000..c3e7587cfdea
--- /dev/null
+++ b/src/main/resources/spring/spring-app.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml
new file mode 100644
index 000000000000..60c491252ff7
--- /dev/null
+++ b/src/main/resources/spring/spring-db.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml
new file mode 100644
index 000000000000..12ac233daa15
--- /dev/null
+++ b/src/main/resources/spring/spring-mvc.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring-tools.xml b/src/main/resources/spring/spring-tools.xml
new file mode 100644
index 000000000000..784de2d79e20
--- /dev/null
+++ b/src/main/resources/spring/spring-tools.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/tomcat/context.xml b/src/main/resources/tomcat/context.xml
new file mode 100644
index 000000000000..49b5ab759bc8
--- /dev/null
+++ b/src/main/resources/tomcat/context.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+ WEB-INF/web.xml
+ ${catalina.base}/conf/web.xml
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp
new file mode 100644
index 000000000000..833a84d0b2a4
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp
@@ -0,0 +1,4 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp
new file mode 100644
index 000000000000..997a1677296c
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp
@@ -0,0 +1,4 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp
new file mode 100644
index 000000000000..90f86d23e672
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp
@@ -0,0 +1,12 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+
+
+
+
+ ${pageContext.request.requestURL}
+
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp
new file mode 100644
index 000000000000..0208cf97703b
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/index.jsp
@@ -0,0 +1,24 @@
+<%@ 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" %>
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/mealEdit.jsp b/src/main/webapp/WEB-INF/jsp/mealEdit.jsp
new file mode 100644
index 000000000000..3df0c26c6dc9
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/mealEdit.jsp
@@ -0,0 +1,33 @@
+<%@ 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" %>
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/mealList.jsp b/src/main/webapp/WEB-INF/jsp/mealList.jsp
new file mode 100644
index 000000000000..a29dcedd0645
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/mealList.jsp
@@ -0,0 +1,62 @@
+<%@ page import="ru.javawebinar.topjava.util.TimeUtil" %>
+<%@ 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" %>
+
+
+
+
+
+ <%--http://stackoverflow.com/questions/10327390/how-should-i-get-root-folder-path-in-jsp-page--%>
+
+
+
+
+ Add Meal
+
+
+
+
+ | Date |
+ Description |
+ Calories |
+ |
+ |
+
+
+
+
+
+ |
+ <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%>
+ <%=TimeUtil.toString(meal.getDateTime())%>
+ |
+ ${meal.description} |
+ ${meal.calories} |
+ Update |
+ Delete |
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/userList.jsp b/src/main/webapp/WEB-INF/jsp/userList.jsp
new file mode 100644
index 000000000000..e3b6ed640305
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/userList.jsp
@@ -0,0 +1,37 @@
+<%@ 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" %>
+
+
+
+
+
+
+
+
+
+
+
+ | Name |
+ Email |
+ Roles |
+ Active |
+ Registered |
+
+
+
+
+
+ |
+ ${user.email} |
+ ${user.roles} |
+ <%=user.isEnabled()%>
+ |
+ |
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000000..abc0d013bcfc
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,56 @@
+
+
+ Topjava
+
+
+ spring.profiles.default
+ postgres,datajpa
+
+
+
+ contextConfigLocation
+
+ classpath:spring/spring-app.xml
+ classpath:spring/spring-db.xml
+
+
+
+
+
+ org.springframework.web.context.ContextLoaderListener
+
+
+ mvc-dispatcher
+ org.springframework.web.servlet.DispatcherServlet
+
+ contextConfigLocation
+ classpath:spring/spring-mvc.xml
+
+ 1
+
+
+ mvc-dispatcher
+ /
+
+
+
+ encodingFilter
+ org.springframework.web.filter.CharacterEncodingFilter
+
+ encoding
+ UTF-8
+
+
+ forceEncoding
+ true
+
+
+
+ encodingFilter
+ /*
+
+
+
diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css
new file mode 100644
index 000000000000..d4807fb38efb
--- /dev/null
+++ b/src/main/webapp/resources/css/style.css
@@ -0,0 +1,32 @@
+dl {
+ background: none repeat scroll 0 0 #FAFAFA;
+ margin: 8px 0;
+ padding: 0;
+}
+
+dt {
+ display: inline-block;
+ width: 170px;
+}
+
+dd {
+ display: inline-block;
+ margin-left: 8px;
+ vertical-align: top;
+}
+
+.normal {
+ color: green;
+}
+
+.exceeded {
+ color: red;
+}
+
+header, footer {
+ background: none repeat scroll 0 0 #A6C9E2;
+ color: #2E6E9E;
+ font-size: 20px;
+ padding: 5px 20px;
+ margin: 6px 0;
+}
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..8d8b0629675c
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java
@@ -0,0 +1,43 @@
+package ru.javawebinar.topjava;
+
+import ru.javawebinar.topjava.matcher.ModelMatcher;
+import ru.javawebinar.topjava.model.UserMeal;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.util.Arrays;
+import java.util.List;
+
+import static java.time.LocalDateTime.of;
+import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ;
+
+/**
+ * GKislin
+ * 13.03.2015.
+ */
+public class MealTestData {
+
+ public static final ModelMatcher MATCHER = new ModelMatcher<>(UserMeal::toString);
+
+ public static final int MEAL1_ID = START_SEQ + 2;
+ public static final int ADMIN_MEAL_ID = START_SEQ + 8;
+
+ public static final UserMeal MEAL1 = new UserMeal(MEAL1_ID, of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500);
+ public static final UserMeal MEAL2 = new UserMeal(MEAL1_ID + 1, of(2015, Month.MAY, 30, 13, 0), "Обед", 1000);
+ public static final UserMeal MEAL3 = new UserMeal(MEAL1_ID + 2, of(2015, Month.MAY, 30, 20, 0), "Ужин", 500);
+ public static final UserMeal MEAL4 = new UserMeal(MEAL1_ID + 3, of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500);
+ public static final UserMeal MEAL5 = new UserMeal(MEAL1_ID + 4, of(2015, Month.MAY, 31, 13, 0), "Обед", 1000);
+ public static final UserMeal MEAL6 = new UserMeal(MEAL1_ID + 5, of(2015, Month.MAY, 31, 20, 0), "Ужин", 510);
+ public static final UserMeal ADMIN_MEAL = new UserMeal(ADMIN_MEAL_ID, of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510);
+ public static final UserMeal ADMIN_MEAL2 = new UserMeal(ADMIN_MEAL_ID + 1, of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500);
+
+ public static final List USER_MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1);
+
+ public static UserMeal getCreated() {
+ return new UserMeal(null, of(2015, Month.JUNE, 1, 18, 0), "Созданный ужин", 300);
+ }
+
+ public static UserMeal getUpdated() {
+ return new UserMeal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200);
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java
new file mode 100644
index 000000000000..5f8d29495f4c
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java
@@ -0,0 +1,39 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.context.support.GenericXmlApplicationContext;
+import ru.javawebinar.topjava.to.UserMealWithExceed;
+import ru.javawebinar.topjava.web.meal.AbstractUserMealController;
+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;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+public class SpringMain {
+ public static void main(String[] args) {
+ // java 7 Automatic resource management
+ try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) {
+ appCtx.getEnvironment().setActiveProfiles(Profiles.ACTIVE_DB, Profiles.DB_IMPLEMENTATION);
+ appCtx.load("spring/spring-app.xml", "spring/spring-db.xml", "spring/spring-mvc.xml");
+ appCtx.refresh();
+
+ System.out.println(Arrays.toString(appCtx.getBeanDefinitionNames()));
+ AdminRestController adminUserController = appCtx.getBean(AdminRestController.class);
+ System.out.println(adminUserController.get(UserTestData.USER_ID));
+ System.out.println();
+
+ AbstractUserMealController mealController = appCtx.getBean(AbstractUserMealController.class);
+ List filteredMealsWithExceeded =
+ mealController.getBetween(
+ LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0),
+ LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0));
+ filteredMealsWithExceeded.forEach(System.out::println);
+ }
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java
new file mode 100644
index 000000000000..06823a8151d0
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java
@@ -0,0 +1,76 @@
+package ru.javawebinar.topjava;
+
+import ru.javawebinar.topjava.matcher.ModelMatcher;
+import ru.javawebinar.topjava.model.Role;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.util.UserMealsUtil;
+
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+
+import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ;
+
+/**
+ * GKislin
+ * 24.09.2015.
+ */
+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, Role.ROLE_USER);
+
+ public static final ModelMatcher MATCHER = new ModelMatcher<>(u -> ((u instanceof TestUser) ? (TestUser) u : new TestUser(u)));
+
+ public static class TestUser extends User {
+
+ public TestUser(User u) {
+ this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRoles());
+ }
+
+ public TestUser(String name, String email, String password, Role role, Role... roles) {
+ this(null, name, email, password, UserMealsUtil.DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles));
+ }
+
+ public TestUser(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) {
+ super(id, name, email, password, caloriesPerDay, enabled, roles);
+ }
+
+ public User asUser() {
+ return new User(this);
+ }
+
+ @Override
+ public String toString() {
+ return "User (" +
+ "id=" + id +
+ ", email=" + email +
+ ", name=" + name +
+ ", enabled=" + enabled +
+ ", password=" + password +
+ ", authorities=" + roles +
+ ')';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ TestUser that = (TestUser) o;
+ return Objects.equals(this.password, that.password)
+ && Objects.equals(this.id, that.id)
+ && Objects.equals(this.name, that.name)
+ && Objects.equals(this.email, that.email)
+ && Objects.equals(this.caloriesPerDay, that.caloriesPerDay)
+ && Objects.equals(this.enabled, that.enabled)
+ && Objects.equals(this.roles, that.roles);
+ }
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java
new file mode 100644
index 000000000000..f2b18b2e34bd
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/matcher/ModelMatcher.java
@@ -0,0 +1,34 @@
+package ru.javawebinar.topjava.matcher;
+
+import org.junit.Assert;
+
+import java.util.Collection;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * GKislin
+ * 06.01.2015.
+ *
+ * @param : entity
+ * @param : testEntity, converter result for compare
+ */
+public class ModelMatcher {
+ protected Function entityConverter;
+
+ public ModelMatcher(Function entityConverter) {
+ this.entityConverter = entityConverter;
+ }
+
+ public void assertEquals(T expected, T actual) {
+ Assert.assertEquals(entityConverter.apply(expected), entityConverter.apply(actual));
+ }
+
+ public void assertCollectionEquals(Collection expected, Collection actual) {
+ Assert.assertEquals(map(expected, entityConverter), map(actual, entityConverter));
+ }
+
+ public static Collection map(Collection collection, Function converter) {
+ return collection.stream().map(converter).collect(Collectors.toList());
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java
new file mode 100644
index 000000000000..190c7281c572
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserMealRepositoryImpl.java
@@ -0,0 +1,85 @@
+package ru.javawebinar.topjava.repository.mock;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.util.Assert;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.repository.UserMealRepository;
+import ru.javawebinar.topjava.util.TimeUtil;
+import ru.javawebinar.topjava.util.UserMealsUtil;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import static ru.javawebinar.topjava.UserTestData.ADMIN_ID;
+import static ru.javawebinar.topjava.UserTestData.USER_ID;
+
+/**
+ * GKislin
+ * 15.09.2015.
+ */
+@Repository
+public class InMemoryUserMealRepositoryImpl implements UserMealRepository {
+
+ public static final Comparator USER_MEAL_COMPARATOR = Comparator.comparing(UserMeal::getDateTime).reversed();
+
+ // Map userId -> (mealId-> meal)
+ private Map> repository = new ConcurrentHashMap<>();
+ private AtomicInteger counter = new AtomicInteger(0);
+
+ {
+ UserMealsUtil.MEAL_LIST.forEach(um -> save(um, USER_ID));
+
+ save(new UserMeal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID);
+ save(new UserMeal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID);
+ }
+
+ @Override
+ public UserMeal save(UserMeal userMeal, int userId) {
+ Assert.notNull(userMeal, "userMeal must not be null");
+
+ Integer mealId = userMeal.getId();
+
+ if (userMeal.isNew()) {
+ mealId = counter.incrementAndGet();
+ userMeal.setId(mealId);
+ } else if (get(mealId, userId) == null) {
+ return null;
+ }
+ Map userMeals = repository.computeIfAbsent(userId, ConcurrentHashMap::new);
+ userMeals.put(mealId, userMeal);
+ return userMeal;
+ }
+
+ @Override
+ public boolean delete(int id, int userId) {
+ Map userMeals = repository.get(userId);
+ return userMeals != null && userMeals.remove(id) != null;
+ }
+
+ @Override
+ public UserMeal get(int id, int userId) {
+ Map userMeals = repository.get(userId);
+ return userMeals == null ? null : userMeals.get(id);
+ }
+
+ @Override
+ public Collection getAll(int userId) {
+ Map userMeals = repository.get(userId);
+ return userMeals == null ?
+ Collections.emptyList() :
+ userMeals.values().stream().sorted(USER_MEAL_COMPARATOR).collect(Collectors.toList());
+ }
+
+ @Override
+ public Collection getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) {
+ Assert.notNull(startDateTime, "startDateTime must not be null");
+ Assert.notNull(endDateTime, "endDateTime must not be null");
+ return getAll(userId).stream()
+ .filter(um -> TimeUtil.isBetween(um.getDateTime(), startDateTime, endDateTime))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java
new file mode 100644
index 000000000000..cbc13c95fda4
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/repository/mock/InMemoryUserRepositoryImpl.java
@@ -0,0 +1,72 @@
+package ru.javawebinar.topjava.repository.mock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.Assert;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+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;
+
+/**
+ * GKislin
+ * 15.06.2015.
+ */
+@Repository
+public class InMemoryUserRepositoryImpl implements UserRepository {
+ private static final Logger LOG = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class);
+
+ private Map repository = new ConcurrentHashMap<>();
+ private AtomicInteger counter = new AtomicInteger(0);
+
+ public static final Comparator USER_COMPARATOR = Comparator.comparing(User::getName);
+
+ @Override
+ public User save(User user) {
+ Assert.notNull(user, "user must not be null");
+ if (user.isNew()) {
+ user.setId(counter.incrementAndGet());
+ }
+ repository.put(user.getId(), user);
+ return user;
+ }
+
+ @PostConstruct
+ public void postConstruct() {
+ LOG.info("+++ PostConstruct");
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ LOG.info("+++ PreDestroy");
+ }
+
+ @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(USER_COMPARATOR).collect(Collectors.toList());
+ }
+
+ @Override
+ public User getByEmail(String email) {
+ Assert.notNull(email, "email must not be null");
+ return repository.values().stream().filter(u -> email.equals(u.getEmail())).findFirst().orElse(null);
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java
new file mode 100644
index 000000000000..eb8f8e9ea70e
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java
@@ -0,0 +1,21 @@
+package ru.javawebinar.topjava.service;
+
+import org.junit.Before;
+import org.springframework.beans.factory.annotation.Autowired;
+import ru.javawebinar.topjava.repository.JpaUtil;
+
+/**
+ * GKislin
+ * 07.04.2015.
+ */
+abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest {
+ @SuppressWarnings("SpringJavaAutowiringInspection")
+ @Autowired
+ private JpaUtil jpaUtil;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ jpaUtil.clear2ndLevelHibernateCache();
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java
new file mode 100644
index 000000000000..c38423d1bc95
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java
@@ -0,0 +1,51 @@
+package ru.javawebinar.topjava.service;
+
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.Stopwatch;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.jdbc.SqlConfig;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.HashSet;
+import java.util.concurrent.TimeUnit;
+
+import static ru.javawebinar.topjava.Profiles.ACTIVE_DB;
+
+/**
+ * User: gkislin
+ */
+@ContextConfiguration({
+ "classpath:spring/spring-app.xml",
+ "classpath:spring/spring-db.xml"
+})
+@RunWith(SpringJUnit4ClassRunner.class)
+@ActiveProfiles(ACTIVE_DB)
+@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8"))
+abstract public class AbstractServiceTest {
+
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev
+ public Stopwatch stopwatch = new Stopwatch() {
+ private void logInfo(Description description, long nanos) {
+ log.info(String.format("+++ Test %s spent %d microseconds",
+ description.getMethodName(), TimeUnit.NANOSECONDS.toMicros(nanos)));
+ }
+
+ @Override
+ protected void finished(long nanos, Description description) {
+ logInfo(description, nanos);
+ }
+ };
+}
diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java
new file mode 100644
index 000000000000..2385804f8eb7
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserMealServiceTest.java
@@ -0,0 +1,77 @@
+package ru.javawebinar.topjava.service;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.Arrays;
+
+import static ru.javawebinar.topjava.MealTestData.*;
+import static ru.javawebinar.topjava.UserTestData.ADMIN_ID;
+import static ru.javawebinar.topjava.UserTestData.USER_ID;
+
+abstract public class AbstractUserMealServiceTest extends AbstractServiceTest {
+
+ @Autowired
+ protected UserMealService service;
+
+ @Test
+ public void testDelete() throws Exception {
+ service.delete(MEAL1_ID, USER_ID);
+ MATCHER.assertCollectionEquals(Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2), service.getAll(USER_ID));
+ }
+
+ @Test
+ public void testDeleteNotFound() throws Exception {
+ thrown.expect(NotFoundException.class);
+ service.delete(MEAL1_ID, 1);
+ }
+
+ @Test
+ public void testSave() throws Exception {
+ UserMeal created = getCreated();
+ service.save(created, USER_ID);
+ MATCHER.assertCollectionEquals(Arrays.asList(created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1), service.getAll(USER_ID));
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ UserMeal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID);
+ MATCHER.assertEquals(ADMIN_MEAL, actual);
+ }
+
+ @Test
+ public void testGetNotFound() throws Exception {
+ thrown.expect(NotFoundException.class);
+ service.get(MEAL1_ID, ADMIN_ID);
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ UserMeal updated = getUpdated();
+ service.update(updated, USER_ID);
+ MATCHER.assertEquals(updated, service.get(MEAL1_ID, USER_ID));
+ }
+
+ @Test
+ public void testNotFoundUpdate() throws Exception {
+ UserMeal item = service.get(MEAL1_ID, USER_ID);
+ thrown.expect(NotFoundException.class);
+ thrown.expectMessage("Not found entity with id=" + MEAL1_ID);
+ service.update(item, ADMIN_ID);
+ }
+
+ @Test
+ public void testGetAll() throws Exception {
+ MATCHER.assertCollectionEquals(USER_MEALS, service.getAll(USER_ID));
+ }
+
+ @Test
+ public void testGetBetween() throws Exception {
+ MATCHER.assertCollectionEquals(Arrays.asList(MEAL3, MEAL2, MEAL1),
+ service.getBetweenDates(LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java
new file mode 100644
index 000000000000..e2549bd8a731
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java
@@ -0,0 +1,84 @@
+package ru.javawebinar.topjava.service;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import ru.javawebinar.topjava.UserTestData.*;
+import ru.javawebinar.topjava.model.Role;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import static ru.javawebinar.topjava.UserTestData.*;
+
+abstract public class AbstractUserServiceTest extends AbstractServiceTest {
+
+ @Autowired
+ protected UserService service;
+
+ @Before
+ public void setUp() throws Exception {
+ service.evictCache();
+ }
+
+ @Test
+ public void testSave() throws Exception {
+ TestUser tu = new TestUser(null, "New", "new@gmail.com", "newPass", 1555, false, Collections.singleton(Role.ROLE_USER));
+ User created = service.save(tu.asUser());
+ tu.setId(created.getId());
+ MATCHER.assertCollectionEquals(Arrays.asList(ADMIN, tu, USER), service.getAll());
+ }
+
+ @Test(expected = DataAccessException.class)
+ public void testDuplicateMailSave() throws Exception {
+ service.save(new TestUser("Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER).asUser());
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ service.delete(USER_ID);
+ MATCHER.assertCollectionEquals(Collections.singletonList(ADMIN), service.getAll());
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testNotFoundDelete() throws Exception {
+ service.delete(1);
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ User user = service.get(USER_ID);
+ MATCHER.assertEquals(USER, user);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testGetNotFound() throws Exception {
+ service.get(1);
+ }
+
+ @Test
+ public void testGetByEmail() throws Exception {
+ User user = service.getByEmail("user@yandex.ru");
+ MATCHER.assertEquals(USER, user);
+ }
+
+ @Test
+ public void testGetAll() throws Exception {
+ Collection all = service.getAll();
+ MATCHER.assertCollectionEquals(Arrays.asList(ADMIN, USER), all);
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ TestUser updated = new TestUser(USER);
+ updated.setName("UpdatedName");
+ updated.setCaloriesPerDay(330);
+ updated.setRoles(Arrays.asList(Role.ROLE_ADMIN, Role.ROLE_USER));
+ service.update(updated.asUser());
+ MATCHER.assertEquals(updated, service.get(USER_ID));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java
new file mode 100644
index 000000000000..b2252cc6cff4
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserMealServiceTest.java
@@ -0,0 +1,10 @@
+package ru.javawebinar.topjava.service.datajpa;
+
+import org.springframework.test.context.ActiveProfiles;
+import ru.javawebinar.topjava.service.AbstractUserMealServiceTest;
+
+import static ru.javawebinar.topjava.Profiles.DATAJPA;
+
+@ActiveProfiles(DATAJPA)
+public class DataJpaUserMealServiceTest extends AbstractUserMealServiceTest {
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java
new file mode 100644
index 000000000000..77bbf09eaf6a
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java
@@ -0,0 +1,10 @@
+package ru.javawebinar.topjava.service.datajpa;
+
+import org.springframework.test.context.ActiveProfiles;
+import ru.javawebinar.topjava.service.AbstractJpaUserServiceTest;
+
+import static ru.javawebinar.topjava.Profiles.DATAJPA;
+
+@ActiveProfiles(DATAJPA)
+public class DataJpaUserServiceTest extends AbstractJpaUserServiceTest {
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserMealServiceTest.java
new file mode 100644
index 000000000000..d310717c67a5
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserMealServiceTest.java
@@ -0,0 +1,10 @@
+package ru.javawebinar.topjava.service.jdbc;
+
+import org.springframework.test.context.ActiveProfiles;
+import ru.javawebinar.topjava.service.AbstractUserMealServiceTest;
+
+import static ru.javawebinar.topjava.Profiles.JDBC;
+
+@ActiveProfiles(JDBC)
+public class JdbcUserMealServiceTest extends AbstractUserMealServiceTest {
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java
new file mode 100644
index 000000000000..419f68ed1098
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java
@@ -0,0 +1,10 @@
+package ru.javawebinar.topjava.service.jdbc;
+
+import org.springframework.test.context.ActiveProfiles;
+import ru.javawebinar.topjava.service.AbstractUserServiceTest;
+
+import static ru.javawebinar.topjava.Profiles.JDBC;
+
+@ActiveProfiles(JDBC)
+public class JdbcUserServiceTest extends AbstractUserServiceTest {
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserMealServiceTest.java
new file mode 100644
index 000000000000..778235ed8ef0
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserMealServiceTest.java
@@ -0,0 +1,10 @@
+package ru.javawebinar.topjava.service.jpa;
+
+import org.springframework.test.context.ActiveProfiles;
+import ru.javawebinar.topjava.service.AbstractUserMealServiceTest;
+
+import static ru.javawebinar.topjava.Profiles.JPA;
+
+@ActiveProfiles(JPA)
+public class JpaUserMealServiceTest extends AbstractUserMealServiceTest {
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java
new file mode 100644
index 000000000000..ffb1fea03c22
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java
@@ -0,0 +1,10 @@
+package ru.javawebinar.topjava.service.jpa;
+
+import org.springframework.test.context.ActiveProfiles;
+import ru.javawebinar.topjava.service.AbstractJpaUserServiceTest;
+
+import static ru.javawebinar.topjava.Profiles.JPA;
+
+@ActiveProfiles(JPA)
+public class JpaUserServiceTest extends AbstractJpaUserServiceTest {
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java
new file mode 100644
index 000000000000..ebf46a864743
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java
@@ -0,0 +1,64 @@
+package ru.javawebinar.topjava.web;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.filter.CharacterEncodingFilter;
+import ru.javawebinar.topjava.service.UserService;
+
+import javax.annotation.PostConstruct;
+
+import static ru.javawebinar.topjava.Profiles.DATAJPA;
+import static ru.javawebinar.topjava.Profiles.POSTGRES;
+
+/**
+ * User: gkislin
+ * Date: 10.08.2014
+ */
+@ContextConfiguration({
+ "classpath:spring/spring-app.xml",
+ "classpath:spring/spring-mvc.xml",
+ "classpath:spring/spring-db.xml"
+})
+@WebAppConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+@Transactional
+@ActiveProfiles({POSTGRES, DATAJPA})
+abstract public class AbstractControllerTest {
+
+ private static final CharacterEncodingFilter CHARACTER_ENCODING_FILTER = new CharacterEncodingFilter();
+
+ static {
+ CHARACTER_ENCODING_FILTER.setEncoding("UTF-8");
+ CHARACTER_ENCODING_FILTER.setForceEncoding(true);
+ }
+
+ protected MockMvc mockMvc;
+
+ @Autowired
+ protected UserService userService;
+
+ @Autowired
+ private WebApplicationContext webApplicationContext;
+
+ @PostConstruct
+ void postConstruct() {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(webApplicationContext)
+ .addFilter(CHARACTER_ENCODING_FILTER)
+ .build();
+ }
+
+ @Before
+ public void setUp() {
+ userService.evictCache();
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java
new file mode 100644
index 000000000000..ca416455051c
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerSpringTest.java
@@ -0,0 +1,54 @@
+package ru.javawebinar.topjava.web;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import ru.javawebinar.topjava.UserTestData;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+import ru.javawebinar.topjava.web.user.AdminRestController;
+
+import java.util.Collection;
+
+import static ru.javawebinar.topjava.UserTestData.ADMIN;
+import static ru.javawebinar.topjava.UserTestData.USER;
+
+/**
+ * GKislin
+ * 13.03.2015.
+ */
+@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/spring-mvc.xml", "classpath:spring/mock.xml"})
+@RunWith(SpringJUnit4ClassRunner.class)
+public class InMemoryAdminRestControllerSpringTest {
+
+ @Autowired
+ private AdminRestController controller;
+
+ @Autowired
+ private UserRepository repository;
+
+ @Before
+ public void setUp() throws Exception {
+ repository.getAll().forEach(u -> repository.delete(u.getId()));
+ repository.save(USER);
+ repository.save(ADMIN);
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ controller.delete(UserTestData.USER_ID);
+ Collection users = controller.getAll();
+ Assert.assertEquals(users.size(), 1);
+ Assert.assertEquals(users.iterator().next(), ADMIN);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testDeleteNotFound() throws Exception {
+ controller.delete(10);
+ }
+}
diff --git a/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java
new file mode 100644
index 000000000000..e8cc707271cb
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/web/InMemoryAdminRestControllerTest.java
@@ -0,0 +1,57 @@
+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.UserRepository;
+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;
+import static ru.javawebinar.topjava.UserTestData.USER;
+
+public class InMemoryAdminRestControllerTest {
+ private static ConfigurableApplicationContext appCtx;
+ private static AdminRestController controller;
+
+ @BeforeClass
+ public static void beforeClass() {
+ appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-mvc.xml", "spring/mock.xml");
+ System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n");
+ controller = appCtx.getBean(AdminRestController.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // Re-initialize
+ UserRepository repository = appCtx.getBean(UserRepository.class);
+ repository.getAll().forEach(u -> repository.delete(u.getId()));
+ repository.save(USER);
+ repository.save(ADMIN);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+// May cause during JUnit "Cache is not alive (STATUS_SHUTDOWN)" as JUnit share Spring context for speed
+// http://stackoverflow.com/questions/16281802/ehcache-shutdown-causing-an-exception-while-running-test-suite
+// appCtx.close();
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ controller.delete(UserTestData.USER_ID);
+ Collection users = controller.getAll();
+ Assert.assertEquals(users.size(), 1);
+ Assert.assertEquals(users.iterator().next(), ADMIN);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testDeleteNotFound() throws Exception {
+ controller.delete(10);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java
new file mode 100644
index 000000000000..f4fb6840231a
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/web/user/UserControllerTest.java
@@ -0,0 +1,34 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.junit.Test;
+import ru.javawebinar.topjava.web.AbstractControllerTest;
+
+import static org.hamcrest.Matchers.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+import static ru.javawebinar.topjava.UserTestData.USER;
+import static ru.javawebinar.topjava.model.BaseEntity.START_SEQ;
+
+/**
+ * GKislin
+ * 10.04.2015.
+ */
+public class UserControllerTest extends AbstractControllerTest {
+
+ @Test
+ public void testUserList() throws Exception {
+ mockMvc.perform(get("/users"))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(view().name("userList"))
+ .andExpect(forwardedUrl("/WEB-INF/jsp/userList.jsp"))
+ .andExpect(model().attribute("userList", hasSize(2)))
+ .andExpect(model().attribute("userList", hasItem(
+ allOf(
+ hasProperty("id", is(START_SEQ)),
+ hasProperty("name", is(USER.getName()))
+ )
+ )));
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
new file mode 100644
index 000000000000..661579943554
--- /dev/null
+++ b/src/test/resources/logback-test.xml
@@ -0,0 +1,21 @@
+
+
+
+ true
+
+
+
+
+ UTF-8
+ %-5level %logger{0} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/spring/mock.xml b/src/test/resources/spring/mock.xml
new file mode 100644
index 000000000000..c7c9f62cc5d8
--- /dev/null
+++ b/src/test/resources/spring/mock.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file