diff --git a/.gitignore b/.gitignore
index 19affadb3ca7..a84826880f2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ log
/src/main/java/META-INF/MANIFEST.MF
+code/
diff --git a/pom.xml b/pom.xml
index 9a68ddb0c721..17cefc57282c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,12 @@
4.0.1
provided
+
+
+ javax.servlet
+ jstl
+ 1.2
+
diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java
deleted file mode 100644
index c2f9cc618f7c..000000000000
--- a/src/main/java/ru/javawebinar/topjava/Main.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package ru.javawebinar.topjava;
-
-/**
- * @see Demo application
- * @see Initial project
- */
-public class Main {
- public static void main(String[] args) {
- System.out.format("Hello TopJava Enterprise!");
- }
-}
diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java
new file mode 100644
index 000000000000..8a13cae59c0d
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java
@@ -0,0 +1,60 @@
+package ru.javawebinar.topjava.model;
+
+import java.time.LocalDateTime;
+
+public class Meal {
+ private Integer id;
+
+ private final LocalDateTime dateTime;
+ private final String description;
+ private final int calories;
+
+ public Meal(LocalDateTime dateTime, String description, int calories) {
+ this(null, dateTime, description, calories);
+ }
+
+ public Meal(Integer id, LocalDateTime dateTime, String description, int calories) {
+ this.id = id;
+ this.dateTime = dateTime;
+ this.description = description;
+ this.calories = calories;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getCalories() {
+ return calories;
+ }
+
+ public boolean isNew() {
+ return id == null;
+ }
+
+ @Override
+ public String toString() {
+ return "Meal{" +
+ "id=" + id +
+ ", dateTime=" + dateTime +
+ ", description='" + description + '\'' +
+ ", calories=" + calories +
+ '}';
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java
similarity index 54%
rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java
rename to src/main/java/ru/javawebinar/topjava/model/MealTo.java
index d8f91b127f6a..25e625398f8e 100644
--- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java
+++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java
@@ -2,17 +2,19 @@
import java.time.LocalDateTime;
-public class UserMeal {
+public class MealTo {
+ protected Integer id;
private final LocalDateTime dateTime;
-
private final String description;
-
private final int calories;
+ private final boolean excess;
- public UserMeal(LocalDateTime dateTime, String description, int calories) {
+ public MealTo(Integer id, LocalDateTime dateTime, String description, int calories, boolean excess) {
+ this.id = id;
this.dateTime = dateTime;
this.description = description;
this.calories = calories;
+ this.excess = excess;
}
public LocalDateTime getDateTime() {
@@ -26,4 +28,16 @@ public String getDescription() {
public int getCalories() {
return calories;
}
-}
+
+ public boolean isExcess() {
+ return excess;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java
deleted file mode 100644
index d0aa431a35d9..000000000000
--- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package ru.javawebinar.topjava.model;
-
-import java.time.LocalDateTime;
-
-public class UserMealWithExcess {
- private final LocalDateTime dateTime;
-
- private final String description;
-
- private final int calories;
-
- private final boolean excess;
-
- public UserMealWithExcess(LocalDateTime dateTime, String description, int calories, boolean excess) {
- this.dateTime = dateTime;
- this.description = description;
- this.calories = calories;
- this.excess = excess;
- }
-
- @Override
- public String toString() {
- return "UserMealWithExcess{" +
- "dateTime=" + dateTime +
- ", description='" + description + '\'' +
- ", calories=" + calories +
- ", excess=" + excess +
- '}';
- }
-}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepository.java
new file mode 100644
index 000000000000..d8e6490d87da
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryUserMealRepository.java
@@ -0,0 +1,47 @@
+package ru.javawebinar.topjava.repository;
+
+import ru.javawebinar.topjava.model.Meal;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class InMemoryUserMealRepository implements MealRepository {
+ private Map repository = new ConcurrentHashMap<>();
+ private AtomicInteger counter = new AtomicInteger(0);
+
+ {
+ save(new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500));
+ save(new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000));
+ save(new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500));
+ save(new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000));
+ save(new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500));
+ save(new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510));
+ }
+
+ @Override
+ public Collection getAll() {
+ return repository.values();
+ }
+
+ @Override
+ public Meal save(Meal meal) {
+ if (meal.isNew()) {
+ meal.setId(counter.incrementAndGet());
+ }
+ return repository.put(meal.getId(), meal);
+ }
+
+ @Override
+ public boolean delete(int id) {
+ return repository.remove(id) != null;
+ }
+
+ @Override
+ public Meal get(int id) {
+ return repository.get(id);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java
new file mode 100644
index 000000000000..2cb2aef8ba8d
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java
@@ -0,0 +1,18 @@
+package ru.javawebinar.topjava.repository;
+
+import ru.javawebinar.topjava.model.Meal;
+
+import java.util.Collection;
+
+public interface MealRepository {
+ // null if not found, when updated
+ Meal save(Meal meal);
+
+ // false if not found
+ boolean delete(int id);
+
+ // null if not found
+ Meal get(int id);
+
+ Collection getAll();
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java
new file mode 100644
index 000000000000..fc9e659fdca4
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java
@@ -0,0 +1,18 @@
+package ru.javawebinar.topjava.util;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+public class DateTimeUtil {
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+
+ public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) {
+ return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0;
+ }
+
+ public static String toString(LocalDateTime ldt) {
+ return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER);
+ }
+}
+
diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java
new file mode 100644
index 000000000000..291c1afd92be
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java
@@ -0,0 +1,38 @@
+package ru.javawebinar.topjava.util;
+
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.model.MealTo;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class MealsUtil {
+
+ public static final int DEFAULT_CALORIES_PER_DAY = 1000;
+
+ public static List getTos(Collection meals, int caloriesPerDay) {
+ return getFiltered(meals, caloriesPerDay, meal -> true);
+ }
+
+ public static List getFilteredTos(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) {
+ return getFiltered(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime));
+ }
+
+ private static List getFiltered(Collection meals, int caloriesPerDay, Predicate filter) {
+ Map caloriesSumByDate = meals.stream()
+ .collect(Collectors.groupingBy(Meal::getDateTime, Collectors.summingInt(Meal::getCalories)));
+ return meals.stream()
+ .filter(filter)
+ .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDateTime()) > caloriesPerDay))
+ .collect(Collectors.toList());
+ }
+
+ private static MealTo createTo(Meal meal, boolean excess) {
+ return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java
deleted file mode 100644
index 0ebfdb5fcdcb..000000000000
--- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package ru.javawebinar.topjava.util;
-
-import java.time.LocalTime;
-
-public class TimeUtil {
- public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) {
- return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0;
- }
-}
diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java
deleted file mode 100644
index 3c171b4a5972..000000000000
--- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package ru.javawebinar.topjava.util;
-
-import ru.javawebinar.topjava.model.UserMeal;
-import ru.javawebinar.topjava.model.UserMealWithExcess;
-
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.Month;
-import java.util.Arrays;
-import java.util.List;
-
-public class UserMealsUtil {
- public static void main(String[] args) {
- List meals = Arrays.asList(
- new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500),
- new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000),
- new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500),
- new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100),
- new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000),
- new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500),
- new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410)
- );
-
- List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000);
- mealsTo.forEach(System.out::println);
-
-// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000));
- }
-
- public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
- // TODO return filtered list with excess. Implement by cycles
- return null;
- }
-
- public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
- // TODO Implement by streams
- return null;
- }
-}
diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java
new file mode 100644
index 000000000000..cc7fb720f417
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java
@@ -0,0 +1,79 @@
+package ru.javawebinar.topjava.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.repository.InMemoryUserMealRepository;
+import ru.javawebinar.topjava.repository.MealRepository;
+import ru.javawebinar.topjava.util.MealsUtil;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Objects;
+
+public class MealServlet extends HttpServlet {
+ private static final Logger log = LoggerFactory.getLogger(MealServlet.class);
+
+ private MealRepository repository;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+ repository = new InMemoryUserMealRepository();
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ request.setCharacterEncoding("UTF-8");
+ String id = request.getParameter("id");
+
+ Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id),
+ LocalDateTime.parse(request.getParameter("dateTime")),
+ request.getParameter("description"),
+ Integer.parseInt(request.getParameter("calories")));
+
+ log.info(meal.isNew() ? "Create {}" : "Update {}", meal);
+ repository.save(meal);
+ response.sendRedirect("meals");
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String action = request.getParameter("action");
+
+ switch (action == null ? "all" : action) {
+ case "delete":
+ int id = getId(request);
+ log.info("Delete {}", id);
+ repository.delete(id);
+ response.sendRedirect("meals");
+ break;
+ case "create":
+ case "update":
+ final Meal meal = "create".equals(action) ?
+ new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) :
+ repository.get(getId(request));
+ request.setAttribute("meal", meal);
+ request.getRequestDispatcher("/mealForm.jsp").forward(request, response);
+ break;
+ case "all":
+ default:
+ log.info("getAll");
+ request.setAttribute("meals",
+ MealsUtil.getTos(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY));
+ request.getRequestDispatcher("/meals.jsp").forward(request, response);
+ break;
+ }
+ }
+
+ private int getId(HttpServletRequest request) {
+ String paramId = Objects.requireNonNull(request.getParameter("id"));
+ return Integer.parseInt(paramId);
+ }
+}
diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld
new file mode 100644
index 000000000000..d138fecdbfb5
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tld/functions.tld
@@ -0,0 +1,16 @@
+
+
+
+ 1.0
+ functions
+ http://topjava.javawebinar.ru/functions
+
+
+ formatDateTime
+ ru.javawebinar.topjava.util.DateTimeUtil
+ java.lang.String toString(java.time.LocalDateTime)
+
+
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
index c63810c43593..6b89f4e08c8f 100644
--- a/src/main/webapp/WEB-INF/web.xml
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -16,4 +16,12 @@
/users
+
+ mealServlet
+ ru.javawebinar.topjava.web.MealServlet
+
+
+ mealServlet
+ /meals
+
diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css
new file mode 100644
index 000000000000..49de1f4f54eb
--- /dev/null
+++ b/src/main/webapp/css/style.css
@@ -0,0 +1,36 @@
+header {
+ background: none repeat scroll 0 0 #A6C9E2;
+ color: #2E6E9E;
+ font-size: 20px;
+ padding: 5px 20px;
+}
+
+footer {
+ background: none repeat scroll 0 0 #A6C9E2;
+ color: #2E6E9E;
+ font-size: 20px;
+ padding: 5px 20px;
+ margin: 20px 0;
+}
+
+dl {
+ background: none repeat scroll 0 0 #FAFAFA;
+ margin: 8px 0;
+ padding: 0;
+}
+
+dt {
+ display: inline-block;
+ width: 170px;
+}
+
+dd {
+ display: inline-block;
+ margin-left: 8px;
+ vertical-align: top;
+}
+
+h2 {
+ margin: 15px 0 5px;
+}
+
diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
index 6253517f8b84..c0fea3e87bcc 100644
--- a/src/main/webapp/index.html
+++ b/src/main/webapp/index.html
@@ -7,7 +7,8 @@