diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 000000000000..a7672ebf36af --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,4 @@ +--- +exclude_paths: + - 'src/main/webapp/**' + - 'README.md' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6ca05c4a5184..a687d4b70ac9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ out target *.iml log +patch diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..68ab0a037ad1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +# https://docs.travis-ci.com/user/languages/java/ +language: java +jdk: oraclejdk8 + +#https://dzone.com/articles/travis-ci-tutorial-java-projects +cache: + directories: + - $HOME/.m2 + +# https://docs.travis-ci.com/user/database-setup/#PostgreSQL +before_script: +- psql -c 'create database topjava' -U postgres +- psql -c 'create user "user"; grant all privileges on database topjava to "user"' -U postgres + +# https://docs.travis-ci.com/user/customizing-the-build#Building-Specific-Branches +branches: + only: + - master + +# https://docs.travis-ci.com/user/notifications#Configuring-email-notifications +#notifications: +# email: false \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000000..56bcef35575a --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Java Enterprise Online Project +=============================== +Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API и хранением в базах данных Postgresql и HSQLDB. + diff --git a/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 000000000000..ae142c42357e --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,185 @@ +# TopJava Release Notes +### Topjava 16 +- Выделил общий код реализации хранения в памяти в `InMemoryBaseRepositoryImpl` +- Сделал подтверждение для удаления записей +- Обновились видео 7-го занятия. [Выложил его как пример занятия, некоторые видео открыты](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md) +- Сделали валидации дублирования email через `WebDataBinder` и `Validator` + +### Topjava 15 +- Миграция на Servlet API 4.0 / Tomcat 9.x +- [Миграция на JDK11](http://javaops.ru/view/resources/jdk8_11) +- JUnit5 fix: junit-platform-surefire-provider не нужен +- Рефакторинг тестов: + - в `RootControllerTest.testUsers` для проверки используем `AssertionMatcher` адаптер + - вместо `content().json()` от `jsonassert` десериализуем json и используем сравнения через `AssertJ` +- В javascript место глабальных переменных и одинаковой функции обновления таблицы задаю их в объекте контекст, который передаю в `makeEditable()` как параметр +- Починил `back` в браузере после логина. Кнопки входа и регистрации отображаю только для `isAnonymous()` + +### Topjava 14 +- [Миграция на JUnit 5](http://javaops.ru/view/resources/junit5) +- Для измерения времени в тестах использую [Spring StopWatch](https://www.logicbig.com/how-to/code-snippets/jcode-spring-framework-stopwatch.html) +- `SimpleJdbcInsert` и `NamedParameterJdbcTemplate` конструируются (и берут настройки) из `jdbcTemplate` +- `AuthorizedUser` зарефакторился в `SecurityUtil` +- В javascript [заменил `var` на `let/const`](https://learn.javascript.ru/let-const). [Поддержка 95% браузеров](https://caniuse.com/#feat=const) +- Подправил UI фильтрации и заголовка страниц, добавилась кнопка `Cancel` в профиль +- Починил [баг в FireFox](https://bugzilla.mozilla.org/show_bug.cgi?id=884693): пустой ответ по ajax +- Сделал вход в приложение при нажании кнопок `Зайти как ...` +- Добавил регистрацию пользователя по REST +- Преименовал js файлы согласно [javascript filename naming convention](https://stackoverflow.com/questions/7273316/what-is-the-javascript-filename-naming-convention) +- Сделал проверку startTime/endTime на фильтре времени (после обновления datetimepicker до 2.5.20) + +### Topjava 13 +- [Миграция на Botstrap 4](https://getbootstrap.com/docs/4.1/migration/) +- Добавил [Responsive behaviors](https://getbootstrap.com/docs/4.1/components/navbar/#responsive-behaviors) - при уменшении ширины экрана навигация сворачивается в кнопку +- Для отображения цвета еды и выключенного юзера использую [data-* атрибуты](https://developer.mozilla.org/ru/docs/Web/Guide/HTML/Using_data_attributes) +- В `inputField.tag` передаю как параметр код для локализации label, а в `i18n.jsp` передаю как параметр `page`. См. [JSP include action with parameter example](https://beginnersbook.com/2013/12/jsp-include-with-parameter-example) + +### Topjava 12 +- [Миграция на Spring 5](http://javaops.ru/view/resources/spring5) +- обновил версии: Ehcache 3.x, datatables, datetimepicker +- добавил видео решений HW0 с одним проходом +- поправил видео [Обзор Spring Framework. Spring Context](https://drive.google.com/file/d/1fBSLGEbc7YXBbmr_EwEHltCWNW_pUmIH). Дописал про Constructor injection. +- заменил видео про тетсирование сервисов. Вместо самодельных матчеров стали использовать [AssertJ](http://joel-costigliola.github.io/assertj/index.html). Видео [Тестирование UserService через AssertJ](https://drive.google.com/open?id=1SPMkWMYPvpk9i0TA7ioa-9Sn1EGBtClD), время 1:53 +- сделал [видео с jQuery конвертерами и дефолтными группами валидации при сохранении в базу](https://drive.google.com/open?id=1tOMOdmaP5OQ7iynwC77bdXSs-13Ommax) +- сделал [видео с новым `DelegatingPasswordEncoder` и Json READ/WRITE access](https://drive.google.com/file/d/1XZXvOThinzPw4EhigAUdo8-MWT_g8wOt/view?usp=sharing) +- убрал `AccessType.PROPERTY` для `AbstractBaseEntity.id` (см. [fixed HHH-3718](https://hibernate.atlassian.net/browse/HHH-3718)) +- удалил `PasswordUtil`, возвращаю статус `NO_CONTENT` для REST delete, убрал группы валидации в `UserTo` +- заменил в jQuery [success на done](https://stackoverflow.com/a/22213543/548473) +- вместо `lang.jsp` сделал общий `bodyHeader.jsp` + +### Topjava 11 +- добавил + - доп. решение HW1 через одним return и O(N) + - раскрасил лог ([Logback layouts coloring](https://logback.qos.ch/manual/layouts.html#coloring)) +- рефакторинг + - починил коммит формы по cancel (`history.back()`) в FireFox + - заменил неработающий DependencyCi на [VersionEye](https://www.versioneye.com/) c проверкой зависимостей на uptodate + - починил `CrudUserRepository.getWithMeals()` через `@EntityGraph`. С неколькими ролями (у админа) еда дублируется + - починил тесты контроллеров с профилем JDBC (`JpaUtil` отсутствует в контексте JDBC) + - переименовал `meal.jsp/user.jsp` в `mealForm.jsp/userForm.jsp` + - в `InMemoryMealRepositoryImpl.save()` сделал update атомарным + - переименовал методы сервисов `save` в `create` + - переименовал и cделал классы `BaseEntity` и `NamedEntity` абстрактными + - обновил Noty и API с ним до 3.1.0. Добавил glyphicon в сообщения Noty + - заменил `MATCHER_WITH_EXCEED` на валидацию через [JSONassert](https://github.com/skyscreamer/JSONassert). + - поменял Deprecated валидаторы `org.hibernate.validator.constraints` на `javax.validation.constraints` + - убрал пароль из результатов REST через [@JsonProperty READ_ONLY / WRITE_ONLY](https://stackoverflow.com/questions/12505141/only-using-jsonignore-during-serialization-but-not-deserialization/12505165#12505165). Тесты на REST пришлось починить добавлением добавлением в JSON пароля как дополнительного параметра (`JsonUtil.writeWithExtraProps`) + - **убрал JSON View и сделал преобразование времени на UI с помощью [jQuery converters](http://api.jquery.com/jQuery.ajax/#using-converters)** + - **поменял [группу валидации по умолчанию при сохранении через JPA](https://stackoverflow.com/questions/16930623/16930663#16930663).** Теперь + все валидаторы в модели работаю по умолчанию (`groups` не требуется). + - Добавил в `ErrorInfo` тип ошибки `ErrorType` + i18n. + +- правки + - переименовал `ModelMatcher` в `BeanMatcher` и починил: можно сравнивать только упорядоченные коллекции (List) + - поменял зависимость `org.hibernate:hibernate-validator` на `org.hibernate.validator:hibernate-validator` (warning при сборке) + +### Topjava 10 +- добавил + - доступ к AuthorizedUser через [`@AuthenticationPrincipal`](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#mvc-authentication-principal) и [authentication Tag](http://docs.spring.io/spring-security/site/docs/current/reference/html/taglibs.html#the-authentication-tag) + - [Обработку 404 NotFound](https://stackoverflow.com/questions/18322279/spring-mvc-spring-security-and-error-handling) + - локализацию ошибок валидации + - проверки json в тестах через [JSONassert](https://github.com/skyscreamer/JSONassert) и [через jsonPath](https://www.petrikainulainen.net/programming/spring-framework/integration-testing-of-spring-mvc-applications-write-clean-assertions-with-jsonpath/) + - [логирование от Postgres Driver](http://stackoverflow.com/a/43242620/548473) + - в `.travis.yml` [сборку только ветки master](https://docs.travis-ci.com/user/customizing-the-build#Building-Specific-Branches) + - [защиту от кэширование ajax запросов в IE](https://stackoverflow.com/a/4303862/548473) + - обработку запрета модификации системный юзеров через универсальный `ApplicationException` + - рефакторинг + - сделал `@EntityGraph` через `attributePaths` + - реализаовал обработку дублирования `user.email` и `meal.dateTime` через [Controller Based Exception Handling](https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc#controller-based-exception-handling) + - поменял отключение транзакционности в тестах через `@Transactional(propagation = Propagation.NEVER)` + - сделал выбор в сервлете через switch + - [все логгирование сделал через {} форматирование](http://stackoverflow.com/questions/10555409/logger-slf4j-advantages-of-formatting-with-instead-of-string-concatenation) и поправил его в контроллерах (поле проверки id) + - [перешел на конструктор DI](http://stackoverflow.com/questions/39890849/what-exactly-is-field-injection-and-how-to-avoid-it) + - в `ModelMatcher` переименовал `Comparator` -> `Equality` + - [заинлайнил все лямбды](http://stackoverflow.com/questions/19718353/is-repeatedly-instantiating-an-anonymous-class-wasteful) (компараторы, ModelMatcher.equality) + - поменялась реализация `JdbcUserRepositoryImpl.getAll()` + - на UI кнопки в таблице заменились на линки, поправил сообщения локализации + - [сделал кастомизацию JSON (@JsonView) и валидацию (groups)](https://drive.google.com/file/d/0B9Ye2auQ_NsFRTFsTjVHR2dXczA) для данных еды, отдаваемых на UI + - в `JdbcUserRepositoryImpl` поменял `MapSqlParameterSource` на `BeanPropertySqlParameterSource` +- удалил + - зависимость `javax.transaction.jta` (уже не нужна) + - `${spring.version}` в `pom.xml` зависимостях (уже есть в `spring-framework-bom`) + - distinct из запроса Hibernate на пользователей с ролями. [Оптимизация запроса distinct: 15.16.2](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct) + - лишние `
+ * Handling Hibernate lazy-loading
+ *
+ * @link https://github.com/FasterXML/jackson
+ * @link https://github.com/FasterXML/jackson-datatype-hibernate
+ * @link https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers
+ */
+public class JacksonObjectMapper extends ObjectMapper {
+
+ private static final ObjectMapper MAPPER = new JacksonObjectMapper();
+
+ private JacksonObjectMapper() {
+ registerModule(new Hibernate5Module());
+
+ registerModule(new JavaTimeModule());
+ configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+
+ setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
+ setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+ setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ }
+
+ public static ObjectMapper getMapper() {
+ return MAPPER;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java
new file mode 100644
index 000000000000..fda04590d618
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java
@@ -0,0 +1,37 @@
+package ru.javawebinar.topjava.web.json;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectReader;
+
+import java.io.IOException;
+import java.util.List;
+
+import static ru.javawebinar.topjava.web.json.JacksonObjectMapper.getMapper;
+
+public class JsonUtil {
+
+ public static
+
+
+ Стек технологий: Spring Security,
+ Spring MVC,
+ Spring Data JPA,
+ Spring Security
+ Test,
+ Hibernate ORM,
+ Hibernate Validator,
+ SLF4J,
+ Json Jackson,
+ JSP,
+ JSTL,
+ Apache Tomcat,
+ WebJars,
+ DataTables plugin,
+ EHCACHE,
+ PostgreSQL,
+ JUnit,
+ Hamcrest,
+ jQuery,
+ jQuery notification,
+ Bootstrap.Filter separately
+ *
+ */
+ public List
");
+ result.getFieldErrors().forEach(
+ fe -> {
+ String msg = fe.getDefaultMessage();
+ if (msg != null) {
+ if (!msg.startsWith(fe.getField())) {
+ msg = fe.getField() + ' ' + msg;
+ }
+ joiner.add(msg);
+ }
+ });
+ return ResponseEntity.unprocessableEntity().body(joiner.toString());
+ }
+ if (userTo.isNew()) {
+ super.create(UserUtil.createNewFromTo(userTo));
+ } else {
+ super.update(userTo, userTo.id());
+ }
+ return ResponseEntity.ok().build();
+ }
+
+ @Override
+ @PostMapping("/{id}")
+ @ResponseStatus(value = HttpStatus.NO_CONTENT)
+ public void enable(@PathVariable int id, @RequestParam boolean enabled) {
+ super.enable(id, enabled);
+ }
+}
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..724de8823399
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java
@@ -0,0 +1,37 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import ru.javawebinar.topjava.model.User;
+import ru.javawebinar.topjava.to.UserTo;
+
+import static ru.javawebinar.topjava.web.SecurityUtil.authUserId;
+
+@RestController
+@RequestMapping(ProfileRestController.REST_URL)
+public class ProfileRestController extends AbstractUserController {
+ static final String REST_URL = "/rest/profile";
+
+ @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+ public User get() {
+ return super.get(authUserId());
+ }
+
+ @DeleteMapping
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void delete() {
+ super.delete(authUserId());
+ }
+
+ @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void update(@RequestBody UserTo userTo) {
+ super.update(userTo, authUserId());
+ }
+
+ @GetMapping(value = "/text")
+ public String testUTF() {
+ return "Русский текст";
+ }
+}
\ 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..c6347bd940d6
--- /dev/null
+++ b/src/main/resources/cache/ehcache.xml
@@ -0,0 +1,25 @@
+
+
+
+ <%--https://getbootstrap.com/docs/4.0/components/card/--%>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
" + jqXHR.responseJSON : ""),
+ type: "error",
+ layout: "bottomRight"
+ }).show();
+}
+
+function renderEditBtn(data, type, row) {
+ if (type === "display") {
+ return "";
+ }
+}
+
+function renderDeleteBtn(data, type, row) {
+ if (type === "display") {
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/src/main/webapp/resources/js/topjava.meals.js b/src/main/webapp/resources/js/topjava.meals.js
new file mode 100644
index 000000000000..e015f4571f02
--- /dev/null
+++ b/src/main/webapp/resources/js/topjava.meals.js
@@ -0,0 +1,48 @@
+function updateFilteredTable() {
+ $.ajax({
+ type: "GET",
+ url: "ajax/profile/meals/filter",
+ data: $("#filter").serialize()
+ }).done(updateTableByData);
+}
+
+function clearFilter() {
+ $("#filter")[0].reset();
+ $.get("ajax/profile/meals/", updateTableByData);
+}
+
+$(function () {
+ makeEditable({
+ ajaxUrl: "ajax/profile/meals/",
+ datatableApi: $("#datatable").DataTable({
+ "paging": false,
+ "info": true,
+ "columns": [
+ {
+ "data": "dateTime"
+ },
+ {
+ "data": "description"
+ },
+ {
+ "data": "calories"
+ },
+ {
+ "defaultContent": "Edit",
+ "orderable": false
+ },
+ {
+ "defaultContent": "Delete",
+ "orderable": false
+ }
+ ],
+ "order": [
+ [
+ 0,
+ "desc"
+ ]
+ ]
+ }),
+ updateTable: updateFilteredTable
+ });
+});
\ No newline at end of file
diff --git a/src/main/webapp/resources/js/topjava.users.js b/src/main/webapp/resources/js/topjava.users.js
new file mode 100644
index 000000000000..6601846b62d7
--- /dev/null
+++ b/src/main/webapp/resources/js/topjava.users.js
@@ -0,0 +1,91 @@
+const userAjaxUrl = "ajax/admin/users/";
+
+function enable(chkbox, id) {
+ const enabled = chkbox.is(":checked");
+// https://stackoverflow.com/a/22213543/548473
+ $.ajax({
+ url: userAjaxUrl + id,
+ type: "POST",
+ data: "enabled=" + enabled
+ }).done(function () {
+ chkbox.closest("tr").attr("data-userEnabled", enabled);
+ successNoty(enabled ? "common.enabled" : "common.disabled");
+ }).fail(function () {
+ $(chkbox).prop("checked", !enabled);
+ });
+}
+
+// $(document).ready(function () {
+$(function () {
+ makeEditable({
+ ajaxUrl: userAjaxUrl,
+ datatableApi: $("#datatable").DataTable({
+ "ajax": {
+ "url": userAjaxUrl,
+ "dataSrc": ""
+ },
+ "paging": false,
+ "info": true,
+ "columns": [
+ {
+ "data": "name"
+ },
+ {
+ "data": "email",
+ "render": function (data, type, row) {
+ if (type === "display") {
+ return "" + data + "";
+ }
+ return data;
+ }
+ },
+ {
+ "data": "roles"
+ },
+ {
+ "data": "enabled",
+ "render": function (data, type, row) {
+ if (type === "display") {
+ return "";
+ }
+ return data;
+ }
+ },
+ {
+ "data": "registered",
+ "render": function (date, type, row) {
+ if (type === "display") {
+ return date.substring(0, 10);
+ }
+ return date;
+ }
+ },
+ {
+ "orderable": false,
+ "defaultContent": "",
+ "render": renderEditBtn
+ },
+ {
+ "orderable": false,
+ "defaultContent": "",
+ "render": renderDeleteBtn
+ }
+ ],
+ "order": [
+ [
+ 0,
+ "asc"
+ ]
+ ],
+ "createdRow": function (row, data, dataIndex) {
+ if (!data.enabled) {
+ $(row).attr("data-userEnabled", false);
+ }
+ }
+ }),
+ updateTable: function () {
+ $.get(userAjaxUrl, updateTableByData);
+ }
+ }
+ );
+});
\ No newline at end of file
diff --git a/src/main/webapp/test.html b/src/main/webapp/test.html
new file mode 100644
index 000000000000..711e4e2d8190
--- /dev/null
+++ b/src/main/webapp/test.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java
new file mode 100644
index 000000000000..05d47b19f214
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java
@@ -0,0 +1,12 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.test.context.ActiveProfilesResolver;
+
+//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver
+public class ActiveDbProfileResolver implements ActiveProfilesResolver {
+
+ @Override
+ public String[] resolve(Class> aClass) {
+ return new String[]{Profiles.getActiveDbProfile()};
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java b/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java
new file mode 100644
index 000000000000..4d1d1e9e3789
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java
@@ -0,0 +1,12 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.test.context.ActiveProfilesResolver;
+
+//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver
+public class AllActiveProfileResolver implements ActiveProfilesResolver {
+
+ @Override
+ public String[] resolve(Class> aClass) {
+ return new String[]{Profiles.REPOSITORY_IMPLEMENTATION, Profiles.getActiveDbProfile()};
+ }
+}
\ No newline at end of file
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..621d34ce3818
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java
@@ -0,0 +1,57 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.test.web.servlet.ResultMatcher;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.to.MealTo;
+
+import java.time.Month;
+import java.util.List;
+
+import static java.time.LocalDateTime.of;
+import static org.assertj.core.api.Assertions.assertThat;
+import static ru.javawebinar.topjava.TestUtil.readListFromJsonMvcResult;
+import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ;
+
+public class MealTestData {
+ public static final int MEAL1_ID = START_SEQ + 2;
+ public static final int ADMIN_MEAL_ID = START_SEQ + 8;
+
+ public static final Meal MEAL1 = new Meal(MEAL1_ID, of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500);
+ public static final Meal MEAL2 = new Meal(MEAL1_ID + 1, of(2015, Month.MAY, 30, 13, 0), "Обед", 1000);
+ public static final Meal MEAL3 = new Meal(MEAL1_ID + 2, of(2015, Month.MAY, 30, 20, 0), "Ужин", 500);
+ public static final Meal MEAL4 = new Meal(MEAL1_ID + 3, of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500);
+ public static final Meal MEAL5 = new Meal(MEAL1_ID + 4, of(2015, Month.MAY, 31, 13, 0), "Обед", 1000);
+ public static final Meal MEAL6 = new Meal(MEAL1_ID + 5, of(2015, Month.MAY, 31, 20, 0), "Ужин", 510);
+ public static final Meal ADMIN_MEAL1 = new Meal(ADMIN_MEAL_ID, of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510);
+ public static final Meal ADMIN_MEAL2 = new Meal(ADMIN_MEAL_ID + 1, of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500);
+
+ public static final List