+- Открыть и настроить проект в IDEA
+- По ходу видео сделать Apply Patch... скаченного патча Prepare_ to_ HW0.patch
+- Закоммитить и запушить изменения (commit + push)
+- Сделать ветку домашнего задания
+- Выполнить задание и залить на GitHub (commit + push)
+- Отослать в skype мне ссылку вашей ветки GitHub (при оплате расширенного участия)
+- Переключиться в основную ветку проекта master.
+
+### Вы готовы к участию в проекте!
+
+- Ресурсы
+ - Java 8: Lambda выражения
+ - Java 8 Date and Time API
+ - Java 8: Потоки
+ - Pуководство по Java 8 Stream
+ - Лямбда-выражения в Java 8
+ - Time complexity
+
+## Домашнее задание HW0
+ Реализовать UserMealsUtil.getFilteredMealsWithExceeded:
+ - должны возвращаться только записи между startTime и endTime
+ - поле UserMealWithExceed.exceed должно показывать,
+ превышает ли сумма калорий за весь день параметра метода caloriesPerDay
+
+ Т.е UserMealWithExceed - это запись одной еды, но поле exceeded
+ будет одинаково для всех записей за этот день.
+
+ - Проверте результат выполнения ДЗ!
+ - Оцените Time complexity вашего алгоритма.
+
+Optional
+
+ Сделать реализация через Java 8 Stream API
diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml
new file mode 100644
index 000000000000..66d38a3a472c
--- /dev/null
+++ b/config/Topjava-soapui-project.xml
@@ -0,0 +1,29 @@
+
+http://localhost:8080text/html;charset=utf-8500 401 403htmlapplication/json200ns:Response0dataapplication/json;charset=UTF-8200Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/usersadmin@gmail.comadminBasicBasicGlobal HTTP Settingsapplication/jsontext/html;charset=utf-8500htmlapplication/json201user:Responseapplication/json;charset=UTF-8201user:Response<xml-fragment/>http://localhost:8080{
+ "name": "New2",
+ "email": "new2@yandex.ru",
+ "password": "passwordNew",
+ "roles": ["ROLE_USER"]
+}http://localhost/topjava/rest/admin/usersadmin@gmail.comadminBasicBasicGlobal HTTP Settingstext/html;charset=utf-8401html<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/usersNo Authorizationtext/html;charset=utf-8500htmlapplication/json200ns:Responseapplication/json;charset=UTF-8200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/admin/users/100000No AuthorizationBasicNo Authorizationtext/html;charset=utf-8405 500htmlapplication/json200data200data200data200data<xml-fragment/>http://localhost:8080{
+ "name": "UserUpdated",
+ "email": "user@yandex.ru",
+ "password": "passwordNew",
+ "roles": ["ROLE_USER"]
+}http://localhost/topjava/rest/admin/users/100000BasicBasicGlobal HTTP Settingstext/html;charset=utf-8500htmlapplication/json200ns:Responseapplication/json;charset=UTF-8200prof:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profileBasicBasicGlobal HTTP Settingsapplication/jsontext/html;charset=utf-8500 405htmlapplication/json201user:Response200data200data200data200data200data200data<xml-fragment/>http://localhost:8080{
+ "name": "New777",
+ "email": "new777@yandex.ru",
+ "password": "passwordNew",
+ "roles": ["ROLE_USER"]
+}http://localhost/topjava/rest/profileNo AuthorizationBasicNo Authorization200datatext/html;charset=utf-8500htmlapplication/json200data<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profileNo AuthorizationBasicNo Authorizationapplication/json;charset=UTF-8200Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profile/mealsNo Authorizationtext/html;charset=utf-8400htmlapplication/json0dataapplication/json;charset=UTF-8201meal:Response<xml-fragment/>http://localhost:8080 {
+ "dateTime": "2015-06-01 10:00",
+ "description": "Новый завтрак",
+ "calories": 777
+ }http://localhost/topjava/rest/profile/mealsNo Authorizationapplication/json;charset=UTF-8200ns:Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profile/meals/100002No Authorizationtext/html;charset=utf-8400htmlapplication/json200data200data200data<xml-fragment/>http://localhost:8080{
+ "id": 100002,
+ "dateTime": "2015-05-30 10:00",
+ "description": "Обновленный завтрак",
+ "calories": 500
+}http://localhost/topjava/rest/profile/meals/100002No AuthorizationstartDateTimeQUERYendDateTimeQUERYapplication/json;charset=UTF-8200Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profile/meals/betweenNo Authorization
+
+
+startDateTimeendDateTimestartDateQUERYapplication/json;charset=UTF-8200Response<xml-fragment/>http://localhost:8080http://localhost/topjava/rest/profile/meals/filterNo AuthorizationstartDate
\ No newline at end of file
diff --git a/config/messages/app.properties b/config/messages/app.properties
new file mode 100644
index 000000000000..6ca82d2d1e1e
--- /dev/null
+++ b/config/messages/app.properties
@@ -0,0 +1,15 @@
+app.title=Calories management
+app.footer=Project Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery application
+app.login=Login
+app.logout=Logout
+app.registered=You are registered. Please Sign in.
+
+users.title=User list
+users.edit=Edit user
+users.add=Add user
+
+meals.title=Meals list
+meals.edit=Edit meal
+meals.add=Add 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..51b10c2e9fe8
--- /dev/null
+++ b/config/messages/app_ru.properties
@@ -0,0 +1,15 @@
+app.title=Подсчет калорий
+app.footer=Приложение по вебинару Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery
+app.login=Войти
+app.logout=Выйти
+app.registered=Вы загеристрированы. Введите ваш логин/пароль.
+
+users.title=Список пользователей
+users.edit=Редактировать пользователя
+users.add=Добавить пользователя
+
+meals.title=Список еды
+meals.edit=Редактировать еду
+meals.add=Добавить еду
+
+common.select=Выбрать
diff --git a/config/tomcat/setenv.bat b/config/tomcat/setenv.bat
new file mode 100644
index 000000000000..ebbd659386f6
--- /dev/null
+++ b/config/tomcat/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..76e60ced941e
--- /dev/null
+++ b/cv.md
@@ -0,0 +1,34 @@
+## Составление резюме
+
+
+
+### Общие вещи:
+- Как правильно составить резюме
+- Резюме программистов. Часть 1 (плохие)
+- Резюме программистов. Часть 2 (хорошие)
+- Покритикуйте пожалуйста резюме (Junior java)
+- Как составить резюме программисту?
+- Мое (только как пример), linkedIn
+
+### От себя:
+- email, skype - очень желательно, чтобы по ним вы были узнаваемы. Заведите рабочие, если не так.
+- написать ВЕСЬ IT опыт (исключая опыт пользователя: Windows, MS Word, Photophop, Yandex disk, Google docs, ..): технологии, какие задачи решали (конкретные), какие инструменты использовали, VCS, DB, инструменты сборки, ... включая опыт в ВУЗе.
+- на English иметь желательно. Если вакансия опублинована на Englsih - шлите на нем. Часто могут на нем попросить, если работодатель иностранный.
+- формат: pdf или doc на мой взгляд не так важно. doc нарпимер в USA любят, я - нет:) На мой взгляд удобно иметь резюме где то в инете (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.
+ - отлично будет, если вы его еще как то сами доработаете
+
+ Ссылка на github и Heroku с задеплоенным приложением.
+
+- По поводу обучения на курсах- можно упомянуть в разделе образования. Но на собеседовании меньше смотрят на то, что вы заканчивали, больше - на ваш опыт и то что вы знаете.
+
+- В течении проекта заполните форму соискателя "Разработчик Java"
diff --git a/description.md b/description.md
new file mode 100644
index 000000000000..fcf8c9067227
--- /dev/null
+++ b/description.md
@@ -0,0 +1,67 @@
+#### Старт проекта 26.11.2015
+Присылайте заявки в skype:grigory.kislin
+
+#### Разработка полнофункционального 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 кодированию и архитектуре приложения.
+- Каждая итерация проекта в закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий.
+- Большое внимание уделяется тестированию кода: в проекте ~ 85 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
+- Spring кэш
+- Кэш Hibernate
+
+## Разработка WEB
+- Spring Web
+- JPS, 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/hr.bat b/hr.bat
new file mode 100644
index 000000000000..1742ed99fa81
--- /dev/null
+++ b/hr.bat
@@ -0,0 +1,2 @@
+call mvn -B -s settings.xml -DskipTests=true clean install
+call java -Dspring.profiles.active="datajpa,heroku" -DDATABASE_URL="postgres://postgres:passwd@localhost:5432/topjava" -jar target/dependency/webapp-runner.jar target/*.war
diff --git a/pom.xml b/pom.xml
index c8a1c78f3b29..45ffcd2860ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
ru.javawebinartopjava
- jar
+ war1.0-SNAPSHOT
@@ -13,13 +13,43 @@
1.8
+ 7.0.57
+
UTF-8UTF-8
+
+ 1.9.0.RELEASE
+ 4.2.3.RELEASE
+
+ 9.4-1206-jdbc42
+ 2.3.2
+
+
+ 1.1.3
+ 1.7.13
+
+ 4.12
+
+
+ 5.0.6.Final
+ 5.1.3.Final
+
+ 1.3
+ 2.6.4
+ 2.10.1
+
+
+ 3.3.5
+ 2.1.4
+ 2.3.4
+ 2.3.7
+ 4.0.3.RELEASE
+ 1.10.9topjava
- install
+ packageorg.apache.maven.plugins
@@ -30,15 +60,311 @@
${java.version}
+
+
+ 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
+ ${postgres.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.17
+
+ -Dfile.encoding=UTF-8
+
+
+
+
+ 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.apache.tomcat
+ tomcat-servlet-api
+ ${tomcat.version}
+ provided
+
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ javax.servlet
+ jstl
+ 1.2
+
+
+
+
+ org.springframework
+ spring-context-support
+ ${spring.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+ org.springframework.security
+ spring-security-web
+ ${spring-security.version}
+
+
+ org.springframework.security
+ spring-security-config
+ ${spring-security.version}
+
+
+
+
+ org.webjars
+ bootstrap
+ ${webjars-bootstrap.version}
+
+
+ org.webjars
+ jquery
+ ${webjars-jquery.version}
+
+
+ org.webjars
+ datetimepicker
+ ${webjars-datetimepicker.version}
+
+
+ org.webjars.bower
+ noty
+ ${webjars-noty.version}
+
+
+ org.webjars
+ datatables
+ ${webjars-datatables.version}
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson-json.version}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-hibernate4
+ ${jackson-json.version}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ ${jackson-json.version}
+
+
+
+ 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}
+
+
+ org.springframework.security
+ spring-security-taglibs
+ ${spring-security.version}
+
+
+
+
+ net.sf.ehcache
+ ehcache
+ ${ehcache.version}
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ hamcrest-core
+ org.hamcrest
+
+
+
+
+
+ org.hamcrest
+ hamcrest-all
+ ${hamcrest.version}
+ test
+
+
+
+ org.springframework
+ spring-test
+ ${spring.version}
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ ${spring-security.version}
+
+
+ hsqldb
+
+
+ org.hsqldb
+ hsqldb
+ ${hsqldb.version}
+
+
+
+
+ postgres
+
+
+ org.postgresql
+ postgresql
+ ${postgres.version}
+
+
+ org.apache.tomcat
+ tomcat-jdbc
+ ${tomcat.version}
+
+
+
+ true
+
+
+
+ heroku
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.10
+
+
+ package
+
+ copy
+
+
+
+
+ com.github.jsimone
+ webapp-runner
+ 7.0.57.2
+ webapp-runner.jar
+
+
+
+
+
+
+
+
+
+
+
+ org.springframework
+ spring-framework-bom
+ ${spring.version}
+ pom
+ import
+
+
diff --git a/settings.xml b/settings.xml
new file mode 100644
index 000000000000..cb1c8aa69b1b
--- /dev/null
+++ b/settings.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+ postgres
+ heroku
+
+
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..3dd8c3aa7384
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/LoggedUser.java
@@ -0,0 +1,62 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.to.UserTo;
+import ru.javawebinar.topjava.util.UserUtil;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ *
+ * Mock implementation
+ */
+public class LoggedUser extends org.springframework.security.core.userdetails.User {
+ static final long serialVersionUID = 1L;
+
+ private UserTo userTo;
+
+ public LoggedUser(User user) {
+ super(user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, user.getRoles());
+ this.userTo = UserUtil.asTo(user);
+ }
+
+ public static LoggedUser safeGet() {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth == null) {
+ return null;
+ }
+ Object user = auth.getPrincipal();
+ return (user instanceof LoggedUser) ? (LoggedUser) user : null;
+ }
+
+ public static LoggedUser get() {
+ LoggedUser user = safeGet();
+ requireNonNull(user, "No authorized user found");
+ return user;
+ }
+
+ public UserTo getUserTo() {
+ return userTo;
+ }
+
+ public static int id() {
+ return get().userTo.getId();
+ }
+
+ @Override
+ public String toString() {
+ return userTo.toString();
+ }
+
+ public static int getCaloriesPerDay() {
+ return get().userTo.getCaloriesPerDay();
+ }
+
+ public void update(UserTo newTo) {
+ userTo = newTo;
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/LoggerWrapper.java b/src/main/java/ru/javawebinar/topjava/LoggerWrapper.java
new file mode 100644
index 000000000000..54c2c23865e2
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/LoggerWrapper.java
@@ -0,0 +1,84 @@
+package ru.javawebinar.topjava;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.javawebinar.topjava.util.exception.ErrorInfo;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+/**
+ * User: gkislin
+ * Date: 22.01.14
+ */
+public class LoggerWrapper {
+
+ private Logger logger;
+
+ public LoggerWrapper(Logger logger) {
+ this.logger = logger;
+ }
+
+ public static LoggerWrapper get(Class aClass) {
+ return new LoggerWrapper(LoggerFactory.getLogger(aClass));
+ }
+
+ public void debug(String msg) {
+ logger.debug(msg);
+ }
+
+ public void info(String msg, Object... arguments) {
+ logger.info(msg, arguments);
+ }
+
+ public void warn(String msg) {
+ logger.warn(msg);
+ }
+
+ public void warn(String msg, Throwable t) {
+ logger.warn(msg, t);
+ }
+
+ public void error(String msg) {
+ logger.error(msg);
+ }
+
+ public void error(String msg, Throwable t) {
+ logger.error(msg, t);
+ }
+
+ public boolean isDebug() {
+ return logger.isDebugEnabled();
+ }
+
+ public IllegalStateException getIllegalStateException(String msg) {
+ return getIllegalStateException(msg, null);
+ }
+
+ public IllegalStateException getIllegalStateException(String msg, Throwable e) {
+ logger.error(msg, e);
+ return new IllegalStateException(msg, e);
+ }
+
+ public IllegalArgumentException getIllegalArgumentException(String msg) {
+ return getIllegalArgumentException(msg, null);
+ }
+
+ public IllegalArgumentException getIllegalArgumentException(String msg, Throwable e) {
+ logger.error(msg, e);
+ return new IllegalArgumentException(msg, e);
+ }
+
+ public UnsupportedOperationException getUnsupportedOperationException(String msg) {
+ logger.error(msg);
+ return new UnsupportedOperationException(msg);
+ }
+
+ public NotFoundException getNotFoundException(String reason) {
+ logger.error(reason);
+ return new NotFoundException(reason);
+ }
+
+ public ErrorInfo getErrorInfo(CharSequence requestUrl, Exception e) {
+ logger.error("Exception at request " + requestUrl);
+ return new ErrorInfo(requestUrl, e);
+ }
+}
\ No newline at end of file
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..a3f5266a97ec
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/Profiles.java
@@ -0,0 +1,14 @@
+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";
+}
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..c7d1bb2b0dae
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/BaseEntity.java
@@ -0,0 +1,63 @@
+package ru.javawebinar.topjava.model;
+
+import ru.javawebinar.topjava.LoggerWrapper;
+
+import javax.persistence.*;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+@MappedSuperclass
+@Access(AccessType.FIELD)
+//@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE)
+public class BaseEntity {
+ protected static final LoggerWrapper LOG = LoggerWrapper.get(BaseEntity.class);
+
+ 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) {
+ throw LOG.getIllegalStateException("Equals '" + this + "' and '" + that + "' with null id");
+ }
+ 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..9c5210417f0b
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/Role.java
@@ -0,0 +1,17 @@
+package ru.javawebinar.topjava.model;
+
+import org.springframework.security.core.GrantedAuthority;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+public enum Role implements GrantedAuthority {
+ ROLE_USER,
+ ROLE_ADMIN;
+
+ @Override
+ public String getAuthority() {
+ return name();
+ }
+}
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..deb1c33b1b68
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/User.java
@@ -0,0 +1,150 @@
+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.UserUtil;
+
+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 = UserUtil.DEFAULT_CALORIES_PER_DAY;
+
+ @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "user")
+ @OrderBy("dateTime DESC")
+// @JsonIgnore
+ protected List meals;
+
+ 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, int caloriesPerDay, Role role, Role... roles) {
+ this(id, name, email, password, caloriesPerDay, 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 List getMeals() {
+ return meals;
+ }
+
+ 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..6bb5472a97f1
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java
@@ -0,0 +1,107 @@
+package ru.javawebinar.topjava.model;
+
+import org.hibernate.validator.constraints.NotEmpty;
+import org.hibernate.validator.constraints.Range;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+/**
+ * GKislin
+ * 11.01.2015.
+ */
+@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
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+ protected LocalDateTime dateTime;
+
+ @Column(name = "description", nullable = false)
+ @NotEmpty
+ protected String description;
+
+ @Column(name = "calories", nullable = false)
+ @Range(min = 10, max = 5000)
+ @NotNull
+ protected Integer 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(Integer 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..afe485740dbe
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/UserMealRepository.java
@@ -0,0 +1,31 @@
+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 DATE, TIME
+ Collection getAll(int userId);
+
+ // ORDERED DATE, TIME
+ Collection getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId);
+
+ default UserMeal getWithUser(Integer id, Integer userId) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java
new file mode 100644
index 000000000000..47ded4cec6b8
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java
@@ -0,0 +1,28 @@
+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();
+
+ default User getWithMeals(int id){
+ throw new UnsupportedOperationException();
+ }
+}
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..5a633070298d
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserMealRepositoryImpl.java
@@ -0,0 +1,58 @@
+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);
+ }
+
+ @Override
+ public UserMeal getWithUser(Integer id, Integer userId) {
+ return proxy.getWithUser(id, 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..422478684ec4
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java
@@ -0,0 +1,52 @@
+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);
+ }
+
+ @Override
+ public User getWithMeals(int id) {
+ return proxy.getWithMeals(id);
+ }
+}
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..5139af76a0f5
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserMealRepository.java
@@ -0,0 +1,35 @@
+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);
+
+ @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);
+
+ @Query("SELECT um FROM UserMeal um JOIN FETCH um.user WHERE um.id = ?1 and um.user.id = ?2")
+ UserMeal getWithUser(Integer id, Integer 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..b3c497b46c31
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/ProxyUserRepository.java
@@ -0,0 +1,40 @@
+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);
+
+ @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1")
+ User getWithMeals(Integer id);
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java
new file mode 100644
index 000000000000..31e802a837a3
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserMealRepositoryImpl.java
@@ -0,0 +1,96 @@
+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.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.model.UserMeal;
+import ru.javawebinar.topjava.repository.UserMealRepository;
+
+import javax.sql.DataSource;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * User: gkislin
+ * Date: 26.08.2014
+ */
+
+@Repository
+@Transactional(readOnly = true)
+public class JdbcUserMealRepositoryImpl implements UserMealRepository {
+
+ private static final RowMapper ROW_MAPPER =
+ (rs, rowNum) ->
+ new UserMeal(rs.getInt("id"), rs.getTimestamp("date_time").toLocalDateTime(),
+ rs.getString("description"), rs.getInt("calories"));
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ private SimpleJdbcInsert insertUserMeal;
+
+ @Autowired
+ public JdbcUserMealRepositoryImpl(DataSource dataSource) {
+ this.insertUserMeal = new SimpleJdbcInsert(dataSource)
+ .withTableName("meals")
+ .usingGeneratedKeyColumns("id");
+ }
+
+ @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", Timestamp.valueOf(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 = ?", ROW_MAPPER, 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", ROW_MAPPER, userId);
+ }
+
+ @Override
+ public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) {
+ return jdbcTemplate.query(
+ "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC",
+ ROW_MAPPER, userId, Timestamp.valueOf(startDate), Timestamp.valueOf(endDate));
+ }
+}
\ 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..e566c833f167
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java
@@ -0,0 +1,156 @@
+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.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);
+
+ @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 {
+ public 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> userRoles = 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));
+
+ users.forEach(u -> u.setRoles(userRoles.get(u.getId()).stream().map(UserRole::getRole).collect(Collectors.toList())));
+ 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=?",
+ (rs, rowNum) -> Role.valueOf(rs.getString("role")), u.getId());
+ u.setRoles(roles);
+ }
+ return u;
+ }
+}
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..16b304c57eff
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserMealRepositoryImpl.java
@@ -0,0 +1,73 @@
+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) {
+ User ref = em.getReference(User.class, userId);
+ userMeal.setUser(ref);
+
+ if (userMeal.isNew()) {
+ em.persist(userMeal);
+ return userMeal;
+ } else {
+ return get(userMeal.getId(), userId) == null ? null : 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..9cfd2f128aeb
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserMealService.java
@@ -0,0 +1,33 @@
+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);
+
+ UserMeal getWithUser(Integer id, Integer 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..746b0dd14543
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserMealServiceImpl.java
@@ -0,0 +1,63 @@
+package ru.javawebinar.topjava.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+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);
+ }
+
+// @Autowired
+// private UserRepository userRepository;
+
+// @Transactional
+ public UserMeal getWithUser(Integer id, Integer userId) {
+// UserMeal meal = get(id, userId);
+// meal.setUser(userRepository.get(userId));
+// return meal;
+ return repository.getWithUser(id, 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..ac1b88529775
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java
@@ -0,0 +1,35 @@
+package ru.javawebinar.topjava.service;
+
+
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.to.UserTo;
+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;
+
+ void update(UserTo user);
+
+ List getAll();
+
+ void update(User user);
+
+ void evictCache();
+
+ void enable(int id, boolean enable);
+
+ User getWithMeals(int id);
+}
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..4fc2d4e1da80
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java
@@ -0,0 +1,101 @@
+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.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javawebinar.topjava.LoggedUser;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.repository.UserRepository;
+import ru.javawebinar.topjava.to.UserTo;
+import ru.javawebinar.topjava.util.UserUtil;
+import ru.javawebinar.topjava.util.exception.ExceptionUtil;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * GKislin
+ * 06.03.2015.
+ */
+@Service("userService")
+public class UserServiceImpl implements UserService, UserDetailsService {
+
+ @Autowired
+ private UserRepository repository;
+
+ @CacheEvict(value = "users", allEntries = true)
+ @Override
+ public User save(User user) {
+ return repository.save(user);
+ }
+
+ @CacheEvict(value = "users", allEntries = true)
+ @Override
+ public void delete(int id) {
+ ExceptionUtil.check(repository.delete(id), id);
+ }
+
+ @Override
+ public User get(int id) throws NotFoundException {
+ return ExceptionUtil.check(repository.get(id), id);
+ }
+
+ @Override
+ 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")
+ @Override
+ public List getAll() {
+ return repository.getAll();
+ }
+
+ @CacheEvict(value = "users", allEntries = true)
+ public void update(User user) {
+ repository.save(user);
+ }
+
+ @CacheEvict(value = "users", allEntries = true)
+ @Transactional
+ @Override
+ public void update(UserTo userTo) {
+ User user = get(userTo.getId());
+ repository.save(UserUtil.updateFromTo(user, userTo));
+ }
+
+
+ @CacheEvict(value = "users", allEntries = true)
+ @Override
+ public void evictCache() {
+ }
+
+ @CacheEvict(value = "users", allEntries = true)
+ @Override
+ @Transactional
+ public void enable(int id, boolean enabled) {
+ User user = get(id);
+ user.setEnabled(enabled);
+ repository.save(user);
+ }
+
+ @Override
+ public LoggedUser loadUserByUsername(String email) throws UsernameNotFoundException {
+ User u = repository.getByEmail(email.toLowerCase());
+ if (u == null) {
+ throw new UsernameNotFoundException("User " + email + " is not found");
+ }
+ return new LoggedUser(u);
+ }
+
+ @Override
+ public User getWithMeals(int id) {
+ return repository.getWithMeals(id);
+ }
+}
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..de4a87407ce6
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/to/UserMealWithExceed.java
@@ -0,0 +1,64 @@
+package ru.javawebinar.topjava.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.LocalDateTime;
+
+/**
+ * GKislin
+ * 11.01.2015.
+ */
+public class UserMealWithExceed {
+ protected final Integer id;
+
+ protected final LocalDateTime dateTime;
+
+ protected final String description;
+
+ protected final int calories;
+
+ protected final boolean exceed;
+
+ public UserMealWithExceed(@JsonProperty("id") Integer id,
+ @JsonProperty("dateTime") LocalDateTime dateTime,
+ @JsonProperty("description") String description,
+ @JsonProperty("calories") int calories,
+ @JsonProperty("exceed") 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/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java
new file mode 100644
index 000000000000..83323d937167
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java
@@ -0,0 +1,95 @@
+package ru.javawebinar.topjava.to;
+
+import org.hibernate.validator.constraints.Email;
+import org.hibernate.validator.constraints.NotEmpty;
+import org.hibernate.validator.constraints.Range;
+import ru.javawebinar.topjava.util.UserUtil;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+
+public class UserTo implements Serializable {
+ static final long serialVersionUID = 1L;
+
+ protected int id;
+
+ @NotEmpty
+ protected String name;
+
+ @Email
+ @NotEmpty
+ protected String email;
+
+ @Size(min = 5, max = 64, message = " must between 5 and 64 characters")
+ protected String password;
+
+ @Range(min = 500, max = 5000)
+ @NotNull(message = " must not be empty")
+ protected Integer caloriesPerDay = UserUtil.DEFAULT_CALORIES_PER_DAY;
+
+ public UserTo() {
+ }
+
+ public UserTo(int id, String name, String email, String password, int caloriesPerDay) {
+ this.id = id;
+ this.name = name;
+ this.email = email;
+ this.password = password;
+ this.caloriesPerDay = caloriesPerDay;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = (id == null ? 0 : id);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public Integer getCaloriesPerDay() {
+ return caloriesPerDay;
+ }
+
+ public void setCaloriesPerDay(Integer caloriesPerDay) {
+ this.caloriesPerDay = caloriesPerDay;
+ }
+
+ @Override
+ public String toString() {
+ return "UserTo{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ ", email='" + email + '\'' +
+ ", caloriesPerDay='" + caloriesPerDay + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/PasswordUtil.java b/src/main/java/ru/javawebinar/topjava/util/PasswordUtil.java
new file mode 100644
index 000000000000..07faed1e87d7
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/PasswordUtil.java
@@ -0,0 +1,39 @@
+package ru.javawebinar.topjava.util;
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.util.StringUtils;
+
+import java.util.regex.Pattern;
+
+/**
+ * GKislin
+ * 05.01.2015.
+ */
+
+public class PasswordUtil {
+ private static final Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
+ public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
+
+ public static PasswordEncoder getPasswordEncoder() {
+ return PASSWORD_ENCODER;
+ }
+
+ public static String encode(String newPassword) {
+ if (StringUtils.isEmpty(newPassword)) {
+ return null;
+ }
+ if (isEncoded(newPassword)) {
+ return newPassword;
+ }
+ return PASSWORD_ENCODER.encode(newPassword);
+ }
+
+ public static boolean isMatch(String rawPassword, String password) {
+ return PASSWORD_ENCODER.matches(rawPassword, password);
+ }
+
+ public static boolean isEncoded(String newPassword) {
+ return BCRYPT_PATTERN.matcher(newPassword).matches();
+ }
+}
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..abc977567e19
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java
@@ -0,0 +1,46 @@
+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_TME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+ public static final LocalDate MIN_DATE = LocalDate.of(0, 1, 1);
+ public static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1);
+
+ public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) {
+ return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0;
+ }
+
+ public static boolean isBetween(LocalDateTime ldt, LocalDateTime startDateTime, LocalDateTime endDateTime) {
+ return ldt.compareTo(startDateTime) >= 0 && ldt.compareTo(endDateTime) <= 0;
+ }
+
+ public static String toString(LocalDateTime ldt) {
+ return ldt == null ? "" : ldt.format(DATE_TME_FORMATTER);
+ }
+
+ public static LocalDate parseLocalDate(String str, LocalDate def) {
+ return StringUtils.isEmpty(str) ? def : LocalDate.parse(str);
+ }
+
+ public static LocalTime parseLocalTime(String str, LocalTime def) {
+ return StringUtils.isEmpty(str) ? def : LocalTime.parse(str);
+ }
+
+ public static LocalDateTime parseLocalDateTime(String str) {
+ return parseLocalDateTime(str, DATE_TME_FORMATTER);
+ }
+
+ public static LocalDateTime parseLocalDateTime(String str, DateTimeFormatter formatter) {
+ return StringUtils.isEmpty(str) ? LocalDateTime.now() : LocalDateTime.parse(str, formatter);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java
new file mode 100644
index 000000000000..77b238c70064
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java
@@ -0,0 +1,69 @@
+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 void main(String[] args) {
+ List filteredMealsWithExceeded = getFilteredWithExceeded(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), UserUtil.DEFAULT_CALORIES_PER_DAY);
+ filteredMealsWithExceeded.forEach(System.out::println);
+
+ System.out.println(getFilteredWithExceededByCycle(MEAL_LIST, LocalTime.of(7, 0), LocalTime.of(12, 0), UserUtil.DEFAULT_CALORIES_PER_DAY));
+ }
+
+ public static List getWithExceeded(Collection mealList, int caloriesPerDay) {
+ return getFilteredWithExceeded(mealList, LocalTime.MIN, LocalTime.MAX, caloriesPerDay);
+ }
+
+ public static UserMealWithExceed createWithExceed(UserMeal um, boolean exceeded) {
+ return new UserMealWithExceed(um.getId(), um.getDateTime(), um.getDescription(), um.getCalories(), exceeded);
+ }
+
+ 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) {
+
+ Map caloriesSumPerDate = new HashMap<>();
+ for (UserMeal meal : mealList) {
+ caloriesSumPerDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum);
+ }
+
+ List mealExceeded = new ArrayList<>();
+ for (UserMeal meal : mealList) {
+ LocalDateTime dateTime = meal.getDateTime();
+ if (TimeUtil.isBetween(dateTime.toLocalTime(), startTime, endTime)) {
+ mealExceeded.add(createWithExceed(meal, caloriesSumPerDate.get(dateTime.toLocalDate()) > caloriesPerDay));
+ }
+ }
+ return mealExceeded;
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java
new file mode 100644
index 000000000000..da23e4557822
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java
@@ -0,0 +1,34 @@
+package ru.javawebinar.topjava.util;
+
+import ru.javawebinar.topjava.model.Role;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.to.UserTo;
+
+/**
+ * GKislin
+ */
+public class UserUtil {
+ public static final int DEFAULT_CALORIES_PER_DAY = 2000;
+
+ public static User createFromTo(UserTo newUser) {
+ return new User(null, newUser.getName(), newUser.getEmail().toLowerCase(), newUser.getPassword(), newUser.getCaloriesPerDay(), Role.ROLE_USER);
+ }
+
+ public static UserTo asTo(User user) {
+ return new UserTo(user.getId(), user.getName(), user.getEmail(), user.getPassword(), user.getCaloriesPerDay());
+ }
+
+ public static User updateFromTo(User user, UserTo userTo) {
+ user.setName(userTo.getName());
+ user.setEmail(userTo.getEmail());
+ user.setPassword(userTo.getPassword());
+ user.setCaloriesPerDay(userTo.getCaloriesPerDay());
+ return prepareToSave(user);
+ }
+
+ public static User prepareToSave(User user) {
+ user.setPassword(PasswordUtil.encode(user.getPassword()));
+ user.setEmail(user.getEmail().toLowerCase());
+ return user;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/util/converter/LocalDateFormatter.java b/src/main/java/ru/javawebinar/topjava/util/converter/LocalDateFormatter.java
new file mode 100644
index 000000000000..193b32de9406
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/converter/LocalDateFormatter.java
@@ -0,0 +1,26 @@
+package ru.javawebinar.topjava.util.converter;
+
+import org.springframework.format.Formatter;
+import ru.javawebinar.topjava.util.TimeUtil;
+
+import java.text.ParseException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * GKislin
+ * 15.04.2015.
+ */
+public class LocalDateFormatter implements Formatter {
+
+ @Override
+ public LocalDate parse(String text, Locale locale) throws ParseException {
+ return TimeUtil.parseLocalDate(text, null);
+ }
+
+ @Override
+ public String print(LocalDate lt, Locale locale) {
+ return lt.format(DateTimeFormatter.ISO_LOCAL_DATE);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/converter/LocalTimeFormatter.java b/src/main/java/ru/javawebinar/topjava/util/converter/LocalTimeFormatter.java
new file mode 100644
index 000000000000..1a29f9c5accd
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/converter/LocalTimeFormatter.java
@@ -0,0 +1,26 @@
+package ru.javawebinar.topjava.util.converter;
+
+import org.springframework.format.Formatter;
+import ru.javawebinar.topjava.util.TimeUtil;
+
+import java.text.ParseException;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * GKislin
+ * 15.04.2015.
+ */
+public class LocalTimeFormatter implements Formatter {
+
+ @Override
+ public LocalTime parse(String text, Locale locale) throws ParseException {
+ return TimeUtil.parseLocalTime(text, null);
+ }
+
+ @Override
+ public String print(LocalTime lt, Locale locale) {
+ return lt.format(DateTimeFormatter.ISO_LOCAL_TIME);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java
new file mode 100644
index 000000000000..c422c409e630
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java
@@ -0,0 +1,17 @@
+package ru.javawebinar.topjava.util.exception;
+
+/**
+ * User: gkislin
+ * Date: 19.08.2014
+ */
+public class ErrorInfo {
+ public final String url;
+ public final String cause;
+ public final String detail;
+
+ public ErrorInfo(CharSequence url, Throwable ex) {
+ this.url = url.toString();
+ this.cause = ex.getClass().getSimpleName();
+ this.detail = ex.getLocalizedMessage();
+ }
+}
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..295aadab347f
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/exception/ExceptionUtil.java
@@ -0,0 +1,29 @@
+package ru.javawebinar.topjava.util.exception;
+
+
+import ru.javawebinar.topjava.LoggerWrapper;
+
+/**
+ * User: gkislin
+ * Date: 14.05.2014
+ */
+public class ExceptionUtil {
+ private static final LoggerWrapper LOG = LoggerWrapper.get(ExceptionUtil.class);
+
+ 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 LOG.getNotFoundException("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..1c85842a3497
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java
@@ -0,0 +1,15 @@
+package ru.javawebinar.topjava.util.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * User: gkislin
+ * Date: 19.08.2014
+ */
+@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No data found") // 404
+public class NotFoundException extends RuntimeException {
+ public NotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ValidationException.java b/src/main/java/ru/javawebinar/topjava/util/exception/ValidationException.java
new file mode 100644
index 000000000000..4a912734cbf8
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/exception/ValidationException.java
@@ -0,0 +1,20 @@
+package ru.javawebinar.topjava.util.exception;
+
+import org.springframework.validation.BindingResult;
+
+/**
+ * User: gkislin
+ * Date: 19.08.2014
+ */
+public class ValidationException extends RuntimeException {
+
+ public ValidationException(BindingResult result) {
+ super(getDescription(result));
+ }
+
+ private static String getDescription(BindingResult result) {
+ StringBuilder sb = new StringBuilder();
+ result.getFieldErrors().forEach(fe -> sb.append(fe.getField()).append(" ").append(fe.getDefaultMessage()).append(" "));
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java
new file mode 100644
index 000000000000..03d1f96edf84
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java
@@ -0,0 +1,54 @@
+package ru.javawebinar.topjava.web;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.javawebinar.topjava.LoggerWrapper;
+import ru.javawebinar.topjava.util.exception.ErrorInfo;
+import ru.javawebinar.topjava.util.exception.NotFoundException;
+import ru.javawebinar.topjava.util.exception.ValidationException;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * User: gkislin
+ * Date: 23.09.2014
+ */
+@ControllerAdvice(annotations = RestController.class)
+public class ExceptionInfoHandler {
+ private static final LoggerWrapper LOG = LoggerWrapper.get(ExceptionInfoHandler.class);
+
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ @ExceptionHandler(NotFoundException.class)
+ @ResponseBody
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) {
+ return LOG.getErrorInfo(req.getRequestURL(), e);
+ }
+
+ @ResponseStatus(value = HttpStatus.CONFLICT) // 409
+ @ExceptionHandler(DataIntegrityViolationException.class)
+ @ResponseBody
+ @Order(Ordered.HIGHEST_PRECEDENCE + 1)
+ public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) {
+ return LOG.getErrorInfo(req.getRequestURL(), e);
+ }
+
+ @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) // 422
+ @ExceptionHandler(ValidationException.class)
+ @ResponseBody
+ @Order(Ordered.HIGHEST_PRECEDENCE + 2)
+ ErrorInfo validationError(HttpServletRequest req, ValidationException e) {
+ return LOG.getErrorInfo(req.getRequestURL(), e);
+ }
+
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ @ExceptionHandler(Exception.class)
+ @ResponseBody
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ public ErrorInfo handleError(HttpServletRequest req, Exception e) {
+ return LOG.getErrorInfo(req.getRequestURL(), e);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java
new file mode 100644
index 000000000000..b633bda11fc2
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java
@@ -0,0 +1,36 @@
+package ru.javawebinar.topjava.web;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.ModelAndView;
+import ru.javawebinar.topjava.LoggedUser;
+import ru.javawebinar.topjava.LoggerWrapper;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * User: gkislin
+ * Date: 23.09.2014
+ */
+@ControllerAdvice
+public class GlobalControllerExceptionHandler {
+ private static final LoggerWrapper LOG = LoggerWrapper.get(GlobalControllerExceptionHandler.class);
+
+
+ @ExceptionHandler(Exception.class)
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
+ LOG.error("Exception at request " + req.getRequestURL());
+ ModelAndView mav = new ModelAndView("exception/exception");
+ mav.addObject("exception", e);
+ LoggedUser loggedUser = LoggedUser.safeGet();
+
+ // Interceptor is not invoked, put userTo
+ if (loggedUser != null) {
+ mav.addObject("userTo", loggedUser.getUserTo());
+ }
+ return mav;
+ }
+}
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..332d7d71c174
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java
@@ -0,0 +1,95 @@
+package ru.javawebinar.topjava.web;
+
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.support.SessionStatus;
+import ru.javawebinar.topjava.LoggedUser;
+import ru.javawebinar.topjava.to.UserTo;
+import ru.javawebinar.topjava.util.UserUtil;
+import ru.javawebinar.topjava.web.user.AbstractUserController;
+
+import javax.validation.Valid;
+
+/**
+ * User: gkislin
+ * Date: 22.08.2014
+ */
+@Controller
+public class RootController extends AbstractUserController {
+
+ @RequestMapping(value = "/", method = RequestMethod.GET)
+ public String root() {
+ return "redirect:meals";
+ }
+
+ // @Secured("ROLE_ADMIN")
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ @RequestMapping(value = "/users", method = RequestMethod.GET)
+ public String userList() {
+ return "userList";
+ }
+
+ @RequestMapping(value = "/meals", method = RequestMethod.GET)
+ public String mealList() {
+ return "mealList";
+ }
+
+ @RequestMapping(value = "/login", method = RequestMethod.GET)
+ public String login(ModelMap model,
+ @RequestParam(value = "error", required = false) boolean error,
+ @RequestParam(value = "message", required = false) String message) {
+
+ model.put("error", error);
+ model.put("message", message);
+ return "login";
+ }
+
+ @RequestMapping(value = "/profile", method = RequestMethod.GET)
+ public String profile() {
+ return "profile";
+ }
+
+ @RequestMapping(value = "/profile", method = RequestMethod.POST)
+ public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status) {
+ if (!result.hasErrors()) {
+ try {
+ userTo.setId(LoggedUser.id());
+ super.update(userTo);
+ LoggedUser.get().update(userTo);
+ status.setComplete();
+ return "redirect:meals";
+ } catch (DataIntegrityViolationException ex) {
+ result.rejectValue("email", "error.user", "user with this email already present in application");
+ }
+ }
+ return "profile";
+ }
+
+ @RequestMapping(value = "/register", method = RequestMethod.GET)
+ public String register(ModelMap model) {
+ model.addAttribute("userTo", new UserTo());
+ model.addAttribute("register", true);
+ return "profile";
+ }
+
+ @RequestMapping(value = "/register", method = RequestMethod.POST)
+ public String saveRegister(@Valid UserTo userTo, BindingResult result, SessionStatus status, ModelMap model) {
+ if (!result.hasErrors()) {
+ try {
+ super.create(UserUtil.createFromTo(userTo));
+ status.setComplete();
+ return "redirect:login?message=app.registered";
+ } catch (DataIntegrityViolationException ex) {
+ result.rejectValue("email", "error.user", "user with this email already present in application");
+ }
+ }
+ model.addAttribute("register", true);
+ return "profile";
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java b/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java
new file mode 100644
index 000000000000..84f99195eae2
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java
@@ -0,0 +1,24 @@
+package ru.javawebinar.topjava.web.interceptor;
+
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+import ru.javawebinar.topjava.LoggedUser;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This interceptor adds the user to the model of every requests managed
+ */
+public class ModelInterceptor extends HandlerInterceptorAdapter {
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+ if (modelAndView != null && !modelAndView.isEmpty() && modelAndView.getModelMap().get("user") == null) {
+ LoggedUser loggedUser = LoggedUser.safeGet();
+ if (loggedUser != null) {
+ modelAndView.getModelMap().addAttribute("userTo", loggedUser.getUserTo());
+ }
+ }
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java
new file mode 100644
index 000000000000..3f4ec95f8176
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java
@@ -0,0 +1,39 @@
+package ru.javawebinar.topjava.web.json;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+/**
+ * User: gkislin
+ * Date: 26.05.2014
+ *
+ Java Enterprise проект с регистрацией/авторизацией и интерфейсом на основе ролей (USER, ADMIN).
+ Администратор может создавать/редактировать/удалять/пользователей, а пользователь - управлять своим
+ профилем и данными (день, еда, калории) через UI (по AJAX) и по REST интерфейсу с базовой авторизацией.
+ Возможна фильтрация данных по датам и времени, при этом цвет записи таблицы еды зависит от того, превышает ли сумма
+ калорий за день норму (редактируемый параметр в профиле пользователя).
+ Весь REST интерфейс покрывается JUnit тестами, используя Spring MVC Test и Spring Security Test.
+