From 5ac0cc62afd3a507f00a4d32e128c8566e966ab8 Mon Sep 17 00:00:00 2001 From: "javawebinar@yandex.ru" Date: Thu, 31 Jan 2019 14:16:22 +0300 Subject: [PATCH 001/125] Prepare_to_HW0 --- pom.xml | 2 +- .../java/ru/javawebinar/topjava/Main.java | 7 ++--- .../javawebinar/topjava/model/UserMeal.java | 29 +++++++++++++++++ .../topjava/model/UserMealWithExceed.java | 20 ++++++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 9 ++++++ .../topjava/util/UserMealsUtil.java | 31 +++++++++++++++++++ 6 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMeal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/pom.xml b/pom.xml index c8a1c78..e3b8143 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.7.0 ${java.version} ${java.version} diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java index b23a2f0..cb7e35a 100644 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ b/src/main/java/ru/javawebinar/topjava/Main.java @@ -1,11 +1,8 @@ package ru.javawebinar.topjava; /** - * User: gkislin - * Date: 05.08.2015 - * - * @link http://caloriesmng.herokuapp.com/ - * @link https://github.com/JavaOPs/topjava + * @see Demo + * @see Initial project */ public class Main { public static void main(String[] args) { 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 0000000..d8f91b1 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class UserMeal { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + public UserMeal(LocalDateTime dateTime, String description, int calories) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java new file mode 100644 index 0000000..3fe6237 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class UserMealWithExceed { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private final boolean exceed; + + public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.exceed = exceed; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java new file mode 100644 index 0000000..b7eb2af --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalTime; + +public class TimeUtil { + public static boolean isBetween(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 new file mode 100644 index 0000000..6ba2f4e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -0,0 +1,31 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.model.UserMealWithExceed; + +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 mealList = 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) + ); + getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +// .toLocalDate(); +// .toLocalTime(); + } + + public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO return filtered list with correctly exceeded field + return null; + } +} From 06ccf6ad31c887145a6e85fc4e2568d17f5932cf Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Feb 2019 21:33:00 +0300 Subject: [PATCH 002/125] 1_0_fix --- .../model/{UserMeal.java => Meal.java} | 4 +-- .../{UserMealWithExceed.java => MealTo.java} | 8 ++--- .../javawebinar/topjava/util/MealsUtil.java | 31 +++++++++++++++++++ .../topjava/util/UserMealsUtil.java | 31 ------------------- 4 files changed, 37 insertions(+), 37 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{UserMeal.java => Meal.java} (83%) rename src/main/java/ru/javawebinar/topjava/model/{UserMealWithExceed.java => MealTo.java} (59%) create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java similarity index 83% rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java rename to src/main/java/ru/javawebinar/topjava/model/Meal.java index d8f91b1..f546cef 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -2,14 +2,14 @@ import java.time.LocalDateTime; -public class UserMeal { +public class Meal { private final LocalDateTime dateTime; private final String description; private final int calories; - public UserMeal(LocalDateTime dateTime, String description, int calories) { + public Meal(LocalDateTime dateTime, String description, int calories) { this.dateTime = dateTime; this.description = description; this.calories = calories; diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java similarity index 59% rename from src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java rename to src/main/java/ru/javawebinar/topjava/model/MealTo.java index 3fe6237..743053b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExceed.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -2,19 +2,19 @@ import java.time.LocalDateTime; -public class UserMealWithExceed { +public class MealTo { private final LocalDateTime dateTime; private final String description; private final int calories; - private final boolean exceed; + private final boolean excess; - public UserMealWithExceed(LocalDateTime dateTime, String description, int calories, boolean exceed) { + public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) { this.dateTime = dateTime; this.description = description; this.calories = calories; - this.exceed = exceed; + this.excess = excess; } } 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 0000000..2711c89 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,31 @@ +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.time.Month; +import java.util.Arrays; +import java.util.List; + +public class MealsUtil { + public static void main(String[] args) { + List mealList = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) + ); + getFilteredWithExcess(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); +// .toLocalDate(); +// .toLocalTime(); + } + + public static List getFilteredWithExcess(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO return filtered list with correctly excess field + return null; + } +} \ 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 deleted file mode 100644 index 6ba2f4e..0000000 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.javawebinar.topjava.util; - -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.model.UserMealWithExceed; - -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 mealList = 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) - ); - getFilteredWithExceeded(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); -// .toLocalDate(); -// .toLocalTime(); - } - - public static List getFilteredWithExceeded(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with correctly exceeded field - return null; - } -} From 5986c4e9ffe9f66a68966115c18fd3e7818b6c2f Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Feb 2019 22:01:08 +0300 Subject: [PATCH 003/125] 1_1_HW0_stream --- .../ru/javawebinar/topjava/model/Meal.java | 10 +++++ .../ru/javawebinar/topjava/model/MealTo.java | 12 +++++- .../javawebinar/topjava/util/MealsUtil.java | 38 ++++++++++++------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index f546cef..943ff5c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; public class Meal { private final LocalDateTime dateTime; @@ -26,4 +28,12 @@ public String getDescription() { public int getCalories() { return calories; } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java index 743053b..23f88ce 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -17,4 +17,14 @@ public MealTo(LocalDateTime dateTime, String description, int calories, boolean this.calories = calories; this.excess = excess; } -} + + @Override + public String toString() { + return "MealTo{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", excess=" + excess + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 2711c89..7896011 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -3,29 +3,41 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.model.MealTo; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class MealsUtil { public static void main(String[] args) { - List mealList = Arrays.asList( - new Meal(LocalDateTime.of(2015, Month.MAY, 30,10,0), "Завтрак", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 30,13,0), "Обед", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 30,20,0), "Ужин", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31,10,0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 31,13,0), "Обед", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31,20,0), "Ужин", 510) + List meals = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) ); - getFilteredWithExcess(mealList, LocalTime.of(7, 0), LocalTime.of(12,0), 2000); -// .toLocalDate(); -// .toLocalTime(); + List mealsWithExcess = getFilteredWithExcess(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsWithExcess.forEach(System.out::println); } - public static List getFilteredWithExcess(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with correctly excess field - return null; + public static List getFilteredWithExcess(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) + ); + + return meals.stream() + .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> + new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), + caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); } } \ No newline at end of file From 808f55188871d945234f79dbfb4bbb7893100f41 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Feb 2019 22:10:48 +0300 Subject: [PATCH 004/125] 1_2_HW0_cycle --- .../javawebinar/topjava/util/MealsUtil.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index 7896011..c472334 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -7,9 +7,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class MealsUtil { @@ -24,6 +22,8 @@ public static void main(String[] args) { ); List mealsWithExcess = getFilteredWithExcess(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); mealsWithExcess.forEach(System.out::println); + + System.out.println(getFilteredWithExcessByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List getFilteredWithExcess(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -35,9 +35,25 @@ public static List getFilteredWithExcess(List meals, LocalTime sta return meals.stream() .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) - .map(meal -> - new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), - caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .map(meal -> createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) .collect(Collectors.toList()); } + + public static List getFilteredWithExcessByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + + final Map caloriesSumByDate = new HashMap<>(); + meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); + + final List mealsWithExcess = new ArrayList<>(); + meals.forEach(meal -> { + if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { + mealsWithExcess.add(createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + }); + return mealsWithExcess; + } + + public static MealTo createWithExcess(Meal meal, boolean excess) { + return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + } } \ No newline at end of file From 8555455d2d7dd457185ef079ad89b2195f0ed643 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Feb 2019 22:12:45 +0300 Subject: [PATCH 005/125] 1_3_HW0_optional2 --- .../javawebinar/topjava/util/MealsUtil.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c472334..e251ba0 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -8,7 +8,12 @@ import java.time.LocalTime; import java.time.Month; import java.util.*; +import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; public class MealsUtil { public static void main(String[] args) { @@ -24,6 +29,8 @@ public static void main(String[] args) { mealsWithExcess.forEach(System.out::println); System.out.println(getFilteredWithExcessByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExcessInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + System.out.println(getFilteredWithExcessInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List getFilteredWithExcess(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -53,6 +60,51 @@ public static List getFilteredWithExcessByCycle(List meals, LocalT return mealsWithExcess; } + public static List getFilteredWithExcessInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Collection> list = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate)).values(); + + return list.stream().flatMap(dayMeals -> { + boolean excess = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; + return dayMeals.stream().filter(meal -> + TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .map(meal -> createWithExcess(meal, excess)); + }).collect(toList()); + } + + public static List getFilteredWithExcessInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final class Aggregate { + private final List dailyMeals = new ArrayList<>(); + private int dailySumOfCalories; + + private void accumulate(Meal meal) { + dailySumOfCalories += meal.getCalories(); + if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { + dailyMeals.add(meal); + } + } + + // never invoked if the upstream is sequential + private Aggregate combine(Aggregate that) { + this.dailySumOfCalories += that.dailySumOfCalories; + this.dailyMeals.addAll(that.dailyMeals); + return this; + } + + private Stream finisher() { + final boolean excess = dailySumOfCalories > caloriesPerDay; + return dailyMeals.stream().map(meal -> createWithExcess(meal, excess)); + } + } + + Collection> values = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate, + Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) + ).values(); + + return values.stream().flatMap(identity()).collect(toList()); + } + public static MealTo createWithExcess(Meal meal, boolean excess) { return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); } From e060e902ac5f7fb5a7e5ea24ba53413259e86347 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 10 Feb 2019 13:26:58 +0300 Subject: [PATCH 006/125] 1_4_switch_to_war --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 19 +++++++++++++++++++ src/main/webapp/index.html | 13 +++++++++++++ src/main/webapp/users.jsp | 10 ++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index e3b8143..2e87eaf 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..c63810c --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + Topjava + + + userServlet + ru.javawebinar.topjava.web.UserServlet + 0 + + + userServlet + /users + + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 0000000..6253517 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,13 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

+
+ + + diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp new file mode 100644 index 0000000..acf19d7 --- /dev/null +++ b/src/main/webapp/users.jsp @@ -0,0 +1,10 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Users + + +

Home

+

Users

+ + \ No newline at end of file From e68147752abadbb1f58175de0746a3826ae96268 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 10 Feb 2019 13:44:48 +0300 Subject: [PATCH 007/125] 1_5_add_servlet_api --- pom.xml | 8 ++++++++ .../ru/javawebinar/topjava/web/UserServlet.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java diff --git a/pom.xml b/pom.xml index 2e87eaf..a7947fa 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,14 @@ + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java new file mode 100644 index 0000000..76056e0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,15 @@ +package ru.javawebinar.topjava.web; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class UserServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.getRequestDispatcher("/users.jsp").forward(request, response); + } +} From 07db2d5aad5f2f9e6a80929f4d003cc2c04ca5e9 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 10 Feb 2019 13:45:55 +0300 Subject: [PATCH 008/125] 1_6_forward_to_redirect --- pom.xml | 2 +- src/main/java/ru/javawebinar/topjava/web/UserServlet.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a7947fa..df4cb77 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ topjava - install + package org.apache.maven.plugins diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 76056e0..11f282b 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -10,6 +10,7 @@ public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - request.getRequestDispatcher("/users.jsp").forward(request, response); +// request.getRequestDispatcher("/users.jsp").forward(request, response); + response.sendRedirect("users.jsp"); } } From dcd5bbaa41fcac65f41c034cc1c96ad090b1355b Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 10 Feb 2019 13:57:09 +0300 Subject: [PATCH 009/125] 1_7_logging --- pom.xml | 25 ++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 7 +++++ src/main/resources/logback.xml | 29 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index df4cb77..64c3d6b 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ 1.8 UTF-8 UTF-8 + + + 1.2.3 + 1.7.25 @@ -34,6 +38,27 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 11f282b..ef52d67 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,15 +1,22 @@ package ru.javawebinar.topjava.web; +import org.slf4j.Logger; + import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import static org.slf4j.LoggerFactory.getLogger; + public class UserServlet extends HttpServlet { + private static final Logger log = getLogger(UserServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("redirect to users"); + // request.getRequestDispatcher("/users.jsp").forward(request, response); response.sendRedirect("users.jsp"); } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..e9b900b --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From 9632d55b238227cf273277625e888af3d5a40a69 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 15 Feb 2019 19:30:31 +0300 Subject: [PATCH 010/125] 2_1_HW1 --- pom.xml | 6 + .../java/ru/javawebinar/topjava/Main.java | 11 -- .../ru/javawebinar/topjava/model/MealTo.java | 16 +++ .../topjava/util/DateTimeUtil.java | 17 +++ .../javawebinar/topjava/util/MealsUtil.java | 103 ++++-------------- .../ru/javawebinar/topjava/util/TimeUtil.java | 9 -- .../javawebinar/topjava/web/MealServlet.java | 22 ++++ .../javawebinar/topjava/web/UserServlet.java | 6 +- src/main/webapp/WEB-INF/tld/functions.tld | 16 +++ src/main/webapp/WEB-INF/web.xml | 10 ++ src/main/webapp/index.html | 1 + src/main/webapp/meals.jsp | 48 ++++++++ 12 files changed, 161 insertions(+), 104 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/meals.jsp diff --git a/pom.xml b/pom.xml index 64c3d6b..591f030 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 cb7e35a..0000000 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.javawebinar.topjava; - -/** - * @see Demo - * @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/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java index 23f88ce..697ad5e 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -18,6 +18,22 @@ public MealTo(LocalDateTime dateTime, String description, int calories, boolean this.excess = excess; } + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExcess() { + return excess; + } + @Override public String toString() { return "MealTo{" + 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 0000000..5de2884 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,17 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + public static boolean isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; + } + + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index e251ba0..e4f2a2e 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -7,33 +7,35 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.*; -import java.util.stream.Collector; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; -import static java.util.function.Function.identity; import static java.util.stream.Collectors.toList; public class MealsUtil { - public static void main(String[] args) { - List meals = Arrays.asList( - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) - ); - List mealsWithExcess = getFilteredWithExcess(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsWithExcess.forEach(System.out::println); + public static final List MEALS = Arrays.asList( + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) + ); + + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + + public static List getWithExcess(List meals, int caloriesPerDay) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); + } - System.out.println(getFilteredWithExcessByCycle(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - System.out.println(getFilteredWithExcessInOnePass(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - System.out.println(getFilteredWithExcessInOnePass2(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + public static List getFilteredWithExcess(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); } - public static List getFilteredWithExcess(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + private static List getFilteredWithExcess(List meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -41,68 +43,9 @@ public static List getFilteredWithExcess(List meals, LocalTime sta ); return meals.stream() - .filter(meal -> TimeUtil.isBetween(meal.getTime(), startTime, endTime)) + .filter(filter) .map(meal -> createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) - .collect(Collectors.toList()); - } - - public static List getFilteredWithExcessByCycle(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - - final Map caloriesSumByDate = new HashMap<>(); - meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); - - final List mealsWithExcess = new ArrayList<>(); - meals.forEach(meal -> { - if (TimeUtil.isBetween(meal.getTime(), startTime, endTime)) { - mealsWithExcess.add(createWithExcess(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); - } - }); - return mealsWithExcess; - } - - public static List getFilteredWithExcessInOnePass(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - Collection> list = meals.stream() - .collect(Collectors.groupingBy(Meal::getDate)).values(); - - return list.stream().flatMap(dayMeals -> { - boolean excess = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; - return dayMeals.stream().filter(meal -> - TimeUtil.isBetween(meal.getTime(), startTime, endTime)) - .map(meal -> createWithExcess(meal, excess)); - }).collect(toList()); - } - - public static List getFilteredWithExcessInOnePass2(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - final class Aggregate { - private final List dailyMeals = new ArrayList<>(); - private int dailySumOfCalories; - - private void accumulate(Meal meal) { - dailySumOfCalories += meal.getCalories(); - if (TimeUtil.isBetween(meal.getDateTime().toLocalTime(), startTime, endTime)) { - dailyMeals.add(meal); - } - } - - // never invoked if the upstream is sequential - private Aggregate combine(Aggregate that) { - this.dailySumOfCalories += that.dailySumOfCalories; - this.dailyMeals.addAll(that.dailyMeals); - return this; - } - - private Stream finisher() { - final boolean excess = dailySumOfCalories > caloriesPerDay; - return dailyMeals.stream().map(meal -> createWithExcess(meal, excess)); - } - } - - Collection> values = meals.stream() - .collect(Collectors.groupingBy(Meal::getDate, - Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) - ).values(); - - return values.stream().flatMap(identity()).collect(toList()); + .collect(toList()); } public static MealTo createWithExcess(Meal meal, boolean 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 b7eb2af..0000000 --- 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 isBetween(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) <= 0; - } -} 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 0000000..c7d47ea --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.util.MealsUtil; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class MealServlet extends HttpServlet { + private static final Logger log = LoggerFactory.getLogger(MealServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.info("getAll"); + request.setAttribute("meals", MealsUtil.getWithExcess(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index ef52d67..f6cf12e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -15,9 +15,7 @@ public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.debug("redirect to users"); - -// request.getRequestDispatcher("/users.jsp").forward(request, response); - response.sendRedirect("users.jsp"); + log.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); } } 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 0000000..d138fec --- /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 c63810c..bd98d3b 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -16,4 +16,14 @@ /users + + mealServlet + ru.javawebinar.topjava.web.MealServlet + 0 + + + mealServlet + /meals + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 6253517..cd88b33 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -8,6 +8,7 @@

Проект Users +
  • Meals
  • diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp new file mode 100644 index 0000000..1c925f8 --- /dev/null +++ b/src/main/webapp/meals.jsp @@ -0,0 +1,48 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> +<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> + + + Meal list + + + +
    +

    Home

    +

    Meals

    +
    + + + + + + + + + + + + + + + + +
    DateDescriptionCalories
    + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> + ${fn:formatDateTime(meal.dateTime)} + ${meal.description}${meal.calories}
    +
    + + \ No newline at end of file From 3cb9297423d9efe9b8ccb2c62116fe9e8a440526 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 15 Feb 2019 19:33:39 +0300 Subject: [PATCH 011/125] 2_2_HW1_optional --- .../ru/javawebinar/topjava/model/Meal.java | 29 +++++++++ .../ru/javawebinar/topjava/model/MealTo.java | 12 +++- .../InMemoryMealRepositoryImpl.java | 45 +++++++++++++ .../topjava/repository/MealRepository.java | 15 +++++ .../javawebinar/topjava/util/MealsUtil.java | 9 +-- .../javawebinar/topjava/web/MealServlet.java | 63 ++++++++++++++++++- src/main/webapp/mealForm.jsp | 51 +++++++++++++++ src/main/webapp/meals.jsp | 5 ++ 8 files changed, 220 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/MealRepository.java create mode 100644 src/main/webapp/mealForm.jsp diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 943ff5c..3abbee4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,6 +5,8 @@ import java.time.LocalTime; public class Meal { + private Integer id; + private final LocalDateTime dateTime; private final String description; @@ -12,11 +14,24 @@ public class Meal { private final int calories; public Meal(LocalDateTime dateTime, String description, int calories) { + this(null, dateTime, description, calories); + } + + public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { + this.id = id; this.dateTime = dateTime; this.description = description; this.calories = calories; } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -36,4 +51,18 @@ public LocalDate getDate() { public LocalTime getTime() { return dateTime.toLocalTime(); } + + public boolean isNew() { + return id == null; + } + + @Override + public String toString() { + return "Meal{" + + "id=" + id + + ", dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + '}'; + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java index 697ad5e..3e502d4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -3,6 +3,8 @@ import java.time.LocalDateTime; public class MealTo { + private final Integer id; + private final LocalDateTime dateTime; private final String description; @@ -11,13 +13,18 @@ public class MealTo { private final boolean excess; - public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) { + 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 Integer getId() { + return id; + } + public LocalDateTime getDateTime() { return dateTime; } @@ -37,7 +44,8 @@ public boolean isExcess() { @Override public String toString() { return "MealTo{" + - "dateTime=" + dateTime + + "id=" + id + + ", dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + ", excess=" + excess + diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java new file mode 100644 index 0000000..9c63d2a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.MealsUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class InMemoryMealRepositoryImpl implements MealRepository { + private Map repository = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); + + { + MealsUtil.MEALS.forEach(this::save); + } + + @Override + public Meal save(Meal meal) { + if (meal.isNew()) { + meal.setId(counter.incrementAndGet()); + repository.put(meal.getId(), meal); + return meal; + } + // treat case: update, but absent in storage + return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + } + + @Override + public void delete(int id) { + repository.remove(id); + } + + @Override + public Meal get(int id) { + return repository.get(id); + } + + @Override + public Collection getAll() { + return repository.values(); + } +} + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java new file mode 100644 index 0000000..e249a88 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -0,0 +1,15 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.Meal; + +import java.util.Collection; + +public interface MealRepository { + Meal save(Meal meal); + + void delete(int id); + + Meal get(int id); + + Collection getAll(); +} diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index e4f2a2e..fd0374b 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -8,6 +8,7 @@ import java.time.LocalTime; import java.time.Month; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -27,15 +28,15 @@ public class MealsUtil { public static final int DEFAULT_CALORIES_PER_DAY = 2000; - public static List getWithExcess(List meals, int caloriesPerDay) { + public static List getWithExcess(Collection meals, int caloriesPerDay) { return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); } - public static List getFilteredWithExcess(List meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); } - private static List getFilteredWithExcess(List meals, int caloriesPerDay, Predicate filter) { + private static List getFilteredWithExcess(Collection meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -49,6 +50,6 @@ private static List getFilteredWithExcess(List meals, int calories } public static MealTo createWithExcess(Meal meal, boolean excess) { - return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + return new MealTo(meal.getId(), meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index c7d47ea..7571fb1 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -2,21 +2,78 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.InMemoryMealRepositoryImpl; +import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; +import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; public class MealServlet extends HttpServlet { private static final Logger log = LoggerFactory.getLogger(MealServlet.class); + private MealRepository repository; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + repository = new InMemoryMealRepositoryImpl(); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String id = request.getParameter("id"); + + Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.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 { - log.info("getAll"); - request.setAttribute("meals", MealsUtil.getWithExcess(MealsUtil.MEALS, MealsUtil.DEFAULT_CALORIES_PER_DAY)); - request.getRequestDispatcher("/meals.jsp").forward(request, response); + 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.getWithExcess(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/mealForm.jsp b/src/main/webapp/mealForm.jsp new file mode 100644 index 0000000..ddc71d3 --- /dev/null +++ b/src/main/webapp/mealForm.jsp @@ -0,0 +1,51 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Meal + + + +
    +

    Home

    +

    ${param.action == 'create' ? 'Create meal' : 'Edit meal'}

    +
    + +
    + +
    +
    DateTime:
    +
    +
    +
    +
    Description:
    +
    +
    +
    +
    Calories:
    +
    +
    + + +
    +
    + + diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 1c925f8..3b14d97 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -20,6 +20,7 @@

    Home

    Meals

    + Add Meal
    @@ -27,6 +28,8 @@ + + @@ -40,6 +43,8 @@ + +
    Date Description Calories
    ${meal.description} ${meal.calories}UpdateDelete
    From e37eab50226976f3c44aba07b85ded9228ea33b1 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 15 Feb 2019 19:54:34 +0300 Subject: [PATCH 012/125] 2_3_app_layers --- .../topjava/model/AbstractBaseEntity.java | 26 ++++++ .../topjava/model/AbstractNamedEntity.java | 24 +++++ .../ru/javawebinar/topjava/model/Role.java | 6 ++ .../ru/javawebinar/topjava/model/User.java | 91 +++++++++++++++++++ .../topjava/repository/UserRepository.java | 20 ++++ .../topjava/service/MealService.java | 4 + .../topjava/service/MealServiceImpl.java | 9 ++ .../topjava/service/UserService.java | 22 +++++ .../topjava/service/UserServiceImpl.java | 45 +++++++++ .../topjava/util/ValidationUtil.java | 42 +++++++++ .../util/exception/NotFoundException.java | 7 ++ .../javawebinar/topjava/web/SecurityUtil.java | 14 +++ .../topjava/web/meal/MealRestController.java | 8 ++ .../web/user/AbstractUserController.java | 49 ++++++++++ .../topjava/web/user/AdminRestController.java | 38 ++++++++ .../web/user/ProfileRestController.java | 20 ++++ 16 files changed, 425 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/Role.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/User.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/UserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserService.java create mode 100644 src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java new file mode 100644 index 0000000..a3d71fc --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractBaseEntity { + protected Integer id; + + protected AbstractBaseEntity(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 String toString() { + return String.format("Entity %s (%s)", getClass().getName(), id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java new file mode 100644 index 0000000..259511d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -0,0 +1,24 @@ +package ru.javawebinar.topjava.model; + +public abstract class AbstractNamedEntity extends AbstractBaseEntity { + + protected String name; + + protected AbstractNamedEntity(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 String.format("Entity %s (%s, '%s')", getClass().getName(), id, name); + } +} \ No newline at end of file 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 0000000..84d6207 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/Role.java @@ -0,0 +1,6 @@ +package ru.javawebinar.topjava.model; + +public enum Role { + ROLE_USER, + ROLE_ADMIN +} \ No newline at end of file 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 0000000..d88e381 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -0,0 +1,91 @@ +package ru.javawebinar.topjava.model; + +import java.util.Date; +import java.util.EnumSet; +import java.util.Set; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class User extends AbstractNamedEntity { + + private String email; + + private String password; + + private boolean enabled = true; + + private Date registered = new Date(); + + private Set roles; + + private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + + public User(Integer id, String name, String email, String password, Role role, Role... roles) { + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + } + + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + super(id, name); + this.email = email; + this.password = password; + this.caloriesPerDay = caloriesPerDay; + this.enabled = enabled; + this.roles = roles; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getRegistered() { + return registered; + } + + public void setRegistered(Date registered) { + this.registered = registered; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getCaloriesPerDay() { + return caloriesPerDay; + } + + public void setCaloriesPerDay(int caloriesPerDay) { + this.caloriesPerDay = caloriesPerDay; + } + + public boolean isEnabled() { + return enabled; + } + + public Set getRoles() { + return roles; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "User (" + + "id=" + id + + ", email=" + email + + ", name=" + name + + ", enabled=" + enabled + + ", roles=" + roles + + ", caloriesPerDay=" + caloriesPerDay + + ')'; + } +} \ No newline at end of file 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 0000000..c37b84d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.repository; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +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(); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java new file mode 100644 index 0000000..b63fc9b --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -0,0 +1,4 @@ +package ru.javawebinar.topjava.service; + +public interface MealService { +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java new file mode 100644 index 0000000..9017380 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.repository.MealRepository; + +public class MealServiceImpl implements MealService { + + private MealRepository repository; + +} \ No newline at end of file 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 0000000..d0cf33e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -0,0 +1,22 @@ +package ru.javawebinar.topjava.service; + + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +public interface UserService { + + User create(User user); + + void delete(int id) throws NotFoundException; + + User get(int id) throws NotFoundException; + + User getByEmail(String email) throws NotFoundException; + + void update(User user); + + List getAll(); +} \ No newline at end of file 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 0000000..a6740fc --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -0,0 +1,45 @@ +package ru.javawebinar.topjava.service; + +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +public class UserServiceImpl implements UserService { + + private UserRepository repository; + + @Override + public User create(User user) { + return repository.save(user); + } + + @Override + public void delete(int id) throws NotFoundException { + checkNotFoundWithId(repository.delete(id), id); + } + + @Override + public User get(int id) throws NotFoundException { + return checkNotFoundWithId(repository.get(id), id); + } + + @Override + public User getByEmail(String email) throws NotFoundException { + return checkNotFound(repository.getByEmail(email), "email=" + email); + } + + @Override + public List getAll() { + return repository.getAll(); + } + + @Override + public void update(User user) { + checkNotFoundWithId(repository.save(user), user.getId()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java new file mode 100644 index 0000000..f38a2d2 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.util; + + +import ru.javawebinar.topjava.model.AbstractBaseEntity; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +public class ValidationUtil { + + public static T checkNotFoundWithId(T object, int id) { + return checkNotFound(object, "id=" + id); + } + + public static void checkNotFoundWithId(boolean found, int id) { + checkNotFound(found, "id=" + id); + } + + public static T checkNotFound(T object, String msg) { + checkNotFound(object != null, msg); + return object; + } + + public static void checkNotFound(boolean found, String msg) { + if (!found) { + throw new NotFoundException("Not found entity with " + msg); + } + } + + public static void checkNew(AbstractBaseEntity entity) { + if (!entity.isNew()) { + throw new IllegalArgumentException(entity + " must be new (id=null)"); + } + } + + public static void assureIdConsistent(AbstractBaseEntity entity, int id) { +// conservative when you reply, but accept liberally (http://stackoverflow.com/a/32728226/548473) + if (entity.isNew()) { + entity.setId(id); + } else if (entity.getId() != id) { + throw new IllegalArgumentException(entity + " must be with id=" + id); + } + } +} \ No newline at end of file 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 0000000..f1e9b0e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.util.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java new file mode 100644 index 0000000..e78a4b2 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -0,0 +1,14 @@ +package ru.javawebinar.topjava.web; + +import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; + +public class SecurityUtil { + + public static int authUserId() { + return 1; + } + + public static int authUserCaloriesPerDay() { + return DEFAULT_CALORIES_PER_DAY; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java new file mode 100644 index 0000000..ab4e8ea --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.web.meal; + +import ru.javawebinar.topjava.service.MealService; + +public class MealRestController { + private MealService service; + +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java new file mode 100644 index 0000000..16a6825 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -0,0 +1,49 @@ +package ru.javawebinar.topjava.web.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.service.UserService; + +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +public abstract class AbstractUserController { + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private UserService service; + + public List getAll() { + log.info("getAll"); + return service.getAll(); + } + + public User get(int id) { + log.info("get {}", id); + return service.get(id); + } + + public User create(User user) { + log.info("create {}", user); + checkNew(user); + return service.create(user); + } + + public void delete(int id) { + log.info("delete {}", id); + service.delete(id); + } + + public void update(User user, int id) { + log.info("update {} with id={}", user, id); + assureIdConsistent(user, id); + service.update(user); + } + + public User getByMail(String email) { + log.info("getByEmail {}", email); + return service.getByEmail(email); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java new file mode 100644 index 0000000..ae33744 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +public class AdminRestController extends AbstractUserController { + + @Override + public List getAll() { + return super.getAll(); + } + + @Override + public User get(int id) { + return super.get(id); + } + + @Override + public User create(User user) { + return super.create(user); + } + + @Override + public void delete(int id) { + super.delete(id); + } + + @Override + public void update(User user, int id) { + super.update(user, id); + } + + @Override + public User getByMail(String email) { + return super.getByMail(email); + } +} \ No newline at end of file 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 0000000..b5062e2 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.web.user; + +import ru.javawebinar.topjava.model.User; + +import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; + +public class ProfileRestController extends AbstractUserController { + + public User get() { + return super.get(authUserId()); + } + + public void delete() { + super.delete(authUserId()); + } + + public void update(User user) { + super.update(user, authUserId()); + } +} \ No newline at end of file From deb242e367e90d0dd4ff604054176f2092f39dda Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 15 Feb 2019 22:55:17 +0300 Subject: [PATCH 013/125] 2_4_add_spring_context --- pom.xml | 16 ++++--- .../ru/javawebinar/topjava/SpringMain.java | 19 ++++++++ .../InMemoryMealRepositoryImpl.java | 3 +- .../inmemory/InMemoryUserRepositoryImpl.java | 43 +++++++++++++++++++ .../javawebinar/topjava/web/MealServlet.java | 2 +- src/main/resources/spring/spring-app.xml | 7 +++ 6 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/SpringMain.java rename src/main/java/ru/javawebinar/topjava/repository/{ => inmemory}/InMemoryMealRepositoryImpl.java (91%) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java create mode 100644 src/main/resources/spring/spring-app.xml diff --git a/pom.xml b/pom.xml index 591f030..ae4bf71 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ UTF-8 UTF-8 + 5.1.4.RELEASE + 1.2.3 1.7.25 @@ -46,13 +48,6 @@ compile - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - runtime - - ch.qos.logback logback-classic @@ -60,6 +55,13 @@ runtime + + + org.springframework + spring-context + ${spring.version} + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java new file mode 100644 index 0000000..e836531 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -0,0 +1,19 @@ +package ru.javawebinar.topjava; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Arrays; + +public class SpringMain { + public static void main(String[] args) { + ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + +// UserRepository userRepository = (UserRepository) appCtx.getBean("inmemoryUserRepository"); + UserRepository userRepository = appCtx.getBean(UserRepository.class); + userRepository.getAll(); + appCtx.close(); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java similarity index 91% rename from src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java rename to src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 9c63d2a..0f46317 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -1,6 +1,7 @@ -package ru.javawebinar.topjava.repository; +package ru.javawebinar.topjava.repository.inmemory; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; import java.util.Collection; diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java new file mode 100644 index 0000000..78e5726 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -0,0 +1,43 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.Collections; +import java.util.List; + +public class InMemoryUserRepositoryImpl implements UserRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); + + @Override + public boolean delete(int id) { + log.info("delete {}", id); + return true; + } + + @Override + public User save(User user) { + log.info("save {}", user); + return user; + } + + @Override + public User get(int id) { + log.info("get {}", id); + return null; + } + + @Override + public List getAll() { + log.info("getAll"); + return Collections.emptyList(); + } + + @Override + public User getByEmail(String email) { + log.info("getByEmail {}", email); + return null; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 7571fb1..83f98b2 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -3,8 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.inmemory.InMemoryMealRepositoryImpl; import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.ServletConfig; diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml new file mode 100644 index 0000000..5069086 --- /dev/null +++ b/src/main/resources/spring/spring-app.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file From 8861c3df89d1692c4d34fbce621dea36c21f9a49 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 15 Feb 2019 22:56:48 +0300 Subject: [PATCH 014/125] 2_5_dependency_injection --- src/main/java/ru/javawebinar/topjava/SpringMain.java | 7 +++++++ .../ru/javawebinar/topjava/service/UserServiceImpl.java | 4 ++++ src/main/resources/spring/spring-app.xml | 3 +++ 3 files changed, 14 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index e836531..4421737 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -2,7 +2,10 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.service.UserService; import java.util.Arrays; @@ -14,6 +17,10 @@ public static void main(String[] args) { // UserRepository userRepository = (UserRepository) appCtx.getBean("inmemoryUserRepository"); UserRepository userRepository = appCtx.getBean(UserRepository.class); userRepository.getAll(); + + UserService userService = appCtx.getBean(UserService.class); + userService.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); + appCtx.close(); } } diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index a6740fc..ca2fd85 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -13,6 +13,10 @@ public class UserServiceImpl implements UserService { private UserRepository repository; + public void setRepository(UserRepository repository) { + this.repository = repository; + } + @Override public User create(User user) { return repository.save(user); diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 5069086..40547b0 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -4,4 +4,7 @@ + + + \ No newline at end of file From 353ac7558067ee691fcf4bbb7729a053f0e48ecf Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 15 Feb 2019 22:58:48 +0300 Subject: [PATCH 015/125] 2_6_annotation_processing --- .../ru/javawebinar/topjava/SpringMain.java | 20 +++++++------------ .../inmemory/InMemoryUserRepositoryImpl.java | 2 ++ .../topjava/service/UserServiceImpl.java | 8 ++++---- .../web/user/AbstractUserController.java | 2 ++ .../topjava/web/user/AdminRestController.java | 2 ++ .../web/user/ProfileRestController.java | 2 ++ src/main/resources/spring/spring-app.xml | 16 ++++++++++++++- 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index 4421737..f4ba6a7 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -4,23 +4,17 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.UserRepository; -import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.web.user.AdminRestController; import java.util.Arrays; public class SpringMain { public static void main(String[] args) { - ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); - System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); - -// UserRepository userRepository = (UserRepository) appCtx.getBean("inmemoryUserRepository"); - UserRepository userRepository = appCtx.getBean(UserRepository.class); - userRepository.getAll(); - - UserService userService = appCtx.getBean(UserService.class); - userService.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); - - appCtx.close(); + // java 7 automatic resource management + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); + AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); + adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); + } } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java index 78e5726..e1ba9b0 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -2,12 +2,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import java.util.Collections; import java.util.List; +@Repository public class InMemoryUserRepositoryImpl implements UserRepository { private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index ca2fd85..b3acc32 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -9,14 +11,12 @@ import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; +@Service public class UserServiceImpl implements UserService { + @Autowired private UserRepository repository; - public void setRepository(UserRepository repository) { - this.repository = repository; - } - @Override public User create(User user) { return repository.save(user); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index 16a6825..0000f1c 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; @@ -13,6 +14,7 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); + @Autowired private UserService service; public List getAll() { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index ae33744..b37a8ed 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,9 +1,11 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.model.User; import java.util.List; +@Controller public class AdminRestController extends AbstractUserController { @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index b5062e2..7d3702c 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,9 +1,11 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.stereotype.Controller; import ru.javawebinar.topjava.model.User; import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; +@Controller public class ProfileRestController extends AbstractUserController { public User get() { diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 40547b0..f72735e 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -1,10 +1,24 @@ + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + + + + + + + + + + \ No newline at end of file From 00c91cac7a275e0873698a1005156f024f9a0866 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 15 Feb 2019 22:59:47 +0300 Subject: [PATCH 016/125] 2_7_constructor_injection --- .../ru/javawebinar/topjava/service/UserServiceImpl.java | 6 +++++- src/main/resources/spring/spring-app.xml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index b3acc32..c651c41 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -14,8 +14,12 @@ @Service public class UserServiceImpl implements UserService { + private final UserRepository repository; + @Autowired - private UserRepository repository; + public UserServiceImpl(UserRepository repository) { + this.repository = repository; + } @Override public User create(User user) { diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index f72735e..f2b82e6 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -8,7 +8,7 @@ - + --> From 72f7f9d00ea9226b491eb99398c2a95e04aa3988 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 22:46:18 +0300 Subject: [PATCH 017/125] 3_01_HW2_repository --- .../topjava/repository/MealRepository.java | 18 ++++-- .../inmemory/InMemoryMealRepositoryImpl.java | 60 ++++++++++++++----- .../inmemory/InMemoryUserRepositoryImpl.java | 46 ++++++++------ .../topjava/util/DateTimeUtil.java | 5 -- .../javawebinar/topjava/util/MealsUtil.java | 2 +- .../ru/javawebinar/topjava/util/Util.java | 7 +++ .../javawebinar/topjava/web/MealServlet.java | 10 ++-- 7 files changed, 101 insertions(+), 47 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/Util.java diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index e249a88..f0578ff 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -2,14 +2,22 @@ import ru.javawebinar.topjava.model.Meal; -import java.util.Collection; +import java.time.LocalDateTime; +import java.util.List; public interface MealRepository { - Meal save(Meal meal); + // null if updated meal do not belong to userId + Meal save(Meal meal, int userId); - void delete(int id); + // false if meal do not belong to userId + boolean delete(int id, int userId); - Meal get(int id); + // null if meal do not belong to userId + Meal get(int id, int userId); - Collection getAll(); + // ORDERED dateTime desc + List getAll(int userId); + + // ORDERED dateTime desc + List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 0f46317..a001aee 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -1,46 +1,78 @@ package ru.javawebinar.topjava.repository.inmemory; +import org.springframework.util.CollectionUtils; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.util.Util; -import java.util.Collection; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl.ADMIN_ID; +import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl.USER_ID; public class InMemoryMealRepositoryImpl implements MealRepository { - private Map repository = new ConcurrentHashMap<>(); + + // Map userId -> (mealId-> meal) + private Map> usersMealsMap = new ConcurrentHashMap<>(); private AtomicInteger counter = new AtomicInteger(0); { - MealsUtil.MEALS.forEach(this::save); + MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); + + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); + save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); } + @Override - public Meal save(Meal meal) { + public Meal save(Meal meal, int userId) { + Map meals = usersMealsMap.computeIfAbsent(userId, ConcurrentHashMap::new); if (meal.isNew()) { meal.setId(counter.incrementAndGet()); - repository.put(meal.getId(), meal); + meals.put(meal.getId(), meal); return meal; } - // treat case: update, but absent in storage - return repository.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); } @Override - public void delete(int id) { - repository.remove(id); + public boolean delete(int id, int userId) { + Map meals = usersMealsMap.get(userId); + return meals != null && meals.remove(id) != null; } @Override - public Meal get(int id) { - return repository.get(id); + public Meal get(int id, int userId) { + Map meals = usersMealsMap.get(userId); + return meals == null ? null : meals.get(id); } @Override - public Collection getAll() { - return repository.values(); + public List getAll(int userId) { + return getAllFiltered(userId, meal -> true); } -} + @Override + public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return getAllFiltered(userId, meal -> Util.isBetween(meal.getDateTime(), startDateTime, endDateTime)); + } + + private List getAllFiltered(int userId, Predicate filter) { + Map meals = usersMealsMap.get(userId); + return CollectionUtils.isEmpty(meals) ? Collections.emptyList() : + meals.values().stream() + .filter(filter) + .sorted(Comparator.comparing(Meal::getDateTime).reversed()) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java index e1ba9b0..ba108de 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -1,45 +1,57 @@ package ru.javawebinar.topjava.repository.inmemory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; -import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; @Repository public class InMemoryUserRepositoryImpl implements UserRepository { - private static final Logger log = LoggerFactory.getLogger(InMemoryUserRepositoryImpl.class); + + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + private Map usersMap = new ConcurrentHashMap<>(); + private AtomicInteger counter = new AtomicInteger(0); @Override - public boolean delete(int id) { - log.info("delete {}", id); - return true; + public User save(User user) { + if (user.isNew()) { + user.setId(counter.incrementAndGet()); + usersMap.put(user.getId(), user); + return user; + } + return usersMap.computeIfPresent(user.getId(), (id, oldUser) -> user); } @Override - public User save(User user) { - log.info("save {}", user); - return user; + public boolean delete(int id) { + return usersMap.remove(id) != null; } @Override public User get(int id) { - log.info("get {}", id); - return null; + return usersMap.get(id); } @Override public List getAll() { - log.info("getAll"); - return Collections.emptyList(); + return usersMap.values().stream() + .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) + .collect(Collectors.toList()); } @Override public User getByEmail(String email) { - log.info("getByEmail {}", email); - return null; + return usersMap.values().stream() + .filter(u -> email.equals(u.getEmail())) + .findFirst() + .orElse(null); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 5de2884..9db7a80 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,16 +1,11 @@ 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 index fd0374b..220a81a 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -33,7 +33,7 @@ public static List getWithExcess(Collection meals, int caloriesPer } public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { - return getFilteredWithExcess(meals, caloriesPerDay, meal -> DateTimeUtil.isBetween(meal.getTime(), startTime, endTime)); + return getFilteredWithExcess(meals, caloriesPerDay, meal -> Util.isBetween(meal.getTime(), startTime, endTime)); } private static List getFilteredWithExcess(Collection meals, int caloriesPerDay, Predicate filter) { diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java new file mode 100644 index 0000000..7ea8914 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.util; + +public class Util { + public static > boolean isBetween(T value, T start, T end) { + return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 83f98b2..ee006ad 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -39,7 +39,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Integer.parseInt(request.getParameter("calories"))); log.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal); + repository.save(meal, SecurityUtil.authUserId()); response.sendRedirect("meals"); } @@ -50,15 +50,15 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t switch (action == null ? "all" : action) { case "delete": int id = getId(request); - log.info("Delete {}", id); - repository.delete(id); + log.info("delete {}", id); + repository.delete(id, SecurityUtil.authUserId()); 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)); + repository.get(getId(request), SecurityUtil.authUserId()); request.setAttribute("meal", meal); request.getRequestDispatcher("/mealForm.jsp").forward(request, response); break; @@ -66,7 +66,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t default: log.info("getAll"); request.setAttribute("meals", - MealsUtil.getWithExcess(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + MealsUtil.getWithExcess(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } From 6042895c55dd85d198424feb911de383b91f9ac5 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 22:47:12 +0300 Subject: [PATCH 018/125] 3_02_HW2_meal_layers --- .../ru/javawebinar/topjava/SpringMain.java | 14 ++++ .../ru/javawebinar/topjava/model/Meal.java | 18 +---- .../inmemory/InMemoryBaseRepositoryImpl.java | 38 ++++++++++ .../inmemory/InMemoryMealRepositoryImpl.java | 28 +++----- .../inmemory/InMemoryUserRepositoryImpl.java | 36 ++-------- .../topjava/service/MealService.java | 26 +++++++ .../topjava/service/MealServiceImpl.java | 45 +++++++++++- .../topjava/{model => to}/MealTo.java | 2 +- .../topjava/util/DateTimeUtil.java | 18 +++++ .../javawebinar/topjava/util/MealsUtil.java | 5 +- .../ru/javawebinar/topjava/util/Util.java | 6 +- .../topjava/web/meal/MealRestController.java | 70 ++++++++++++++++++- src/main/webapp/meals.jsp | 2 +- 13 files changed, 236 insertions(+), 72 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java rename src/main/java/ru/javawebinar/topjava/{model => to}/MealTo.java (96%) diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/main/java/ru/javawebinar/topjava/SpringMain.java index f4ba6a7..f72561b 100644 --- a/src/main/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/main/java/ru/javawebinar/topjava/SpringMain.java @@ -4,9 +4,15 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.web.meal.MealRestController; import ru.javawebinar.topjava.web.user.AdminRestController; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; import java.util.Arrays; +import java.util.List; public class SpringMain { public static void main(String[] args) { @@ -15,6 +21,14 @@ public static void main(String[] args) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); + System.out.println(); + + MealRestController mealController = appCtx.getBean(MealRestController.class); + List filteredMealsWithExcess = + mealController.getBetween( + LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), + LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); + filteredMealsWithExcess.forEach(System.out::println); } } } diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 3abbee4..9eed15f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -4,9 +4,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; -public class Meal { - private Integer id; - +public class Meal extends AbstractBaseEntity { private final LocalDateTime dateTime; private final String description; @@ -18,20 +16,12 @@ public Meal(LocalDateTime dateTime, String description, int calories) { } public Meal(Integer id, LocalDateTime dateTime, String description, int calories) { - this.id = id; + super(id); this.dateTime = dateTime; this.description = description; this.calories = calories; } - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - public LocalDateTime getDateTime() { return dateTime; } @@ -52,10 +42,6 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } - public boolean isNew() { - return id == null; - } - @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java new file mode 100644 index 0000000..f669c6c --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java @@ -0,0 +1,38 @@ +package ru.javawebinar.topjava.repository.inmemory; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.AbstractBaseEntity; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +@Repository +public class InMemoryBaseRepositoryImpl { + + private static AtomicInteger counter = new AtomicInteger(0); + + private Map entryMap = new ConcurrentHashMap<>(); + + public T save(T entry) { + if (entry.isNew()) { + entry.setId(counter.incrementAndGet()); + entryMap.put(entry.getId(), entry); + return entry; + } + return entryMap.computeIfPresent(entry.getId(), (id, oldT) -> entry); + } + + public boolean delete(int id) { + return entryMap.remove(id) != null; + } + + public T get(int id) { + return entryMap.get(id); + } + + Collection getCollection() { + return entryMap.values(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index a001aee..5a90d17 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -1,6 +1,6 @@ package ru.javawebinar.topjava.repository.inmemory; -import org.springframework.util.CollectionUtils; +import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; @@ -13,18 +13,17 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.stream.Collectors; import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl.ADMIN_ID; import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl.USER_ID; +@Repository public class InMemoryMealRepositoryImpl implements MealRepository { // Map userId -> (mealId-> meal) - private Map> usersMealsMap = new ConcurrentHashMap<>(); - private AtomicInteger counter = new AtomicInteger(0); + private Map> usersMealsMap = new ConcurrentHashMap<>(); { MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); @@ -36,24 +35,19 @@ public class InMemoryMealRepositoryImpl implements MealRepository { @Override public Meal save(Meal meal, int userId) { - Map meals = usersMealsMap.computeIfAbsent(userId, ConcurrentHashMap::new); - if (meal.isNew()) { - meal.setId(counter.incrementAndGet()); - meals.put(meal.getId(), meal); - return meal; - } - return meals.computeIfPresent(meal.getId(), (id, oldMeal) -> meal); + InMemoryBaseRepositoryImpl meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepositoryImpl<>()); + return meals.save(meal); } @Override public boolean delete(int id, int userId) { - Map meals = usersMealsMap.get(userId); - return meals != null && meals.remove(id) != null; + InMemoryBaseRepositoryImpl meals = usersMealsMap.get(userId); + return meals != null && meals.delete(id); } @Override public Meal get(int id, int userId) { - Map meals = usersMealsMap.get(userId); + InMemoryBaseRepositoryImpl meals = usersMealsMap.get(userId); return meals == null ? null : meals.get(id); } @@ -68,9 +62,9 @@ public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateT } private List getAllFiltered(int userId, Predicate filter) { - Map meals = usersMealsMap.get(userId); - return CollectionUtils.isEmpty(meals) ? Collections.emptyList() : - meals.values().stream() + InMemoryBaseRepositoryImpl meals = usersMealsMap.get(userId); + return meals == null ? Collections.emptyList() : + meals.getCollection().stream() .filter(filter) .sorted(Comparator.comparing(Meal::getDateTime).reversed()) .collect(Collectors.toList()); diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java index ba108de..1c5873b 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -6,50 +6,24 @@ import java.util.Comparator; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @Repository -public class InMemoryUserRepositoryImpl implements UserRepository { +public class InMemoryUserRepositoryImpl extends InMemoryBaseRepositoryImpl implements UserRepository { - public static final int USER_ID = 1; - public static final int ADMIN_ID = 2; - - private Map usersMap = new ConcurrentHashMap<>(); - private AtomicInteger counter = new AtomicInteger(0); - - @Override - public User save(User user) { - if (user.isNew()) { - user.setId(counter.incrementAndGet()); - usersMap.put(user.getId(), user); - return user; - } - return usersMap.computeIfPresent(user.getId(), (id, oldUser) -> user); - } - - @Override - public boolean delete(int id) { - return usersMap.remove(id) != null; - } - - @Override - public User get(int id) { - return usersMap.get(id); - } + static final int USER_ID = 1; + static final int ADMIN_ID = 2; @Override public List getAll() { - return usersMap.values().stream() + return getCollection().stream() .sorted(Comparator.comparing(User::getName).thenComparing(User::getEmail)) .collect(Collectors.toList()); } @Override public User getByEmail(String email) { - return usersMap.values().stream() + return getCollection().stream() .filter(u -> email.equals(u.getEmail())) .findFirst() .orElse(null); diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index b63fc9b..94cbaaa 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -1,4 +1,30 @@ package ru.javawebinar.topjava.service; +import org.springframework.lang.Nullable; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.DateTimeUtil.adjustEndDateTime; +import static ru.javawebinar.topjava.util.DateTimeUtil.adjustStartDateTime; + public interface MealService { + Meal get(int id, int userId) throws NotFoundException; + + void delete(int id, int userId) throws NotFoundException; + + default List getBetweenDates(@Nullable LocalDate startDate, @Nullable LocalDate endDate, int userId) { + return getBetweenDateTimes(adjustStartDateTime(startDate), adjustEndDateTime(endDate), userId); + } + + List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId); + + List getAll(int userId); + + void update(Meal meal, int userId) throws NotFoundException; + + Meal create(Meal meal, int userId); } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 9017380..6637187 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -1,9 +1,52 @@ package ru.javawebinar.topjava.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; +import java.time.LocalDateTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; + +@Service public class MealServiceImpl implements MealService { - private MealRepository repository; + private final MealRepository repository; + + @Autowired + public MealServiceImpl(MealRepository repository) { + this.repository = repository; + } + + @Override + public Meal get(int id, int userId) { + return checkNotFoundWithId(repository.get(id, userId), id); + } + + @Override + public void delete(int id, int userId) { + checkNotFoundWithId(repository.delete(id, userId), id); + } + + @Override + public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + return repository.getBetween(startDateTime, endDateTime, userId); + } + + @Override + public List getAll(int userId) { + return repository.getAll(userId); + } + + @Override + public void update(Meal meal, int userId) { + checkNotFoundWithId(repository.save(meal, userId), meal.getId()); + } + @Override + public Meal create(Meal meal, int userId) { + return repository.save(meal, userId); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java similarity index 96% rename from src/main/java/ru/javawebinar/topjava/model/MealTo.java rename to src/main/java/ru/javawebinar/topjava/to/MealTo.java index 3e502d4..eedce0f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -1,4 +1,4 @@ -package ru.javawebinar.topjava.model; +package ru.javawebinar.topjava.to; import java.time.LocalDateTime; diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 9db7a80..4038ae5 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,11 +1,29 @@ package ru.javawebinar.topjava.util; +import java.time.LocalDate; 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"); + // DataBase doesn't support LocalDate.MIN/MAX + private static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); + private static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + + public static LocalDateTime adjustStartDateTime(LocalDate localDate) { + return adjustDateTime(localDate, MIN_DATE, LocalTime.MIN); + } + + public static LocalDateTime adjustEndDateTime(LocalDate localDate) { + return adjustDateTime(localDate, MAX_DATE, LocalTime.MAX); + } + + private static LocalDateTime adjustDateTime(LocalDate localDate, LocalDate defaultDate, LocalTime adjustTime) { + return LocalDateTime.of(localDate != null ? localDate : defaultDate, adjustTime); + } + 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 index 220a81a..c05e38a 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -1,7 +1,8 @@ package ru.javawebinar.topjava.util; +import org.springframework.lang.Nullable; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.model.MealTo; +import ru.javawebinar.topjava.to.MealTo; import java.time.LocalDate; import java.time.LocalDateTime; @@ -32,7 +33,7 @@ public static List getWithExcess(Collection meals, int caloriesPer return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); } - public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + public static List getFilteredWithExcess(Collection meals, int caloriesPerDay, @Nullable LocalTime startTime, @Nullable LocalTime endTime) { return getFilteredWithExcess(meals, caloriesPerDay, meal -> Util.isBetween(meal.getTime(), startTime, endTime)); } diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java index 7ea8914..b0a3aee 100644 --- a/src/main/java/ru/javawebinar/topjava/util/Util.java +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -1,7 +1,9 @@ package ru.javawebinar.topjava.util; +import org.springframework.lang.Nullable; + public class Util { - public static > boolean isBetween(T value, T start, T end) { - return value.compareTo(start) >= 0 && value.compareTo(end) <= 0; + public static > boolean isBetween(T value, @Nullable T start, @Nullable T end) { + return (start == null || value.compareTo(start) >= 0) && (end == null || value.compareTo(end) <= 0); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index ab4e8ea..a3ed418 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,8 +1,76 @@ package ru.javawebinar.topjava.web.meal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.web.SecurityUtil; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +@Controller public class MealRestController { - private MealService service; + private static final Logger log = LoggerFactory.getLogger(MealRestController.class); + + private final MealService service; + + @Autowired + public MealRestController(MealService service) { + this.service = service; + } + + public Meal get(int id) { + int userId = SecurityUtil.authUserId(); + log.info("get meal {} for user {}", id, userId); + return service.get(id, userId); + } + + public void delete(int id) { + int userId = SecurityUtil.authUserId(); + log.info("delete meal {} for user {}", id, userId); + service.delete(id, userId); + } + + public List getAll() { + int userId = SecurityUtil.authUserId(); + log.info("getAll for user {}", userId); + return MealsUtil.getWithExcess(service.getAll(userId), SecurityUtil.authUserCaloriesPerDay()); + } + + public Meal create(Meal meal) { + int userId = SecurityUtil.authUserId(); + checkNew(meal); + log.info("create {} for user {}", meal, userId); + return service.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = SecurityUtil.authUserId(); + assureIdConsistent(meal, id); + log.info("update {} for user {}", meal, userId); + service.update(meal, userId); + } + + /** + *
      Filter separately + *
    1. by date
    2. + *
    3. by time for every date
    4. + *
    + */ + public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { + int userId = SecurityUtil.authUserId(); + log.info("getBetween dates({} - {}) time({} - {}) for user {}", startDate, endDate, startTime, endTime, userId); + List mealsDateFiltered = service.getBetweenDates(startDate, endDate, userId); + return MealsUtil.getFilteredWithExcess(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), startTime, endTime); + } } \ No newline at end of file diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 3b14d97..cdac2f0 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -33,7 +33,7 @@ - + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> From 5bf7dca7c275ce029bfb1e915a0a505291a496ac Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 22:47:39 +0300 Subject: [PATCH 019/125] 3_03_HW2_optional_MealServlet --- .../javawebinar/topjava/web/MealServlet.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index ee006ad..cf94cc3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,11 +1,10 @@ package ru.javawebinar.topjava.web; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.util.StringUtils; import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.repository.inmemory.InMemoryMealRepositoryImpl; -import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.web.meal.MealRestController; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -18,28 +17,36 @@ import java.util.Objects; public class MealServlet extends HttpServlet { - private static final Logger log = LoggerFactory.getLogger(MealServlet.class); - private MealRepository repository; + private ConfigurableApplicationContext springContext; + private MealRestController mealController; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - repository = new InMemoryMealRepositoryImpl(); + springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + mealController = springContext.getBean(MealRestController.class); + } + + @Override + public void destroy() { + springContext.close(); + super.destroy(); } @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), + Meal meal = new Meal( LocalDateTime.parse(request.getParameter("dateTime")), request.getParameter("description"), Integer.parseInt(request.getParameter("calories"))); - log.info(meal.isNew() ? "Create {}" : "Update {}", meal); - repository.save(meal, SecurityUtil.authUserId()); + if (StringUtils.isEmpty(request.getParameter("id"))) { + mealController.create(meal); + } else { + mealController.update(meal, getId(request)); + } response.sendRedirect("meals"); } @@ -50,23 +57,20 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t switch (action == null ? "all" : action) { case "delete": int id = getId(request); - log.info("delete {}", id); - repository.delete(id, SecurityUtil.authUserId()); + mealController.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), SecurityUtil.authUserId()); + mealController.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.getWithExcess(repository.getAll(SecurityUtil.authUserId()), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.setAttribute("meals", mealController.getAll()); request.getRequestDispatcher("/meals.jsp").forward(request, response); break; } From f934c7d23901f84dd7660932897309f790cbadd6 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 22:48:20 +0300 Subject: [PATCH 020/125] 3_04_HW2_optional_filter --- .../topjava/util/DateTimeUtil.java | 11 ++++++ .../javawebinar/topjava/web/MealServlet.java | 34 ++++++++++++++----- src/main/webapp/css/style.css | 24 +++++++++++++ src/main/webapp/mealForm.jsp | 19 +---------- src/main/webapp/meals.jsp | 33 ++++++++++++------ 5 files changed, 83 insertions(+), 38 deletions(-) create mode 100644 src/main/webapp/css/style.css diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 4038ae5..4bf62ad 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -1,5 +1,8 @@ package ru.javawebinar.topjava.util; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -27,4 +30,12 @@ private static LocalDateTime adjustDateTime(LocalDate localDate, LocalDate defau public static String toString(LocalDateTime ldt) { return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); } + + public static LocalDate parseLocalDate(@Nullable String str) { + return StringUtils.isEmpty(str) ? null : LocalDate.parse(str); + } + + public static LocalTime parseLocalTime(@Nullable String str) { + return StringUtils.isEmpty(str) ? null : LocalTime.parse(str); + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index cf94cc3..bd41c9a 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -12,10 +12,15 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.Objects; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; + public class MealServlet extends HttpServlet { private ConfigurableApplicationContext springContext; @@ -37,17 +42,28 @@ public void destroy() { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); - Meal meal = new Meal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.parseInt(request.getParameter("calories"))); + String action = request.getParameter("action"); + if (action == null) { + Meal meal = new Meal( + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); + + if (StringUtils.isEmpty(request.getParameter("id"))) { + mealController.create(meal); + } else { + mealController.update(meal, getId(request)); + } + response.sendRedirect("meals"); - if (StringUtils.isEmpty(request.getParameter("id"))) { - mealController.create(meal); - } else { - mealController.update(meal, getId(request)); + } else if ("filter".equals(action)) { + LocalDate startDate = parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = parseLocalTime(request.getParameter("endTime")); + request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); } - response.sendRedirect("meals"); } @Override diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css new file mode 100644 index 0000000..26a14ce --- /dev/null +++ b/src/main/webapp/css/style.css @@ -0,0 +1,24 @@ +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; +} + +tr[data-mealExcess="false"] { + color: green; +} + +tr[data-mealExcess="true"] { + color: red; +} diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/mealForm.jsp index ddc71d3..d4509bb 100644 --- a/src/main/webapp/mealForm.jsp +++ b/src/main/webapp/mealForm.jsp @@ -4,24 +4,7 @@ Meal - +
    diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index cdac2f0..00f53cd 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -2,24 +2,35 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> -<%--<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>--%> Meal list - +

    Home

    Meals

    +
    +
    +
    From Date:
    +
    +
    +
    +
    To Date:
    +
    +
    +
    +
    From Time:
    +
    +
    +
    +
    To Time:
    +
    +
    + +
    +
    Add Meal
    @@ -34,7 +45,7 @@ - + + + + + + + + + + +
    <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> From a192489a436edb6aa53a03332f06d0c087bdd30e Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 22:48:50 +0300 Subject: [PATCH 021/125] 3_05_HW2_optional_select_user --- .../ru/javawebinar/topjava/web/SecurityUtil.java | 8 +++++++- .../java/ru/javawebinar/topjava/web/UserServlet.java | 7 +++++++ src/main/webapp/index.html | 12 ++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index e78a4b2..b9639bf 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -4,8 +4,14 @@ public class SecurityUtil { + private static int id = 1; + public static int authUserId() { - return 1; + return id; + } + + public static void setAuthUserId(int id) { + SecurityUtil.id = id; } public static int authUserCaloriesPerDay() { diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index f6cf12e..2260234 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -13,6 +13,13 @@ public class UserServlet extends HttpServlet { private static final Logger log = getLogger(UserServlet.class); + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + int userId = Integer.parseInt(request.getParameter("userId")); + SecurityUtil.setAuthUserId(userId); + response.sendRedirect("meals"); + } + @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { log.debug("forward to users"); diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index cd88b33..e68aaa1 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -6,9 +6,13 @@

    Проект Java Enterprise (Topjava)


    - +
    + Meals of  + + +
    From 35ab92b3957ff36be4e63f811aa17dbe827f1db8 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 22:49:14 +0300 Subject: [PATCH 022/125] 3_06_bean_life_cycle --- .../inmemory/InMemoryMealRepositoryImpl.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 5a90d17..994b879 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -1,11 +1,15 @@ package ru.javawebinar.topjava.repository.inmemory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.MealsUtil; import ru.javawebinar.topjava.util.Util; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.time.LocalDateTime; import java.time.Month; import java.util.Collections; @@ -21,6 +25,7 @@ @Repository public class InMemoryMealRepositoryImpl implements MealRepository { + private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepositoryImpl.class); // Map userId -> (mealId-> meal) private Map> usersMealsMap = new ConcurrentHashMap<>(); @@ -39,6 +44,16 @@ public Meal save(Meal meal, int userId) { return meals.save(meal); } + @PostConstruct + public void postConstruct() { + log.info("+++ PostConstruct"); + } + + @PreDestroy + public void preDestroy() { + log.info("+++ PreDestroy"); + } + @Override public boolean delete(int id, int userId) { InMemoryBaseRepositoryImpl meals = usersMealsMap.get(userId); From 34e4cef04a069e709ee8a8544cd120b998e955a6 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 23:00:34 +0300 Subject: [PATCH 023/125] 3_07_add_junit --- pom.xml | 19 +++++++ .../ru/javawebinar/topjava/SpringMain.java | 0 .../ru/javawebinar/topjava/UserTestData.java | 12 +++++ .../inmemory/InMemoryBaseRepositoryImpl.java | 2 +- .../inmemory/InMemoryMealRepositoryImpl.java | 4 +- .../inmemory/InMemoryUserRepositoryImpl.java | 11 +++- .../user/InMemoryAdminRestControllerTest.java | 52 +++++++++++++++++++ 7 files changed, 95 insertions(+), 5 deletions(-) rename src/{main => test}/java/ru/javawebinar/topjava/SpringMain.java (100%) create mode 100644 src/test/java/ru/javawebinar/topjava/UserTestData.java rename src/{main => test}/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java (93%) rename src/{main => test}/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java (93%) rename src/{main => test}/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java (73%) create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index ae4bf71..d71ef0b 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ 1.2.3 1.7.25 + + + 4.12 @@ -36,6 +39,14 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + -Dfile.encoding=UTF-8 + + @@ -75,6 +86,14 @@ jstl 1.2 + + + + junit + junit + ${junit.version} + test + diff --git a/src/main/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java similarity index 100% rename from src/main/java/ru/javawebinar/topjava/SpringMain.java rename to src/test/java/ru/javawebinar/topjava/SpringMain.java diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java new file mode 100644 index 0000000..196b093 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -0,0 +1,12 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; + +public class UserTestData { + public static final int USER_ID = 1; + public static final int ADMIN_ID = 2; + + public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); + public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java similarity index 93% rename from src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java index f669c6c..c20a460 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java @@ -13,7 +13,7 @@ public class InMemoryBaseRepositoryImpl { private static AtomicInteger counter = new AtomicInteger(0); - private Map entryMap = new ConcurrentHashMap<>(); + Map entryMap = new ConcurrentHashMap<>(); public T save(T entry) { if (entry.isNew()) { diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java similarity index 93% rename from src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 994b879..d696bd2 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -20,8 +20,8 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl.ADMIN_ID; -import static ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl.USER_ID; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.USER_ID; @Repository public class InMemoryMealRepositoryImpl implements MealRepository { diff --git a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java similarity index 73% rename from src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java rename to src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java index 1c5873b..e2e9c17 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.repository.inmemory; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; @@ -8,11 +9,17 @@ import java.util.List; import java.util.stream.Collectors; +import static ru.javawebinar.topjava.UserTestData.ADMIN; +import static ru.javawebinar.topjava.UserTestData.USER; + @Repository public class InMemoryUserRepositoryImpl extends InMemoryBaseRepositoryImpl implements UserRepository { - static final int USER_ID = 1; - static final int ADMIN_ID = 2; + public void init() { + entryMap.clear(); + entryMap.put(UserTestData.USER_ID, USER); + entryMap.put(UserTestData.ADMIN_ID, ADMIN); + } @Override public List getAll() { diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java new file mode 100644 index 0000000..35e6626 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -0,0 +1,52 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.*; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl; +import ru.javawebinar.topjava.util.exception.NotFoundException; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Arrays; +import java.util.Collection; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; + +public class InMemoryAdminRestControllerTest { + private static ConfigurableApplicationContext appCtx; + private static AdminRestController controller; + + @BeforeClass + public static void beforeClass() { + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); + controller = appCtx.getBean(AdminRestController.class); + } + + @AfterClass + public static void afterClass() { + appCtx.close(); + } + + @Before + public void setUp() throws Exception { + // re-initialize + InMemoryUserRepositoryImpl repository = appCtx.getBean(InMemoryUserRepositoryImpl.class); + repository.init(); + } + + @Test + public void delete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void deleteNotFound() throws Exception { + controller.delete(10); + } +} \ No newline at end of file From 61b11ce6d57044d57ae87bffcf9888ea2efee40d Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 23:02:09 +0300 Subject: [PATCH 024/125] 3_08_add_spring_test --- pom.xml | 6 +++ ...InMemoryAdminRestControllerSpringTest.java | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java diff --git a/pom.xml b/pom.xml index d71ef0b..abfd0a7 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,12 @@ ${junit.version} test + + org.springframework + spring-test + ${spring.version} + test + diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java new file mode 100644 index 0000000..49b48ef --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl; +import ru.javawebinar.topjava.util.exception.NotFoundException; +import ru.javawebinar.topjava.web.user.AdminRestController; + +import java.util.Collection; + +import static ru.javawebinar.topjava.UserTestData.ADMIN; + +@ContextConfiguration("classpath:spring/spring-app.xml") +@RunWith(SpringRunner.class) +public class InMemoryAdminRestControllerSpringTest { + + @Autowired + private AdminRestController controller; + + @Autowired + private InMemoryUserRepositoryImpl repository; + + @Before + public void setUp() throws Exception { + repository.init(); + } + + @Test + public void delete() throws Exception { + controller.delete(UserTestData.USER_ID); + Collection users = controller.getAll(); + Assert.assertEquals(users.size(), 1); + Assert.assertEquals(users.iterator().next(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void deleteNotFound() throws Exception { + controller.delete(10); + } +} From dd2463aa26f4d705e49e317fdb4bfee17bd7e3bb Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 23:04:49 +0300 Subject: [PATCH 025/125] 3_09_add_postgresql --- pom.xml | 9 +++++++++ src/main/resources/db/postgres.properties | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 src/main/resources/db/postgres.properties diff --git a/pom.xml b/pom.xml index abfd0a7..cae60ef 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,8 @@ 1.2.3 1.7.25 + + 42.2.5 4.12 @@ -73,6 +75,13 @@ ${spring.version} + + + org.postgresql + postgresql + ${postgresql.version} + + javax.servlet diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties new file mode 100644 index 0000000..fd8fe56 --- /dev/null +++ b/src/main/resources/db/postgres.properties @@ -0,0 +1,7 @@ +#database.url=jdbc:postgresql://ec2-54-247-74-197.eu-west-1.compute.amazonaws.com:5432/de4fjsqhdvl7ld?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory +#database.username=anbxkjtzukqacj +#database.password=da1f25b2a38784fb0d46858e5b8fc168e08c9e1e9c72faea5bbac9c0e1f9c24f + +database.url=jdbc:postgresql://localhost:5432/topjava +database.username=user +database.password=password From f02da142da823e54c22a5c690f45f01fe563f703 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 23:12:38 +0300 Subject: [PATCH 026/125] 3_10_db_implementation --- pom.xml | 5 ++ .../jdbc/JdbcUserRepositoryImpl.java | 81 +++++++++++++++++++ src/main/resources/db/initDB.sql | 25 ++++++ src/main/resources/db/populateDB.sql | 11 +++ src/main/resources/spring/spring-db.xml | 25 ++++++ 5 files changed, 147 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java create mode 100644 src/main/resources/db/initDB.sql create mode 100644 src/main/resources/db/populateDB.sql create mode 100644 src/main/resources/spring/spring-db.xml diff --git a/pom.xml b/pom.xml index cae60ef..4ae6f18 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,11 @@ spring-context ${spring.version} + + org.springframework + spring-jdbc + ${spring.version} + 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 0000000..47b5056 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -0,0 +1,81 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.support.DataAccessUtils; +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 ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import java.util.List; + +@Repository +public class JdbcUserRepositoryImpl implements UserRepository { + + private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); + + private final JdbcTemplate jdbcTemplate; + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private final SimpleJdbcInsert insertUser; + + @Autowired + public JdbcUserRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertUser = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("users") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + @Override + 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()); + } else if (namedParameterJdbcTemplate.update( + "UPDATE users SET name=:name, email=:email, password=:password, " + + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map) == 0) { + return null; + } + return user; + } + + @Override + 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 DataAccessUtils.singleResult(users); + } + + @Override + public User getByEmail(String email) { +// return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); + return DataAccessUtils.singleResult(users); + } + + @Override + public List getAll() { + return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + } +} diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql new file mode 100644 index 0000000..dd49ab6 --- /dev/null +++ b/src/main/resources/db/initDB.sql @@ -0,0 +1,25 @@ +DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS users; +DROP SEQUENCE IF EXISTS global_seq; + +CREATE SEQUENCE global_seq START WITH 100000; + +CREATE TABLE users +( + id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), + name VARCHAR NOT NULL, + email VARCHAR NOT NULL, + password VARCHAR NOT NULL, + registered TIMESTAMP DEFAULT now() NOT NULL, + enabled BOOL DEFAULT TRUE NOT NULL, + calories_per_day INTEGER DEFAULT 2000 NOT NULL +); +CREATE UNIQUE INDEX users_unique_email_idx ON users (email); + +CREATE TABLE user_roles +( + user_id INTEGER NOT NULL, + role VARCHAR, + CONSTRAINT user_roles_idx UNIQUE (user_id, role), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql new file mode 100644 index 0000000..ccf86e0 --- /dev/null +++ b/src/main/resources/db/populateDB.sql @@ -0,0 +1,11 @@ +DELETE FROM user_roles; +DELETE FROM users; +ALTER SEQUENCE global_seq RESTART WITH 100000; + +INSERT INTO users (name, email, password) VALUES + ('User', 'user@yandex.ru', 'password'), + ('Admin', 'admin@gmail.com', 'admin'); + +INSERT INTO user_roles (role, user_id) VALUES + ('ROLE_USER', 100000), + ('ROLE_ADMIN', 100001); diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml new file mode 100644 index 0000000..8ec56c9 --- /dev/null +++ b/src/main/resources/spring/spring-db.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 4020991ce6d9bf4a88a66f2bf5b0e503d7b119b8 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 23:16:48 +0300 Subject: [PATCH 027/125] 3_11_test_UserService --- pom.xml | 6 ++ .../topjava/model/AbstractBaseEntity.java | 22 +++++ .../topjava/model/AbstractNamedEntity.java | 3 + .../ru/javawebinar/topjava/model/User.java | 25 ++++-- .../jdbc/JdbcMealRepositoryImpl.java | 37 ++++++++ src/main/resources/spring/spring-app.xml | 2 +- .../ru/javawebinar/topjava/UserTestData.java | 21 ++++- .../topjava/service/UserServiceTest.java | 87 +++++++++++++++++++ 8 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java diff --git a/pom.xml b/pom.xml index 4ae6f18..d07327c 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,12 @@ ${spring.version} test + + org.assertj + assertj-core + 3.12.0 + test + diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index a3d71fc..6ab3359 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,8 +1,13 @@ package ru.javawebinar.topjava.model; public abstract class AbstractBaseEntity { + public static final int START_SEQ = 100000; + protected Integer id; + public AbstractBaseEntity() { + } + protected AbstractBaseEntity(Integer id) { this.id = id; } @@ -23,4 +28,21 @@ public boolean isNew() { public String toString() { return String.format("Entity %s (%s)", getClass().getName(), id); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractBaseEntity that = (AbstractBaseEntity) o; + return id != null && id.equals(that.id); + } + + @Override + public int hashCode() { + return id == null ? 0 : id; + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index 259511d..0e07e37 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -4,6 +4,9 @@ public abstract class AbstractNamedEntity extends AbstractBaseEntity { protected String name; + public AbstractNamedEntity() { + } + protected AbstractNamedEntity(Integer id, String name) { super(id); this.name = name; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index d88e381..446d86f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,8 @@ package ru.javawebinar.topjava.model; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; import java.util.Date; import java.util.EnumSet; import java.util.Set; @@ -20,17 +23,25 @@ public class User extends AbstractNamedEntity { private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + public User() { + } + + public User(User u) { + this(u.getId(), u.getName(), u.getEmail(), u.getPassword(), u.getCaloriesPerDay(), u.isEnabled(), u.getRegistered(), u.getRoles()); + } + public User(Integer id, String name, String email, String password, Role role, Role... roles) { - this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, EnumSet.of(role, roles)); + this(id, name, email, password, DEFAULT_CALORIES_PER_DAY, true, new Date(), EnumSet.of(role, roles)); } - public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Set roles) { + public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Date registered, Collection roles) { super(id, name); this.email = email; this.password = password; this.caloriesPerDay = caloriesPerDay; this.enabled = enabled; - this.roles = roles; + this.registered = registered; + setRoles(roles); } public String getEmail() { @@ -77,15 +88,19 @@ public String getPassword() { return password; } + public void setRoles(Collection roles) { + this.roles = CollectionUtils.isEmpty(roles) ? EnumSet.noneOf(Role.class) : EnumSet.copyOf(roles); + } + @Override public String toString() { - return "User (" + + return "User{" + "id=" + id + ", email=" + email + ", name=" + name + ", enabled=" + enabled + ", roles=" + roles + ", caloriesPerDay=" + caloriesPerDay + - ')'; + '}'; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java new file mode 100644 index 0000000..3e48b40 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -0,0 +1,37 @@ +package ru.javawebinar.topjava.repository.jdbc; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class JdbcMealRepositoryImpl implements MealRepository { + + @Override + public Meal save(Meal meal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public Meal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index f2b82e6..fc3b4d1 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -15,7 +15,7 @@ - + diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 196b093..3cc466c 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -3,10 +3,27 @@ import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + public class UserTestData { - public static final int USER_ID = 1; - public static final int ADMIN_ID = 2; + public static final int USER_ID = START_SEQ; + public static final int ADMIN_ID = START_SEQ + 1; public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); + + public static void assertMatch(User actual, User expected) { + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles"); + } + + public static void assertMatch(Iterable actual, User... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); + } } diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java new file mode 100644 index 0000000..dc9ad49 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -0,0 +1,87 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static ru.javawebinar.topjava.UserTestData.*; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +public class UserServiceTest { + + @Autowired + private UserService service; + + @Test + public void create() throws Exception { + User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.ROLE_USER)); + User created = service.create(newUser); + newUser.setId(created.getId()); + assertMatch(service.getAll(), ADMIN, newUser, USER); + } + + @Test(expected = DataAccessException.class) + public void duplicateMailCreate() throws Exception { + service.create(new User(null, "Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER)); + } + + @Test + public void delete() throws Exception { + service.delete(USER_ID); + assertMatch(service.getAll(), ADMIN); + } + + @Test(expected = NotFoundException.class) + public void deletedNotFound() throws Exception { + service.delete(1); + } + + @Test + public void get() throws Exception { + User user = service.get(USER_ID); + assertMatch(user, USER); + } + + @Test(expected = NotFoundException.class) + public void getNotFound() throws Exception { + service.get(1); + } + + @Test + public void getByEmail() throws Exception { + User user = service.getByEmail("user@yandex.ru"); + assertMatch(user, USER); + } + + @Test + public void update() throws Exception { + User updated = new User(USER); + updated.setName("UpdatedName"); + updated.setCaloriesPerDay(330); + service.update(updated); + assertMatch(service.get(USER_ID), updated); + } + + @Test + public void getAll() throws Exception { + List all = service.getAll(); + assertMatch(all, ADMIN, USER); + } +} \ No newline at end of file From 87f1966c1ec815eee5b8eaf1a6975ae2ec8d040a Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 23:18:21 +0300 Subject: [PATCH 028/125] 3_12_test_logging --- pom.xml | 7 ++++++ src/main/resources/logback.xml | 4 ++-- .../topjava/service/UserServiceTest.java | 7 ++++++ src/test/resources/logback-test.xml | 22 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/logback-test.xml diff --git a/pom.xml b/pom.xml index d07327c..8129dbd 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,13 @@ compile + + org.slf4j + jul-to-slf4j + ${slf4j.version} + runtime + + ch.qos.logback logback-classic diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index e9b900b..c7bffc3 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -9,14 +9,14 @@ UTF-8 - %date %-5level %logger{0} [%file:%line] %msg%n + %date %-5level %logger{50}.%M:%L - %msg%n UTF-8 - %-5level %logger{0} [%file:%line] %msg%n + %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index dc9ad49..03bfc5f 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -2,6 +2,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ContextConfiguration; @@ -26,6 +27,12 @@ @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class UserServiceTest { + static { + // Only for postgres driver logging + // It uses java.util.logging and logged via jul-to-slf4j bridge + SLF4JBridgeHandler.install(); + } + @Autowired private UserService service; diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..428ceca --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + true + + + + + UTF-8 + %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + + + + + + + + + + + + \ No newline at end of file From 38c739edac056639e2a2ea51445ae7204dd4ede9 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 21 Feb 2019 23:19:05 +0300 Subject: [PATCH 029/125] 3_13_fix_servlet --- src/main/java/ru/javawebinar/topjava/web/MealServlet.java | 2 +- src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java | 4 +++- src/main/webapp/index.html | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index bd41c9a..9b44538 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -29,7 +29,7 @@ public class MealServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { super.init(config); - springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-db.xml"); mealController = springContext.getBean(MealRestController.class); } diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index b9639bf..5882175 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -1,10 +1,12 @@ package ru.javawebinar.topjava.web; +import ru.javawebinar.topjava.model.AbstractBaseEntity; + import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; public class SecurityUtil { - private static int id = 1; + private static int id = AbstractBaseEntity.START_SEQ; public static int authUserId() { return id; diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index e68aaa1..8864497 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -9,8 +9,8 @@

    Проект Meals of  From 7549e78e649afb0954b683de929453f42a64b57f Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Feb 2019 21:18:03 +0300 Subject: [PATCH 030/125] 4_1_HW3 --- .../ru/javawebinar/topjava/model/Meal.java | 21 ++++++- .../jdbc/JdbcMealRepositoryImpl.java | 60 +++++++++++++++++-- .../jdbc/JdbcUserRepositoryImpl.java | 15 ++--- src/main/resources/db/initDB.sql | 14 ++++- src/main/resources/db/populateDB.sql | 11 ++++ 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 9eed15f..1c1e67b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -5,11 +5,14 @@ import java.time.LocalTime; public class Meal extends AbstractBaseEntity { - private final LocalDateTime dateTime; + private LocalDateTime dateTime; - private final String description; + private String description; - private final int calories; + private int calories; + + public Meal() { + } public Meal(LocalDateTime dateTime, String description, int calories) { this(null, dateTime, description, calories); @@ -42,6 +45,18 @@ public LocalTime getTime() { return dateTime.toLocalTime(); } + public void setDateTime(LocalDateTime dateTime) { + this.dateTime = dateTime; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setCalories(int calories) { + this.calories = calories; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index 3e48b40..80bbdb5 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -1,5 +1,13 @@ package ru.javawebinar.topjava.repository.jdbc; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -10,28 +18,70 @@ @Repository public class JdbcMealRepositoryImpl implements MealRepository { + private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); + + private final JdbcTemplate jdbcTemplate; + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + private final SimpleJdbcInsert insertMeal; + + @Autowired + public JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("meals") + .usingGeneratedKeyColumns("id"); + + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + @Override public Meal save(Meal meal, int userId) { - return null; + MapSqlParameterSource map = new MapSqlParameterSource() + .addValue("id", meal.getId()) + .addValue("description", meal.getDescription()) + .addValue("calories", meal.getCalories()) + .addValue("date_time", meal.getDateTime()) + .addValue("user_id", userId); + + if (meal.isNew()) { + Number newId = insertMeal.executeAndReturnKey(map); + meal.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 meal; } @Override public boolean delete(int id, int userId) { - return false; + return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; } @Override public Meal get(int id, int userId) { - return null; + List meals = jdbcTemplate.query( + "SELECT * FROM meals WHERE id = ? AND user_id = ?", ROW_MAPPER, id, userId); + return DataAccessUtils.singleResult(meals); } @Override public List getAll(int userId) { - return null; + 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 null; + return jdbcTemplate.query( + "SELECT * FROM meals WHERE user_id=? AND date_time BETWEEN ? AND ? ORDER BY date_time DESC", + ROW_MAPPER, userId, startDate, endDate); } } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java index 47b5056..fde6581 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -4,7 +4,7 @@ import org.springframework.dao.support.DataAccessUtils; 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.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; @@ -36,21 +36,14 @@ public JdbcUserRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTempl @Override 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()); + BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); if (user.isNew()) { - Number newKey = insertUser.executeAndReturnKey(map); + Number newKey = insertUser.executeAndReturnKey(parameterSource); user.setId(newKey.intValue()); } else if (namedParameterJdbcTemplate.update( "UPDATE users SET name=:name, email=:email, password=:password, " + - "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", map) == 0) { + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { return null; } return user; diff --git a/src/main/resources/db/initDB.sql b/src/main/resources/db/initDB.sql index dd49ab6..f39e3d5 100644 --- a/src/main/resources/db/initDB.sql +++ b/src/main/resources/db/initDB.sql @@ -1,4 +1,5 @@ DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS meals; DROP TABLE IF EXISTS users; DROP SEQUENCE IF EXISTS global_seq; @@ -22,4 +23,15 @@ CREATE TABLE user_roles role VARCHAR, CONSTRAINT user_roles_idx UNIQUE (user_id, role), FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE -); \ No newline at end of file +); + +CREATE TABLE meals ( + id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'), + user_id INTEGER NOT NULL, + date_time TIMESTAMP NOT NULL, + description TEXT NOT NULL, + calories INT NOT NULL, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx + ON meals (user_id, date_time); \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index ccf86e0..29a97ef 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -1,4 +1,5 @@ DELETE FROM user_roles; +DELETE FROM meals; DELETE FROM users; ALTER SEQUENCE global_seq RESTART WITH 100000; @@ -9,3 +10,13 @@ INSERT INTO users (name, email, password) VALUES INSERT INTO user_roles (role, user_id) VALUES ('ROLE_USER', 100000), ('ROLE_ADMIN', 100001); + +INSERT INTO meals (date_time, description, calories, user_id) +VALUES ('2015-05-30 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-30 13:00:00', 'Обед', 1000, 100000), + ('2015-05-30 20:00:00', 'Ужин', 500, 100000), + ('2015-05-31 10:00:00', 'Завтрак', 500, 100000), + ('2015-05-31 13:00:00', 'Обед', 1000, 100000), + ('2015-05-31 20:00:00', 'Ужин', 510, 100000), + ('2015-06-01 14:00:00', 'Админ ланч', 510, 100001), + ('2015-06-01 21:00:00', 'Админ ужин', 1500, 100001); From 3e8f8fa5e818ef2722e32b22429ac3488348f428 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Feb 2019 21:19:01 +0300 Subject: [PATCH 031/125] 4_2_HW3_optional --- .../javawebinar/topjava/util/MealsUtil.java | 11 --- .../ru/javawebinar/topjava/MealTestData.java | 47 ++++++++++ .../inmemory/InMemoryMealRepositoryImpl.java | 13 --- .../topjava/service/MealServiceTest.java | 90 +++++++++++++++++++ .../topjava/service/UserServiceTest.java | 1 + 5 files changed, 138 insertions(+), 24 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/MealTestData.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c05e38a..e78a08c 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -5,10 +5,7 @@ import ru.javawebinar.topjava.to.MealTo; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.Month; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -18,14 +15,6 @@ import static java.util.stream.Collectors.toList; public class MealsUtil { - public static final List MEALS = Arrays.asList( - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 13, 0), "Обед", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 30, 20, 0), "Ужин", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 10, 0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 13, 0), "Обед", 500), - new Meal(LocalDateTime.of(2015, Month.MAY, 31, 20, 0), "Ужин", 510) - ); public static final int DEFAULT_CALORIES_PER_DAY = 2000; 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 0000000..d94faeb --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava; + +import ru.javawebinar.topjava.model.Meal; + +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +import static java.time.LocalDateTime.of; +import static org.assertj.core.api.Assertions.assertThat; +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 MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + + public static Meal getCreated() { + return new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Созданный ужин", 300); + } + + public static Meal getUpdated() { + return new Meal(MEAL1_ID, MEAL1.getDateTime(), "Обновленный завтрак", 200); + } + + public static void assertMatch(Meal actual, Meal expected) { + assertThat(actual).isEqualToComparingFieldByField(expected); + } + + public static void assertMatch(Iterable actual, Meal... expected) { + assertMatch(actual, Arrays.asList(expected)); + } + + public static void assertMatch(Iterable actual, Iterable expected) { + assertThat(actual).usingFieldByFieldElementComparator().isEqualTo(expected); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index d696bd2..7849c58 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -5,13 +5,11 @@ import org.springframework.stereotype.Repository; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; -import ru.javawebinar.topjava.util.MealsUtil; import ru.javawebinar.topjava.util.Util; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDateTime; -import java.time.Month; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -20,9 +18,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; -import static ru.javawebinar.topjava.UserTestData.USER_ID; - @Repository public class InMemoryMealRepositoryImpl implements MealRepository { private static final Logger log = LoggerFactory.getLogger(InMemoryMealRepositoryImpl.class); @@ -30,14 +25,6 @@ public class InMemoryMealRepositoryImpl implements MealRepository { // Map userId -> (mealId-> meal) private Map> usersMealsMap = new ConcurrentHashMap<>(); - { - MealsUtil.MEALS.forEach(meal -> save(meal, USER_ID)); - - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510), ADMIN_ID); - save(new Meal(LocalDateTime.of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500), ADMIN_ID); - } - - @Override public Meal save(Meal meal, int userId) { InMemoryBaseRepositoryImpl meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepositoryImpl<>()); diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java new file mode 100644 index 0000000..d2d8794 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -0,0 +1,90 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import java.time.LocalDate; +import java.time.Month; + +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.USER_ID; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +public class MealServiceTest { + + static { + SLF4JBridgeHandler.install(); + } + + @Autowired + private MealService service; + + @Test + public void delete() throws Exception { + service.delete(MEAL1_ID, USER_ID); + assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); + } + + @Test(expected = NotFoundException.class) + public void deleteNotFound() throws Exception { + service.delete(MEAL1_ID, 1); + } + + @Test + public void create() throws Exception { + Meal newMeal = getCreated(); + Meal created = service.create(newMeal, USER_ID); + newMeal.setId(created.getId()); + assertMatch(newMeal, created); + assertMatch(service.getAll(USER_ID), newMeal, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + } + + @Test + public void get() throws Exception { + Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); + assertMatch(actual, ADMIN_MEAL1); + } + + @Test(expected = NotFoundException.class) + public void getNotFound() throws Exception { + service.get(MEAL1_ID, ADMIN_ID); + } + + @Test + public void update() throws Exception { + Meal updated = getUpdated(); + service.update(updated, USER_ID); + assertMatch(service.get(MEAL1_ID, USER_ID), updated); + } + + @Test(expected = NotFoundException.class) + public void updateNotFound() throws Exception { + service.update(MEAL1, ADMIN_ID); + } + + @Test + public void getAll() throws Exception { + assertMatch(service.getAll(USER_ID), MEALS); + } + + @Test + public void getBetween() throws Exception { + assertMatch(service.getBetweenDates( + LocalDate.of(2015, Month.MAY, 30), + LocalDate.of(2015, Month.MAY, 30), USER_ID), MEAL3, MEAL2, MEAL1); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 03bfc5f..52c15da 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -41,6 +41,7 @@ public void create() throws Exception { User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.ROLE_USER)); User created = service.create(newUser); newUser.setId(created.getId()); + assertMatch(newUser, created); assertMatch(service.getAll(), ADMIN, newUser, USER); } From 8f453e05c2924ec66b287a6febf10d782c12062d Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Feb 2019 21:19:57 +0300 Subject: [PATCH 032/125] 4_3_improve_code --- .travis.yml | 22 +++++++++++++++++++ .../topjava/service/MealServiceImpl.java | 5 +++++ .../topjava/service/UserServiceImpl.java | 4 ++++ .../topjava/util/DateTimeUtil.java | 3 +++ .../javawebinar/topjava/util/MealsUtil.java | 3 +++ .../ru/javawebinar/topjava/util/Util.java | 3 +++ .../topjava/util/ValidationUtil.java | 3 +++ .../javawebinar/topjava/web/SecurityUtil.java | 3 +++ .../inmemory/InMemoryBaseRepositoryImpl.java | 2 ++ .../inmemory/InMemoryMealRepositoryImpl.java | 8 +++---- .../inmemory/InMemoryUserRepositoryImpl.java | 2 ++ 11 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..68ab0a0 --- /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/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 6637187..276609d 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -32,6 +33,8 @@ public void delete(int id, int userId) { @Override public List getBetweenDateTimes(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Assert.notNull(startDateTime, "startDateTime must not be null"); + Assert.notNull(endDateTime, "endDateTime must not be null"); return repository.getBetween(startDateTime, endDateTime, userId); } @@ -42,11 +45,13 @@ public List getAll(int userId) { @Override public void update(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); checkNotFoundWithId(repository.save(meal, userId), meal.getId()); } @Override public Meal create(Meal meal, int userId) { + Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index c651c41..a3f0901 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -23,6 +24,7 @@ public UserServiceImpl(UserRepository repository) { @Override public User create(User user) { + Assert.notNull(user, "user must not be null"); return repository.save(user); } @@ -38,6 +40,7 @@ public User get(int id) throws NotFoundException { @Override public User getByEmail(String email) throws NotFoundException { + Assert.notNull(email, "email must not be null"); return checkNotFound(repository.getByEmail(email), "email=" + email); } @@ -48,6 +51,7 @@ public List getAll() { @Override public void update(User user) { + Assert.notNull(user, "user must not be null"); checkNotFoundWithId(repository.save(user), user.getId()); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java index 4bf62ad..1403cb3 100644 --- a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -15,6 +15,9 @@ public class DateTimeUtil { private static final LocalDate MIN_DATE = LocalDate.of(1, 1, 1); private static final LocalDate MAX_DATE = LocalDate.of(3000, 1, 1); + private DateTimeUtil() { + } + public static LocalDateTime adjustStartDateTime(LocalDate localDate) { return adjustDateTime(localDate, MIN_DATE, LocalTime.MIN); } diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index e78a08c..39d7272 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -18,6 +18,9 @@ public class MealsUtil { public static final int DEFAULT_CALORIES_PER_DAY = 2000; + private MealsUtil() { + } + public static List getWithExcess(Collection meals, int caloriesPerDay) { return getFilteredWithExcess(meals, caloriesPerDay, meal -> true); } diff --git a/src/main/java/ru/javawebinar/topjava/util/Util.java b/src/main/java/ru/javawebinar/topjava/util/Util.java index b0a3aee..bc62117 100644 --- a/src/main/java/ru/javawebinar/topjava/util/Util.java +++ b/src/main/java/ru/javawebinar/topjava/util/Util.java @@ -3,6 +3,9 @@ import org.springframework.lang.Nullable; public class Util { + private Util() { + } + public static > boolean isBetween(T value, @Nullable T start, @Nullable T end) { return (start == null || value.compareTo(start) >= 0) && (end == null || value.compareTo(end) <= 0); } diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index f38a2d2..cb51f3c 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -6,6 +6,9 @@ public class ValidationUtil { + private ValidationUtil() { + } + public static T checkNotFoundWithId(T object, int id) { return checkNotFound(object, "id=" + id); } diff --git a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java index 5882175..4bad586 100644 --- a/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/SecurityUtil.java @@ -8,6 +8,9 @@ public class SecurityUtil { private static int id = AbstractBaseEntity.START_SEQ; + private SecurityUtil() { + } + public static int authUserId() { return id; } diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java index c20a460..ec9f9a5 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryBaseRepositoryImpl.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -16,6 +17,7 @@ public class InMemoryBaseRepositoryImpl { Map entryMap = new ConcurrentHashMap<>(); public T save(T entry) { + Objects.requireNonNull(entry, "Entry must not be null"); if (entry.isNew()) { entry.setId(counter.incrementAndGet()); entryMap.put(entry.getId(), entry); diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 7849c58..2d2e6e7 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -10,10 +10,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -27,6 +24,7 @@ public class InMemoryMealRepositoryImpl implements MealRepository { @Override public Meal save(Meal meal, int userId) { + Objects.requireNonNull(meal, "meal must not be null"); InMemoryBaseRepositoryImpl meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepositoryImpl<>()); return meals.save(meal); } @@ -60,6 +58,8 @@ public List getAll(int userId) { @Override public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateTime, int userId) { + Objects.requireNonNull(startDateTime, "startDateTime must not be null"); + Objects.requireNonNull(endDateTime, "endDateTime must not be null"); return getAllFiltered(userId, meal -> Util.isBetween(meal.getDateTime(), startDateTime, endDateTime)); } diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java index e2e9c17..a0bd07e 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryUserRepositoryImpl.java @@ -7,6 +7,7 @@ import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import static ru.javawebinar.topjava.UserTestData.ADMIN; @@ -30,6 +31,7 @@ public List getAll() { @Override public User getByEmail(String email) { + Objects.requireNonNull(email, "email must not be null"); return getCollection().stream() .filter(u -> email.equals(u.getEmail())) .findFirst() From e808ceb89c088e8f52618dc28ede0ccaa1653613 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Feb 2019 21:20:24 +0300 Subject: [PATCH 033/125] 4_4_init_and_populate_db --- src/main/resources/db/postgres.properties | 1 + src/main/resources/spring/spring-db.xml | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index fd8fe56..8290980 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,3 +5,4 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password +database.init=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 8ec56c9..d433866 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,11 +1,18 @@ + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> + + + + + From e090a74805262b3df3bf3c32ee3004f363320cf5 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Feb 2019 21:20:50 +0300 Subject: [PATCH 034/125] 4_5_create_inmemory_test_ctx --- src/main/resources/spring/spring-app.xml | 2 -- src/main/resources/spring/spring-db.xml | 2 ++ src/test/java/ru/javawebinar/topjava/SpringMain.java | 2 +- .../web/user/InMemoryAdminRestControllerSpringTest.java | 2 +- .../topjava/web/user/InMemoryAdminRestControllerTest.java | 2 +- src/test/resources/spring/inmemory.xml | 7 +++++++ 6 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/spring/inmemory.xml diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index fc3b4d1..637e3d2 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -15,8 +15,6 @@ - - diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index d433866..ce13be2 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -8,6 +8,8 @@ + + diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index f72561b..15187b1 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -17,7 +17,7 @@ public class SpringMain { public static void main(String[] args) { // java 7 automatic resource management - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml")) { + try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml")) { System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java index 49b48ef..e0962a9 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -17,7 +17,7 @@ import static ru.javawebinar.topjava.UserTestData.ADMIN; -@ContextConfiguration("classpath:spring/spring-app.xml") +@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/inmemory.xml"}) @RunWith(SpringRunner.class) public class InMemoryAdminRestControllerSpringTest { diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java index 35e6626..2346169 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -20,7 +20,7 @@ public class InMemoryAdminRestControllerTest { @BeforeClass public static void beforeClass() { - appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml"); + appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml"); System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); controller = appCtx.getBean(AdminRestController.class); } diff --git a/src/test/resources/spring/inmemory.xml b/src/test/resources/spring/inmemory.xml new file mode 100644 index 0000000..c6a2710 --- /dev/null +++ b/src/test/resources/spring/inmemory.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file From 82af34d5a11f0e09416942820a466a3bbc5cf6aa Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Feb 2019 21:25:21 +0300 Subject: [PATCH 035/125] 4_6_add_jpa --- pom.xml | 27 +++++++- .../topjava/model/AbstractBaseEntity.java | 10 ++- .../topjava/model/AbstractNamedEntity.java | 12 +++- .../ru/javawebinar/topjava/model/Meal.java | 13 ++++ .../ru/javawebinar/topjava/model/User.java | 24 ++++++++ .../repository/jpa/JpaMealRepositoryImpl.java | 37 +++++++++++ .../repository/jpa/JpaUserRepositoryImpl.java | 61 +++++++++++++++++++ src/main/resources/db/postgres.properties | 6 +- src/main/resources/spring/spring-db.xml | 37 +++++++++-- 9 files changed, 217 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java diff --git a/pom.xml b/pom.xml index 8129dbd..d7b391e 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,11 @@ 42.2.5 4.12 + + + 5.4.1.Final + 6.0.15.Final + 3.0.1-b11 @@ -83,7 +88,7 @@ org.springframework - spring-jdbc + spring-orm ${spring.version} @@ -94,6 +99,26 @@ ${postgresql.version} + + + org.hibernate + hibernate-core + ${hibernate.version} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + + + org.glassfish + javax.el + ${javax-el.version} + provided + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 6ab3359..2b02f81 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,11 +1,19 @@ package ru.javawebinar.topjava.model; +import javax.persistence.*; + +@MappedSuperclass +// http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access +@Access(AccessType.FIELD) public abstract class AbstractBaseEntity { public static final int START_SEQ = 100000; + @Id + @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") protected Integer id; - public AbstractBaseEntity() { + protected AbstractBaseEntity() { } protected AbstractBaseEntity(Integer id) { diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index 0e07e37..e0b51eb 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -1,10 +1,20 @@ package ru.javawebinar.topjava.model; +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + + +@MappedSuperclass public abstract class AbstractNamedEntity extends AbstractBaseEntity { + @NotBlank + @Size(min = 2, max = 100) + @Column(name = "name", nullable = false) protected String name; - public AbstractNamedEntity() { + protected AbstractNamedEntity() { } protected AbstractNamedEntity(Integer id, String name) { diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 1c1e67b..788ae8f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -11,6 +13,9 @@ public class Meal extends AbstractBaseEntity { private int calories; + @ManyToOne(fetch = FetchType.LAZY) + private User user; + public Meal() { } @@ -57,6 +62,14 @@ public void setCalories(int calories) { this.calories = calories; } + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 446d86f..ef82c1b 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,7 +1,13 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.util.Collection; import java.util.Date; import java.util.EnumSet; @@ -9,18 +15,36 @@ import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; +@Entity +@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) public class User extends AbstractNamedEntity { + @Column(name = "email", nullable = false, unique = true) + @Email + @NotBlank + @Size(max = 100) private String email; + @Column(name = "password", nullable = false) + @NotBlank + @Size(min = 5, max = 100) private String password; + @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") private boolean enabled = true; + @Column(name = "registered", columnDefinition = "timestamp default now()") + @NotNull private Date registered = new Date(); + @Enumerated(EnumType.STRING) + @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) + @Column(name = "role") + @ElementCollection(fetch = FetchType.EAGER) private Set roles; + @Column(name = "calories_per_day", columnDefinition = "int default 2000") + @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; public User() { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java new file mode 100644 index 0000000..052d875 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -0,0 +1,37 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class JpaMealRepositoryImpl implements MealRepository { + + @Override + public Meal save(Meal meal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public Meal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} \ 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 0000000..380b870 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -0,0 +1,61 @@ +package ru.javawebinar.topjava.repository.jpa; + +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import java.util.List; + +@Repository +public class JpaUserRepositoryImpl implements UserRepository { + +/* + @Autowired + private SessionFactory sessionFactory; + + private Session openSession() { + return sessionFactory.getCurrentSession(); + } +*/ + + @PersistenceContext + private EntityManager em; + + @Override + 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 + 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; + } + + @Override + public User getByEmail(String email) { + return null; + } + + @Override + public List getAll() { + return null; + } +} diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 8290980..f8897fe 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,4 +5,8 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password -database.init=true \ No newline at end of file + +database.init=true +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index ce13be2..85b4dd6 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -1,5 +1,6 @@ - + @@ -24,11 +25,35 @@ - - - + + + + + + + + + + - - + + + + + + \ No newline at end of file From b1a491cfadbd5ae1857397f5200042dbe1016ee4 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Feb 2019 21:26:05 +0300 Subject: [PATCH 036/125] 4_7_add_named_query_and_transaction --- .../ru/javawebinar/topjava/model/User.java | 9 +++++++++ .../repository/jpa/JpaUserRepositoryImpl.java | 19 +++++++++++++++---- src/main/resources/spring/spring-db.xml | 10 +++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index ef82c1b..e32787d 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -15,10 +15,19 @@ import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; +@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 u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"), +}) @Entity @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) public class User extends AbstractNamedEntity { + public static final String DELETE = "User.delete"; + public static final String BY_EMAIL = "User.getByEmail"; + public static final String ALL_SORTED = "User.getAllSorted"; + @Column(name = "email", nullable = false, unique = true) @Email @NotBlank diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java index 380b870..b82dbd7 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -1,15 +1,17 @@ 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.repository.UserRepository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; -import javax.persistence.Query; import java.util.List; @Repository +@Transactional(readOnly = true) public class JpaUserRepositoryImpl implements UserRepository { /* @@ -25,6 +27,7 @@ private Session openSession() { private EntityManager em; @Override + @Transactional public User save(User user) { if (user.isNew()) { em.persist(user); @@ -40,22 +43,30 @@ public User get(int 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 null; + List users = em.createNamedQuery(User.BY_EMAIL, User.class) + .setParameter(1, email) + .getResultList(); + return DataAccessUtils.singleResult(users); } @Override public List getAll() { - return null; + return em.createNamedQuery(User.ALL_SORTED, User.class).getResultList(); } } diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 85b4dd6..889bba9 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -3,9 +3,11 @@ xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" + xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd - http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> @@ -46,6 +48,12 @@ + + + + + diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties new file mode 100644 index 0000000..6371e7b --- /dev/null +++ b/src/main/resources/db/hsqldb.properties @@ -0,0 +1,12 @@ +#database.url=jdbc:hsqldb:file:D:/temp/topjava + +database.url=jdbc:hsqldb:mem:topjava +database.username=sa +database.password= +database.driverClassName=org.hsqldb.jdbcDriver + +database.init=true +jdbc.initLocation=initDB_hsql.sql +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/db/initDB_hsql.sql b/src/main/resources/db/initDB_hsql.sql new file mode 100644 index 0000000..d40fe76 --- /dev/null +++ b/src/main/resources/db/initDB_hsql.sql @@ -0,0 +1,41 @@ +DROP TABLE user_roles IF EXISTS; +DROP TABLE meals IF EXISTS; +DROP TABLE users IF EXISTS; +DROP SEQUENCE global_seq IF EXISTS; + +CREATE SEQUENCE GLOBAL_SEQ + AS INTEGER + START WITH 100000; + +CREATE TABLE users +( + id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, + registered TIMESTAMP DEFAULT now() NOT NULL, + enabled BOOLEAN DEFAULT TRUE NOT NULL, + calories_per_day INTEGER DEFAULT 2000 NOT NULL +); +CREATE UNIQUE INDEX users_unique_email_idx + ON USERS (email); + +CREATE TABLE user_roles +( + user_id INTEGER NOT NULL, + role VARCHAR(255), + CONSTRAINT user_roles_idx UNIQUE (user_id, role), + FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE +); + +CREATE TABLE meals +( + id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY, + date_time TIMESTAMP NOT NULL, + description VARCHAR(255) NOT NULL, + calories INT NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES USERS (id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX meals_unique_user_datetime_idx + ON meals (user_id, date_time) \ No newline at end of file diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index f8897fe..9942a03 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,8 +5,10 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password +database.driverClassName=org.postgresql.Driver database.init=true +jdbc.initLocation=initDB.sql jpa.showSql=true hibernate.format_sql=true hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 889bba9..ef29e6a 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -9,19 +9,22 @@ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + + + - + - + From 615b111d125918b15140260f805d270e60958466 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Mar 2019 09:10:32 +0300 Subject: [PATCH 038/125] 5_0_jdk_11 --- .travis.yml | 2 +- pom.xml | 8 +++++++- src/test/java/ru/javawebinar/topjava/MealTestData.java | 5 ++--- src/test/java/ru/javawebinar/topjava/UserTestData.java | 4 ++-- .../repository/inmemory/InMemoryMealRepositoryImpl.java | 8 ++++---- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68ab0a0..ad4c8b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ # https://docs.travis-ci.com/user/languages/java/ language: java -jdk: oraclejdk8 +jdk: openjdk11 #https://dzone.com/articles/travis-ci-tutorial-java-projects cache: diff --git a/pom.xml b/pom.xml index fc37a89..6f6b97b 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ http://topjava.herokuapp.com/ - 1.8 + 11 UTF-8 UTF-8 @@ -80,6 +80,12 @@ runtime + + javax.annotation + javax.annotation-api + 1.3.2 + + org.springframework diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java index d94faeb..7df45b7 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -3,7 +3,6 @@ import ru.javawebinar.topjava.model.Meal; import java.time.Month; -import java.util.Arrays; import java.util.List; import static java.time.LocalDateTime.of; @@ -23,7 +22,7 @@ public class MealTestData { 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 MEALS = Arrays.asList(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + public static final List MEALS = List.of(MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); public static Meal getCreated() { return new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Созданный ужин", 300); @@ -38,7 +37,7 @@ public static void assertMatch(Meal actual, Meal expected) { } public static void assertMatch(Iterable actual, Meal... expected) { - assertMatch(actual, Arrays.asList(expected)); + assertMatch(actual, List.of(expected)); } public static void assertMatch(Iterable actual, Iterable expected) { diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 3cc466c..ddb04d4 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -3,7 +3,7 @@ import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import java.util.Arrays; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; @@ -20,7 +20,7 @@ public static void assertMatch(User actual, User expected) { } public static void assertMatch(Iterable actual, User... expected) { - assertMatch(actual, Arrays.asList(expected)); + assertMatch(actual, List.of(expected)); } public static void assertMatch(Iterable actual, Iterable expected) { diff --git a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java index 2d2e6e7..4f3ea3a 100644 --- a/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java +++ b/src/test/java/ru/javawebinar/topjava/repository/inmemory/InMemoryMealRepositoryImpl.java @@ -25,7 +25,7 @@ public class InMemoryMealRepositoryImpl implements MealRepository { @Override public Meal save(Meal meal, int userId) { Objects.requireNonNull(meal, "meal must not be null"); - InMemoryBaseRepositoryImpl meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepositoryImpl<>()); + var meals = usersMealsMap.computeIfAbsent(userId, uid -> new InMemoryBaseRepositoryImpl<>()); return meals.save(meal); } @@ -41,13 +41,13 @@ public void preDestroy() { @Override public boolean delete(int id, int userId) { - InMemoryBaseRepositoryImpl meals = usersMealsMap.get(userId); + var meals = usersMealsMap.get(userId); return meals != null && meals.delete(id); } @Override public Meal get(int id, int userId) { - InMemoryBaseRepositoryImpl meals = usersMealsMap.get(userId); + var meals = usersMealsMap.get(userId); return meals == null ? null : meals.get(id); } @@ -64,7 +64,7 @@ public List getBetween(LocalDateTime startDateTime, LocalDateTime endDateT } private List getAllFiltered(int userId, Predicate filter) { - InMemoryBaseRepositoryImpl meals = usersMealsMap.get(userId); + var meals = usersMealsMap.get(userId); return meals == null ? Collections.emptyList() : meals.getCollection().stream() .filter(filter) From d686c3b1e99478db6a7500007af1d67954412c0a Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Mar 2019 09:20:45 +0300 Subject: [PATCH 039/125] 5_1_HW4 --- .../ru/javawebinar/topjava/model/Meal.java | 32 +++++++++++++++- .../repository/jpa/JpaMealRepositoryImpl.java | 38 ++++++++++++++++--- .../ru/javawebinar/topjava/MealTestData.java | 4 +- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 788ae8f..48989e0 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,19 +1,47 @@ package ru.javawebinar.topjava.model; -import javax.persistence.FetchType; -import javax.persistence.ManyToOne; +import org.hibernate.validator.constraints.Range; + +import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +@SuppressWarnings("JpaQlInspection") +@NamedQueries({ + @NamedQuery(name = Meal.ALL_SORTED, query = "SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC"), + @NamedQuery(name = Meal.DELETE, query = "DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId"), + @NamedQuery(name = Meal.GET_BETWEEN, query = "SELECT m FROM Meal m " + + "WHERE m.user.id=:userId AND m.dateTime BETWEEN :startDate AND :endDate ORDER BY m.dateTime DESC"), +// @NamedQuery(name = Meal.UPDATE, query = "UPDATE Meal 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 Meal extends AbstractBaseEntity { + public static final String ALL_SORTED = "Meal.getAll"; + public static final String DELETE = "Meal.delete"; + public static final String GET_BETWEEN = "Meal.getBetween"; + + @Column(name = "date_time", nullable = false) + @NotNull private LocalDateTime dateTime; + @Column(name = "description", nullable = false) + @NotBlank + @Size(min = 2, max = 120) private String description; + @Column(name = "calories", nullable = false) + @Range(min = 10, max = 5000) private int calories; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + @NotNull private User user; public Meal() { diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java index 052d875..2aeeb63 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaMealRepositoryImpl.java @@ -1,37 +1,65 @@ package ru.javawebinar.topjava.repository.jpa; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.MealRepository; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; @Repository +@Transactional(readOnly = true) public class JpaMealRepositoryImpl implements MealRepository { + @PersistenceContext + private EntityManager em; + @Override + @Transactional public Meal save(Meal meal, int userId) { - return null; + if (!meal.isNew() && get(meal.getId(), userId) == null) { + return null; + } + meal.setUser(em.getReference(User.class, userId)); + if (meal.isNew()) { + em.persist(meal); + return meal; + } else { + return em.merge(meal); + } } @Override + @Transactional public boolean delete(int id, int userId) { - return false; + return em.createNamedQuery(Meal.DELETE) + .setParameter("id", id) + .setParameter("userId", userId) + .executeUpdate() != 0; } @Override public Meal get(int id, int userId) { - return null; + Meal meal = em.find(Meal.class, id); + return meal != null && meal.getUser().getId() == userId ? meal : null; } @Override public List getAll(int userId) { - return null; + return em.createNamedQuery(Meal.ALL_SORTED, Meal.class) + .setParameter("userId", userId) + .getResultList(); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return em.createNamedQuery(Meal.GET_BETWEEN, Meal.class) + .setParameter("userId", userId) + .setParameter("startDate", startDate) + .setParameter("endDate", endDate).getResultList(); } } \ 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 index 7df45b7..4567c7d 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -33,7 +33,7 @@ public static Meal getUpdated() { } public static void assertMatch(Meal actual, Meal expected) { - assertThat(actual).isEqualToComparingFieldByField(expected); + assertThat(actual).isEqualToIgnoringGivenFields(expected, "user"); } public static void assertMatch(Iterable actual, Meal... expected) { @@ -41,6 +41,6 @@ public static void assertMatch(Iterable actual, Meal... expected) { } public static void assertMatch(Iterable actual, Iterable expected) { - assertThat(actual).usingFieldByFieldElementComparator().isEqualTo(expected); + assertThat(actual).usingElementComparatorIgnoringFields("user").isEqualTo(expected); } } From e58892fe6184ce44e1e560f5f9f3aadf8f450d3a Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Mar 2019 09:24:19 +0300 Subject: [PATCH 040/125] 5_2_fix_hibernate_issue --- .../ru/javawebinar/topjava/model/AbstractBaseEntity.java | 7 ++++++- src/main/resources/spring/spring-db.xml | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 2b02f81..856eeff 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import org.hibernate.Hibernate; + import javax.persistence.*; @MappedSuperclass @@ -11,6 +13,9 @@ public abstract class AbstractBaseEntity { @Id @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") + +// See https://hibernate.atlassian.net/browse/HHH-3718 and https://hibernate.atlassian.net/browse/HHH-12034 +// Proxy initialization when accessing its identifier managed now by JPA_PROXY_COMPLIANCE setting protected Integer id; protected AbstractBaseEntity() { @@ -42,7 +47,7 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !getClass().equals(Hibernate.getClass(o))) { return false; } AbstractBaseEntity that = (AbstractBaseEntity) o; diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index ef29e6a..eb40927 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -40,6 +40,7 @@ + From 7a68023288bdaec09e8bc129e8f79f4ead7a61f6 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Mar 2019 09:25:26 +0300 Subject: [PATCH 041/125] 5_3_HW4_optional --- .../topjava/service/MealServiceTest.java | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index d2d8794..90a8805 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -1,19 +1,27 @@ package ru.javawebinar.topjava.service; +import org.junit.AfterClass; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; import org.junit.runner.RunWith; +import org.slf4j.Logger; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.time.LocalDate; import java.time.Month; +import java.util.concurrent.TimeUnit; +import static org.slf4j.LoggerFactory.getLogger; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; @@ -22,14 +30,41 @@ "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" }) -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { + private static final Logger log = getLogger(MealServiceTest.class); + + private static StringBuilder results = new StringBuilder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public Stopwatch stopwatch = new Stopwatch() { + @Override + protected void finished(long nanos, Description description) { + String result = String.format("\n%-25s %7d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result); + log.info(result + " ms\n"); + } + }; static { + // needed only for java.util.logging (postgres driver) SLF4JBridgeHandler.install(); } + @AfterClass + public static void printResult() { + log.info("\n---------------------------------" + + "\nTest Duration, ms" + + "\n---------------------------------" + + results + + "\n---------------------------------"); + } + @Autowired private MealService service; @@ -39,8 +74,9 @@ public void delete() throws Exception { assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); } - @Test(expected = NotFoundException.class) + @Test public void deleteNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.delete(MEAL1_ID, 1); } @@ -59,8 +95,9 @@ public void get() throws Exception { assertMatch(actual, ADMIN_MEAL1); } - @Test(expected = NotFoundException.class) + @Test public void getNotFound() throws Exception { + thrown.expect(NotFoundException.class); service.get(MEAL1_ID, ADMIN_ID); } @@ -71,8 +108,10 @@ public void update() throws Exception { assertMatch(service.get(MEAL1_ID, USER_ID), updated); } - @Test(expected = NotFoundException.class) + @Test public void updateNotFound() throws Exception { + thrown.expect(NotFoundException.class); + thrown.expectMessage("Not found entity with id=" + MEAL1_ID); service.update(MEAL1, ADMIN_ID); } From eb5fc3c004692f6c0d5e2e4506b9e9ee6b61adbc Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Mar 2019 20:11:46 +0300 Subject: [PATCH 042/125] 5_4_log_colored --- src/main/resources/logback.xml | 2 +- .../topjava/service/MealServiceTest.java | 2 +- src/test/resources/logback-test.xml | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index c7bffc3..12a4f63 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -16,7 +16,7 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 90a8805..f0e858a 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -33,7 +33,7 @@ @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) public class MealServiceTest { - private static final Logger log = getLogger(MealServiceTest.class); + private static final Logger log = getLogger("result"); private static StringBuilder results = new StringBuilder(); diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 428ceca..019d8a1 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -7,12 +7,23 @@ UTF-8 - %d{HH:mm:ss.SSS} %-5level %class{50}.%M:%L - %msg%n + %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%class{50}.%M:%L) - %msg%n - - + + + UTF-8 + %magenta(%msg%n) + + + + + + + + + From deb29a248ab852df3eb6bdc23c8d87837d369b27 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 9 Mar 2019 22:07:23 +0300 Subject: [PATCH 043/125] 5_5_profiles_connection_pool --- pom.xml | 43 +++++++++++++------ .../java/ru/javawebinar/topjava/Profiles.java | 15 +++++++ src/main/resources/db/hsqldb.properties | 1 - src/main/resources/db/postgres.properties | 1 - src/main/resources/spring/spring-db.xml | 37 ++++++++++------ .../topjava/service/MealServiceTest.java | 3 ++ .../topjava/service/UserServiceTest.java | 3 ++ 7 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/Profiles.java diff --git a/pom.xml b/pom.xml index 6f6b97b..ff8a860 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ UTF-8 5.1.4.RELEASE + 9.0.16 1.2.3 @@ -98,19 +99,6 @@ ${spring.version} - - - org.postgresql - postgresql - ${postgresql.version} - - - org.hsqldb - hsqldb - 2.3.4 - - - org.hibernate @@ -167,6 +155,35 @@ + + hsqldb + + + org.hsqldb + hsqldb + 2.3.4 + + + + + postgres + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + provided + + + + true + + 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 0000000..c438cbb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -0,0 +1,15 @@ +package ru.javawebinar.topjava; + +public class Profiles { + public static final String + JDBC = "jdbc", + JPA = "jpa"; + + public static final String REPOSITORY_IMPLEMENTATION = JPA; + + public static final String + POSTGRES_DB = "postgres", + HSQL_DB = "hsqldb"; + + public static final String ACTIVE_DB = POSTGRES_DB; +} diff --git a/src/main/resources/db/hsqldb.properties b/src/main/resources/db/hsqldb.properties index 6371e7b..0ea68eb 100644 --- a/src/main/resources/db/hsqldb.properties +++ b/src/main/resources/db/hsqldb.properties @@ -3,7 +3,6 @@ database.url=jdbc:hsqldb:mem:topjava database.username=sa database.password= -database.driverClassName=org.hsqldb.jdbcDriver database.init=true jdbc.initLocation=initDB_hsql.sql diff --git a/src/main/resources/db/postgres.properties b/src/main/resources/db/postgres.properties index 9942a03..a8a5406 100644 --- a/src/main/resources/db/postgres.properties +++ b/src/main/resources/db/postgres.properties @@ -5,7 +5,6 @@ database.url=jdbc:postgresql://localhost:5432/topjava database.username=user database.password=password -database.driverClassName=org.postgresql.Driver database.init=true jdbc.initLocation=initDB.sql diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index eb40927..c64cba2 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -9,11 +9,6 @@ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> - - - - - @@ -21,15 +16,6 @@ - - - - - - - - @@ -68,4 +54,27 @@ --> + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index f0e858a..8d89f29 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -10,10 +10,12 @@ import org.slf4j.Logger; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -32,6 +34,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(Profiles.ACTIVE_DB) public class MealServiceTest { private static final Logger log = getLogger("result"); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 52c15da..a4bdac0 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -5,10 +5,12 @@ import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -25,6 +27,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +@ActiveProfiles(Profiles.ACTIVE_DB) public class UserServiceTest { static { From 176b17846eed36a67469f95696c131d25149e190 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 10 Mar 2019 12:55:03 +0300 Subject: [PATCH 044/125] 5_6_profile_resolver --- .../java/ru/javawebinar/topjava/Profiles.java | 15 ++++++++++++++- .../topjava/ActiveDbProfileResolver.java | 12 ++++++++++++ .../topjava/service/MealServiceTest.java | 4 ++-- .../topjava/service/UserServiceTest.java | 4 ++-- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index c438cbb..a11b37a 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -11,5 +11,18 @@ public class Profiles { POSTGRES_DB = "postgres", HSQL_DB = "hsqldb"; - public static final String ACTIVE_DB = POSTGRES_DB; + // Get DB profile depending of DB driver in classpath + public static String getActiveDbProfile() { + try { + Class.forName("org.postgresql.Driver"); + return POSTGRES_DB; + } catch (ClassNotFoundException ex) { + try { + Class.forName("org.hsqldb.jdbcDriver"); + return Profiles.HSQL_DB; + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Could not find DB driver"); + } + } + } } 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 0000000..05d47b1 --- /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/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java index 8d89f29..466fe20 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java @@ -15,7 +15,7 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -34,7 +34,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.ACTIVE_DB) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) public class MealServiceTest { private static final Logger log = getLogger("result"); diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index a4bdac0..1124842 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -10,7 +10,7 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -27,7 +27,7 @@ }) @RunWith(SpringRunner.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(Profiles.ACTIVE_DB) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) public class UserServiceTest { static { From 85d686a4ae92a9ea0e6152be8fdce18aab84a1e2 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 10 Mar 2019 13:59:10 +0300 Subject: [PATCH 045/125] 5_7_spring_data_jpa --- pom.xml | 18 +++++--- .../java/ru/javawebinar/topjava/Profiles.java | 5 ++- .../topjava/model/AbstractBaseEntity.java | 5 ++- .../datajpa/CrudMealRepository.java | 7 ++++ .../datajpa/CrudUserRepository.java | 33 +++++++++++++++ .../datajpa/DataJpaMealRepositoryImpl.java | 41 ++++++++++++++++++ .../datajpa/DataJpaUserRepositoryImpl.java | 42 +++++++++++++++++++ src/main/resources/spring/spring-db.xml | 16 ++++++- 8 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java create mode 100644 src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java diff --git a/pom.xml b/pom.xml index ff8a860..19d3922 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ UTF-8 5.1.4.RELEASE + 2.1.5.RELEASE 9.0.16 @@ -91,12 +92,11 @@ org.springframework spring-context - ${spring.version} - org.springframework - spring-orm - ${spring.version} + org.springframework.data + spring-data-jpa + ${spring-data-jpa.version} @@ -143,7 +143,6 @@ org.springframework spring-test - ${spring.version} test @@ -187,5 +186,14 @@ + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index a11b37a..d6ef2e0 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -3,9 +3,10 @@ public class Profiles { public static final String JDBC = "jdbc", - JPA = "jpa"; + JPA = "jpa", + DATAJPA = "datajpa"; - public static final String REPOSITORY_IMPLEMENTATION = JPA; + public static final String REPOSITORY_IMPLEMENTATION = DATAJPA; public static final String POSTGRES_DB = "postgres", diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index 856eeff..cbb8d6f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,13 +1,14 @@ package ru.javawebinar.topjava.model; import org.hibernate.Hibernate; +import org.springframework.data.domain.Persistable; import javax.persistence.*; @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) -public abstract class AbstractBaseEntity { +public abstract class AbstractBaseEntity implements Persistable { public static final int START_SEQ = 100000; @Id @@ -29,10 +30,12 @@ public void setId(Integer id) { this.id = id; } + @Override public Integer getId() { return id; } + @Override public boolean isNew() { return this.id == null; } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java new file mode 100644 index 0000000..a365967 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.javawebinar.topjava.model.Meal; + +public interface CrudMealRepository extends JpaRepository { +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java new file mode 100644 index 0000000..cd7460d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -0,0 +1,33 @@ +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; +import java.util.Optional; + +@Transactional(readOnly = true) +public interface CrudUserRepository 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 + Optional findById(Integer id); + + @Override + List findAll(Sort sort); + + User getByEmail(String email); +} diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java new file mode 100644 index 0000000..fdd9270 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -0,0 +1,41 @@ +package ru.javawebinar.topjava.repository.datajpa; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public class DataJpaMealRepositoryImpl implements MealRepository { + + @Autowired + private CrudMealRepository crudRepository; + + @Override + public Meal save(Meal meal, int userId) { + return null; + } + + @Override + public boolean delete(int id, int userId) { + return false; + } + + @Override + public Meal get(int id, int userId) { + return null; + } + + @Override + public List getAll(int userId) { + return null; + } + + @Override + public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { + return null; + } +} 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 0000000..aa1615e --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -0,0 +1,42 @@ +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; + +@Repository +public class DataJpaUserRepositoryImpl implements UserRepository { + private static final Sort SORT_NAME_EMAIL = new Sort(Sort.Direction.ASC, "name", "email"); + + @Autowired + private CrudUserRepository crudRepository; + + @Override + public User save(User user) { + return crudRepository.save(user); + } + + @Override + public boolean delete(int id) { + return crudRepository.delete(id) != 0; + } + + @Override + public User get(int id) { + return crudRepository.findById(id).orElse(null); + } + + @Override + public User getByEmail(String email) { + return crudRepository.getByEmail(email); + } + + @Override + public List getAll() { + return crudRepository.findAll(SORT_NAME_EMAIL); + } +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index c64cba2..ddef3ad 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -4,12 +4,17 @@ xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd - http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> - + + + + @@ -77,4 +82,11 @@ p:username="${database.username}" p:password="${database.password}"/> + + + + + + + \ No newline at end of file From 06c271576ebfe730e809bf1d7dd559b948f2e622 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 10 Mar 2019 14:06:26 +0300 Subject: [PATCH 046/125] 5_8_spring_cache --- pom.xml | 25 ++++++++++++++++++- .../topjava/service/UserServiceImpl.java | 6 +++++ src/main/resources/cache/ehcache.xml | 25 +++++++++++++++++++ src/main/resources/spring/spring-app.xml | 1 + src/main/resources/spring/spring-tools.xml | 22 ++++++++++++++++ .../topjava/service/UserServiceTest.java | 10 ++++++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/cache/ehcache.xml create mode 100644 src/main/resources/spring/spring-tools.xml diff --git a/pom.xml b/pom.xml index 19d3922..4b1996f 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,9 @@ 5.4.1.Final 6.0.15.Final 3.0.1-b11 + + + 3.7.0 @@ -91,7 +94,7 @@ org.springframework - spring-context + spring-context-support org.springframework.data @@ -119,6 +122,26 @@ provided + + + javax.cache + cache-api + 1.1.0 + + + org.ehcache + ehcache + runtime + ${ehcache.version} + + + + + org.glassfish.jaxb + jaxb-runtime + 2.4.0-b180830.0438 + + javax.servlet diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index a3f0901..84f1619 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.service; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import ru.javawebinar.topjava.model.User; @@ -22,12 +24,14 @@ public UserServiceImpl(UserRepository repository) { this.repository = repository; } + @CacheEvict(value = "users", allEntries = true) @Override public User create(User user) { Assert.notNull(user, "user must not be null"); return repository.save(user); } + @CacheEvict(value = "users", allEntries = true) @Override public void delete(int id) throws NotFoundException { checkNotFoundWithId(repository.delete(id), id); @@ -44,11 +48,13 @@ public User getByEmail(String email) throws NotFoundException { return checkNotFound(repository.getByEmail(email), "email=" + email); } + @Cacheable("users") @Override public List getAll() { return repository.getAll(); } + @CacheEvict(value = "users", allEntries = true) @Override public void update(User user) { Assert.notNull(user, "user must not be null"); diff --git a/src/main/resources/cache/ehcache.xml b/src/main/resources/cache/ehcache.xml new file mode 100644 index 0000000..c6347bd --- /dev/null +++ b/src/main/resources/cache/ehcache.xml @@ -0,0 +1,25 @@ + + + + + + + + + + 5 + + 5000 + + + + + + + 1 + + + + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 637e3d2..3bdb548 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -11,6 +11,7 @@ --> + diff --git a/src/main/resources/spring/spring-tools.xml b/src/main/resources/spring/spring-tools.xml new file mode 100644 index 0000000..96551dc --- /dev/null +++ b/src/main/resources/spring/spring-tools.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java index 1124842..88a1afc 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java @@ -1,9 +1,11 @@ package ru.javawebinar.topjava.service; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; import org.springframework.dao.DataAccessException; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -39,6 +41,14 @@ public class UserServiceTest { @Autowired private UserService service; + @Autowired + private CacheManager cacheManager; + + @Before + public void setUp() throws Exception { + cacheManager.getCache("users").clear(); + } + @Test public void create() throws Exception { User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.ROLE_USER)); From 56554c834a95d7d850695ac55e2bdb49e02d59cf Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:06:34 +0300 Subject: [PATCH 047/125] 6_01_HW5_data_jpa --- .../datajpa/CrudMealRepository.java | 26 ++++++++++++++++++- .../datajpa/DataJpaMealRepositoryImpl.java | 21 ++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java index a365967..2cd7703 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -1,7 +1,31 @@ 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.Meal; +import java.time.LocalDateTime; +import java.util.List; + +@Transactional(readOnly = true) public interface CrudMealRepository extends JpaRepository { -} + + @Modifying + @Transactional + @Query("DELETE FROM Meal m WHERE m.id=:id AND m.user.id=:userId") + int delete(@Param("id") int id, @Param("userId") int userId); + + @Override + @Transactional + Meal save(Meal item); + + @Query("SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC") + List getAll(@Param("userId") int userId); + + @SuppressWarnings("JpaQlInspection") + @Query("SELECT m from Meal 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); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java index fdd9270..579bb78 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -12,30 +13,38 @@ public class DataJpaMealRepositoryImpl implements MealRepository { @Autowired - private CrudMealRepository crudRepository; + private CrudMealRepository crudMealRepository; + + @Autowired + private CrudUserRepository crudUserRepository; @Override + @Transactional public Meal save(Meal meal, int userId) { - return null; + if (!meal.isNew() && get(meal.getId(), userId) == null) { + return null; + } + meal.setUser(crudUserRepository.getOne(userId)); + return crudMealRepository.save(meal); } @Override public boolean delete(int id, int userId) { - return false; + return crudMealRepository.delete(id, userId) != 0; } @Override public Meal get(int id, int userId) { - return null; + return crudMealRepository.findById(id).filter(meal -> meal.getUser().getId() == userId).orElse(null); } @Override public List getAll(int userId) { - return null; + return crudMealRepository.getAll(userId); } @Override public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { - return null; + return crudMealRepository.getBetween(startDate, endDate, userId); } } From b8da16f5264f2f4ef8d5a4d745d009ccb215b9a0 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:07:26 +0300 Subject: [PATCH 048/125] 6_02_HW5_profile_test --- src/main/resources/spring/spring-db.xml | 85 +++++++++---------- ...Test.java => AbstractMealServiceTest.java} | 56 +----------- .../topjava/service/AbstractServiceTest.java | 61 +++++++++++++ ...Test.java => AbstractUserServiceTest.java} | 23 +---- .../datajpa/DataJpaMealServiceTest.java | 10 +++ .../datajpa/DataJpaUserServiceTest.java | 10 +++ .../service/jdbc/JdbcMealServiceTest.java | 10 +++ .../service/jdbc/JdbcUserServiceTest.java | 10 +++ .../service/jpa/JpaMealServiceTest.java | 10 +++ .../service/jpa/JpaUserServiceTest.java | 10 +++ 10 files changed, 165 insertions(+), 120 deletions(-) rename src/test/java/ru/javawebinar/topjava/service/{MealServiceTest.java => AbstractMealServiceTest.java} (51%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java rename src/test/java/ru/javawebinar/topjava/service/{UserServiceTest.java => AbstractUserServiceTest.java} (72%) create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index ddef3ad..76693d2 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -11,54 +11,12 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -84,9 +42,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java similarity index 51% rename from src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 466fe20..b3f976e 100644 --- a/src/test/java/ru/javawebinar/topjava/service/MealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,72 +1,18 @@ package ru.javawebinar.topjava.service; -import org.junit.AfterClass; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.Stopwatch; -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.slf4j.Logger; -import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.time.LocalDate; import java.time.Month; -import java.util.concurrent.TimeUnit; -import static org.slf4j.LoggerFactory.getLogger; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; -@ContextConfiguration({ - "classpath:spring/spring-app.xml", - "classpath:spring/spring-db.xml" -}) -@RunWith(SpringRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(resolver = ActiveDbProfileResolver.class) -public class MealServiceTest { - private static final Logger log = getLogger("result"); - - private static StringBuilder results = new StringBuilder(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Rule - // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev - public Stopwatch stopwatch = new Stopwatch() { - @Override - protected void finished(long nanos, Description description) { - String result = String.format("\n%-25s %7d", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); - results.append(result); - log.info(result + " ms\n"); - } - }; - - static { - // needed only for java.util.logging (postgres driver) - SLF4JBridgeHandler.install(); - } - - @AfterClass - public static void printResult() { - log.info("\n---------------------------------" + - "\nTest Duration, ms" + - "\n---------------------------------" + - results + - "\n---------------------------------"); - } +public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired private MealService service; diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java new file mode 100644 index 0000000..eaa76c8 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -0,0 +1,61 @@ +package ru.javawebinar.topjava.service; + +import org.junit.AfterClass; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import ru.javawebinar.topjava.ActiveDbProfileResolver; + +import java.util.concurrent.TimeUnit; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-db.xml" +}) +@RunWith(SpringJUnit4ClassRunner.class) +@ActiveProfiles(resolver = ActiveDbProfileResolver.class) +@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) +abstract public class AbstractServiceTest { + private static final Logger log = LoggerFactory.getLogger("result"); + + private static StringBuilder results = new StringBuilder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public Stopwatch stopwatch = new Stopwatch() { + @Override + protected void finished(long nanos, Description description) { + String result = String.format("%-95s %7d", description.getDisplayName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result).append('\n'); + log.info(result + " ms\n"); + } + }; + + static { + // needed only for java.util.logging (postgres driver) + SLF4JBridgeHandler.install(); + } + + @AfterClass + public static void printResult() { + log.info("\n-------------------------------------------------------------------------------------------------------" + + "\nTest Duration, ms" + + "\n-------------------------------------------------------------------------------------------------------\n" + + results + + "-------------------------------------------------------------------------------------------------------\n"); + results.setLength(0); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java similarity index 72% rename from src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java rename to src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 88a1afc..7b506d1 100644 --- a/src/test/java/ru/javawebinar/topjava/service/UserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -2,17 +2,9 @@ import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.dao.DataAccessException; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringRunner; -import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -23,20 +15,7 @@ import static ru.javawebinar.topjava.UserTestData.*; -@ContextConfiguration({ - "classpath:spring/spring-app.xml", - "classpath:spring/spring-db.xml" -}) -@RunWith(SpringRunner.class) -@Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -@ActiveProfiles(resolver = ActiveDbProfileResolver.class) -public class UserServiceTest { - - static { - // Only for postgres driver logging - // It uses java.util.logging and logged via jul-to-slf4j bridge - SLF4JBridgeHandler.install(); - } +public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private UserService service; diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java new file mode 100644 index 0000000..e28cc6d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; + +@ActiveProfiles(DATAJPA) +public class DataJpaMealServiceTest extends AbstractMealServiceTest { +} diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java new file mode 100644 index 0000000..a0757e7 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.datajpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.DATAJPA; + +@ActiveProfiles(DATAJPA) +public class DataJpaUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java new file mode 100644 index 0000000..9ff4ae6 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jdbc; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JDBC; + +@ActiveProfiles(JDBC) +public class JdbcMealServiceTest extends AbstractMealServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java new file mode 100644 index 0000000..419f68e --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jdbc; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.JDBC; + +@ActiveProfiles(JDBC) +public class JdbcUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java new file mode 100644 index 0000000..70e7bf8 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractMealServiceTest; + +import static ru.javawebinar.topjava.Profiles.JPA; + +@ActiveProfiles(JPA) +public class JpaMealServiceTest extends AbstractMealServiceTest { +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java new file mode 100644 index 0000000..d1b3e46 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava.service.jpa; + +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.service.AbstractUserServiceTest; + +import static ru.javawebinar.topjava.Profiles.JPA; + +@ActiveProfiles(JPA) +public class JpaUserServiceTest extends AbstractUserServiceTest { +} \ No newline at end of file From 46b8b3869a51d3442a2955fc7009359066ffd66b Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:08:31 +0300 Subject: [PATCH 049/125] 6_03_extract_rules --- .../ru/javawebinar/topjava/TimingRules.java | 41 +++++++++++++++++++ .../topjava/service/AbstractServiceTest.java | 36 ++++------------ 2 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/TimingRules.java diff --git a/src/test/java/ru/javawebinar/topjava/TimingRules.java b/src/test/java/ru/javawebinar/topjava/TimingRules.java new file mode 100644 index 0000000..46f3339 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TimingRules.java @@ -0,0 +1,41 @@ +package ru.javawebinar.topjava; + +import org.junit.rules.ExternalResource; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public class TimingRules { + private static final Logger log = LoggerFactory.getLogger("result"); + + private static StringBuilder results = new StringBuilder(); + + // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev + public static final Stopwatch STOPWATCH = new Stopwatch() { + @Override + protected void finished(long nanos, Description description) { + String result = String.format("%-95s %7d", description.getDisplayName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + results.append(result).append('\n'); + log.info(result + " ms\n"); + } + }; + + public static final ExternalResource SUMMARY = new ExternalResource() { + @Override + protected void before() throws Throwable { + results.setLength(0); + } + + @Override + protected void after() { + log.info("\n-------------------------------------------------------------------------------------------------------" + + "\nTest Duration, ms" + + "\n-------------------------------------------------------------------------------------------------------\n" + + results + + "-------------------------------------------------------------------------------------------------------\n"); + } + }; +} diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index eaa76c8..4341357 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -1,13 +1,11 @@ package ru.javawebinar.topjava.service; -import org.junit.AfterClass; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.rules.ExpectedException; +import org.junit.rules.ExternalResource; import org.junit.rules.Stopwatch; -import org.junit.runner.Description; import org.junit.runner.RunWith; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -15,8 +13,7 @@ import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import ru.javawebinar.topjava.ActiveDbProfileResolver; - -import java.util.concurrent.TimeUnit; +import ru.javawebinar.topjava.TimingRules; @ContextConfiguration({ "classpath:spring/spring-app.xml", @@ -26,36 +23,17 @@ @ActiveProfiles(resolver = ActiveDbProfileResolver.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) abstract public class AbstractServiceTest { - private static final Logger log = LoggerFactory.getLogger("result"); - - private static StringBuilder results = new StringBuilder(); + @ClassRule + public static ExternalResource summary = TimingRules.SUMMARY; @Rule - public ExpectedException thrown = ExpectedException.none(); + public Stopwatch stopwatch = TimingRules.STOPWATCH; @Rule - // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev - public Stopwatch stopwatch = new Stopwatch() { - @Override - protected void finished(long nanos, Description description) { - String result = String.format("%-95s %7d", description.getDisplayName(), TimeUnit.NANOSECONDS.toMillis(nanos)); - results.append(result).append('\n'); - log.info(result + " ms\n"); - } - }; + public ExpectedException thrown = ExpectedException.none(); static { // needed only for java.util.logging (postgres driver) SLF4JBridgeHandler.install(); } - - @AfterClass - public static void printResult() { - log.info("\n-------------------------------------------------------------------------------------------------------" + - "\nTest Duration, ms" + - "\n-------------------------------------------------------------------------------------------------------\n" + - results + - "-------------------------------------------------------------------------------------------------------\n"); - results.setLength(0); - } } \ No newline at end of file From 078a669b196b6f0b3a9954934a4b2702f894d833 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:09:01 +0300 Subject: [PATCH 050/125] 6_04_HW5_optional_fix_jdbc_profiles --- .../jdbc/JdbcMealRepositoryImpl.java | 42 ++++++++++++++++--- .../javawebinar/topjava/web/MealServlet.java | 9 ++-- .../ru/javawebinar/topjava/SpringMain.java | 13 +++--- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index 80bbdb5..7c35372 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -1,6 +1,6 @@ package ru.javawebinar.topjava.repository.jdbc; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; @@ -9,14 +9,16 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; +import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.List; @Repository -public class JdbcMealRepositoryImpl implements MealRepository { +public abstract class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); @@ -26,8 +28,7 @@ public class JdbcMealRepositoryImpl implements MealRepository { private final SimpleJdbcInsert insertMeal; - @Autowired - public JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) .withTableName("meals") .usingGeneratedKeyColumns("id"); @@ -36,13 +37,42 @@ public JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTempl this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; } + + protected abstract T toDbDateTime(LocalDateTime ldt); + + @Repository + @Profile(Profiles.POSTGRES_DB) + public static class Java8JdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { + public Java8JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + super(jdbcTemplate, namedParameterJdbcTemplate); + } + + @Override + protected LocalDateTime toDbDateTime(LocalDateTime ldt) { + return ldt; + } + } + + @Repository + @Profile(Profiles.HSQL_DB) + public static class TimestampJdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { + public TimestampJdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + super(jdbcTemplate, namedParameterJdbcTemplate); + } + + @Override + protected Timestamp toDbDateTime(LocalDateTime ldt) { + return Timestamp.valueOf(ldt); + } + } + @Override public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) .addValue("description", meal.getDescription()) .addValue("calories", meal.getCalories()) - .addValue("date_time", meal.getDateTime()) + .addValue("date_time", toDbDateTime(meal.getDateTime())) .addValue("user_id", userId); if (meal.isNew()) { @@ -82,6 +112,6 @@ public List getAll(int userId) { 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, startDate, endDate); + ROW_MAPPER, userId, toDbDateTime(startDate), toDbDateTime(endDate)); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 9b44538..37b542d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.web; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import ru.javawebinar.topjava.Profiles; import org.springframework.util.StringUtils; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.web.meal.MealRestController; @@ -23,13 +23,16 @@ public class MealServlet extends HttpServlet { - private ConfigurableApplicationContext springContext; + private ClassPathXmlApplicationContext springContext; private MealRestController mealController; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - springContext = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/spring-db.xml"); + springContext = new ClassPathXmlApplicationContext(new String[]{"spring/spring-app.xml", "spring/spring-db.xml"}, false); +// springContext.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); + springContext.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); + springContext.refresh(); mealController = springContext.getBean(MealRestController.class); } diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 15187b1..26f8086 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -1,7 +1,6 @@ package ru.javawebinar.topjava; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.GenericXmlApplicationContext; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.MealTo; @@ -17,18 +16,22 @@ public class SpringMain { public static void main(String[] args) { // java 7 automatic resource management - try (ConfigurableApplicationContext appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml")) { + try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) { + appCtx.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); + appCtx.load("spring/spring-app.xml", "spring/spring-db.xml"); + appCtx.refresh(); + System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); AdminRestController adminUserController = appCtx.getBean(AdminRestController.class); adminUserController.create(new User(null, "userName", "email@mail.ru", "password", Role.ROLE_ADMIN)); System.out.println(); MealRestController mealController = appCtx.getBean(MealRestController.class); - List filteredMealsWithExcess = + List filteredMealsWithExceeded = mealController.getBetween( LocalDate.of(2015, Month.MAY, 30), LocalTime.of(7, 0), LocalDate.of(2015, Month.MAY, 31), LocalTime.of(11, 0)); - filteredMealsWithExcess.forEach(System.out::println); + filteredMealsWithExceeded.forEach(System.out::println); } } } From 6576f9c6ecf9031dc1f4a9877ebb4ac0f452c9fd Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:10:59 +0300 Subject: [PATCH 051/125] 6_05_update_hsqldb --- pom.xml | 2 +- .../jdbc/JdbcMealRepositoryImpl.java | 42 +++---------------- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/pom.xml b/pom.xml index 4b1996f..3d723e5 100644 --- a/pom.xml +++ b/pom.xml @@ -183,7 +183,7 @@ org.hsqldb hsqldb - 2.3.4 + 2.4.1 diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index 7c35372..80bbdb5 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -1,6 +1,6 @@ package ru.javawebinar.topjava.repository.jdbc; -import org.springframework.context.annotation.Profile; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; @@ -9,16 +9,14 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; -import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; -import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.List; @Repository -public abstract class JdbcMealRepositoryImpl implements MealRepository { +public class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); @@ -28,7 +26,8 @@ public abstract class JdbcMealRepositoryImpl implements MealRepository { private final SimpleJdbcInsert insertMeal; - JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + @Autowired + public JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { this.insertMeal = new SimpleJdbcInsert(jdbcTemplate) .withTableName("meals") .usingGeneratedKeyColumns("id"); @@ -37,42 +36,13 @@ public abstract class JdbcMealRepositoryImpl implements MealRepository { this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; } - - protected abstract T toDbDateTime(LocalDateTime ldt); - - @Repository - @Profile(Profiles.POSTGRES_DB) - public static class Java8JdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { - public Java8JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { - super(jdbcTemplate, namedParameterJdbcTemplate); - } - - @Override - protected LocalDateTime toDbDateTime(LocalDateTime ldt) { - return ldt; - } - } - - @Repository - @Profile(Profiles.HSQL_DB) - public static class TimestampJdbcMealRepositoryImpl extends JdbcMealRepositoryImpl { - public TimestampJdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { - super(jdbcTemplate, namedParameterJdbcTemplate); - } - - @Override - protected Timestamp toDbDateTime(LocalDateTime ldt) { - return Timestamp.valueOf(ldt); - } - } - @Override public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) .addValue("description", meal.getDescription()) .addValue("calories", meal.getCalories()) - .addValue("date_time", toDbDateTime(meal.getDateTime())) + .addValue("date_time", meal.getDateTime()) .addValue("user_id", userId); if (meal.isNew()) { @@ -112,6 +82,6 @@ public List getAll(int userId) { 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, toDbDateTime(startDate), toDbDateTime(endDate)); + ROW_MAPPER, userId, startDate, endDate); } } From dd366500f8dd3519d6629a4b6514ec166363affe Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:13:00 +0300 Subject: [PATCH 052/125] 6_06_HW5_optional_fetch_join --- .../java/ru/javawebinar/topjava/model/User.java | 15 ++++++++++----- .../topjava/repository/MealRepository.java | 4 ++++ .../topjava/repository/UserRepository.java | 4 ++++ .../repository/datajpa/CrudMealRepository.java | 3 +++ .../repository/datajpa/CrudUserRepository.java | 3 +++ .../datajpa/DataJpaMealRepositoryImpl.java | 5 +++++ .../datajpa/DataJpaUserRepositoryImpl.java | 5 +++++ .../topjava/service/MealService.java | 2 ++ .../topjava/service/MealServiceImpl.java | 5 +++++ .../topjava/service/UserService.java | 2 ++ .../topjava/service/UserServiceImpl.java | 5 +++++ .../ru/javawebinar/topjava/UserTestData.java | 4 ++-- .../service/AbstractMealServiceTest.java | 2 +- .../service/AbstractUserServiceTest.java | 2 +- .../service/datajpa/DataJpaMealServiceTest.java | 17 +++++++++++++++++ .../service/datajpa/DataJpaUserServiceTest.java | 16 ++++++++++++++++ 16 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index e32787d..2733725 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -8,10 +8,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -import java.util.Collection; -import java.util.Date; -import java.util.EnumSet; -import java.util.Set; +import java.util.*; import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; @@ -42,7 +39,7 @@ public class User extends AbstractNamedEntity { @Column(name = "enabled", nullable = false, columnDefinition = "bool default true") private boolean enabled = true; - @Column(name = "registered", columnDefinition = "timestamp default now()") + @Column(name = "registered", nullable = false, columnDefinition = "timestamp default now()") @NotNull private Date registered = new Date(); @@ -56,6 +53,10 @@ public class User extends AbstractNamedEntity { @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") + @OrderBy("dateTime DESC") + protected List meals; + public User() { } @@ -125,6 +126,10 @@ public void setRoles(Collection roles) { this.roles = CollectionUtils.isEmpty(roles) ? EnumSet.noneOf(Role.class) : EnumSet.copyOf(roles); } + public List getMeals() { + return meals; + } + @Override public String toString() { return "User{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java index f0578ff..6d717ec 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/MealRepository.java @@ -20,4 +20,8 @@ public interface MealRepository { // ORDERED dateTime desc List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId); + + default Meal getWithUser(int id, int 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 index c37b84d..6c999c9 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/UserRepository.java @@ -17,4 +17,8 @@ public interface UserRepository { User getByEmail(String email); List getAll(); + + default User getWithMeals(int id) { + throw new UnsupportedOperationException(); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java index 2cd7703..5ae264c 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudMealRepository.java @@ -28,4 +28,7 @@ public interface CrudMealRepository extends JpaRepository { @SuppressWarnings("JpaQlInspection") @Query("SELECT m from Meal 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 m FROM Meal m JOIN FETCH m.user WHERE m.id = ?1 and m.user.id = ?2") + Meal getWithUser(int id, int userId); } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java index cd7460d..fa2bdc4 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -30,4 +30,7 @@ public interface CrudUserRepository extends JpaRepository { 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(int id); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java index 579bb78..4db0e63 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaMealRepositoryImpl.java @@ -47,4 +47,9 @@ public List getAll(int userId) { public List getBetween(LocalDateTime startDate, LocalDateTime endDate, int userId) { return crudMealRepository.getBetween(startDate, endDate, userId); } + + @Override + public Meal getWithUser(int id, int userId) { + return crudMealRepository.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 index aa1615e..4e0b1ac 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/DataJpaUserRepositoryImpl.java @@ -39,4 +39,9 @@ public User getByEmail(String email) { public List getAll() { return crudRepository.findAll(SORT_NAME_EMAIL); } + + @Override + public User getWithMeals(int id) { + return crudRepository.getWithMeals(id); + } } diff --git a/src/main/java/ru/javawebinar/topjava/service/MealService.java b/src/main/java/ru/javawebinar/topjava/service/MealService.java index 94cbaaa..f525c9a 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealService.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealService.java @@ -27,4 +27,6 @@ default List getBetweenDates(@Nullable LocalDate startDate, @Nullable Loca void update(Meal meal, int userId) throws NotFoundException; Meal create(Meal meal, int userId); + + Meal getWithUser(int id, int userId); } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java index 276609d..04bdb5b 100644 --- a/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/MealServiceImpl.java @@ -54,4 +54,9 @@ public Meal create(Meal meal, int userId) { Assert.notNull(meal, "meal must not be null"); return repository.save(meal, userId); } + + @Override + public Meal getWithUser(int id, int userId) { + return checkNotFoundWithId(repository.getWithUser(id, userId), id); + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserService.java b/src/main/java/ru/javawebinar/topjava/service/UserService.java index d0cf33e..aecb823 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserService.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserService.java @@ -19,4 +19,6 @@ public interface UserService { void update(User user); List getAll(); + + User getWithMeals(int id); } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index 84f1619..8173aa3 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -60,4 +60,9 @@ public void update(User user) { Assert.notNull(user, "user must not be null"); checkNotFoundWithId(repository.save(user), user.getId()); } + + @Override + public User getWithMeals(int id) { + return checkNotFoundWithId(repository.getWithMeals(id), id); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index ddb04d4..397af2b 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -16,7 +16,7 @@ public class UserTestData { public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); public static void assertMatch(User actual, User expected) { - assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles"); + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles", "meals"); } public static void assertMatch(Iterable actual, User... expected) { @@ -24,6 +24,6 @@ public static void assertMatch(Iterable actual, User... expected) { } public static void assertMatch(Iterable actual, Iterable expected) { - assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles").isEqualTo(expected); + assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles", "meals").isEqualTo(expected); } } diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index b3f976e..92d8cf9 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -15,7 +15,7 @@ public abstract class AbstractMealServiceTest extends AbstractServiceTest { @Autowired - private MealService service; + protected MealService service; @Test public void delete() throws Exception { diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 7b506d1..8e82c0b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -18,7 +18,7 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired - private UserService service; + protected UserService service; @Autowired private CacheManager cacheManager; diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java index e28cc6d..2b83636 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -1,10 +1,27 @@ package ru.javawebinar.topjava.service.datajpa; +import org.junit.Test; import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.AbstractMealServiceTest; +import ru.javawebinar.topjava.util.exception.NotFoundException; +import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; @ActiveProfiles(DATAJPA) public class DataJpaMealServiceTest extends AbstractMealServiceTest { + @Test + public void testGetWithUser() throws Exception { + Meal adminMeal = service.getWithUser(ADMIN_MEAL_ID, ADMIN_ID); + assertMatch(adminMeal, ADMIN_MEAL1); + UserTestData.assertMatch(adminMeal.getUser(), UserTestData.ADMIN); + } + + @Test(expected = NotFoundException.class) + public void testGetWithUserNotFound() throws Exception { + service.getWithUser(MEAL1_ID, ADMIN_ID); + } } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index a0757e7..b9d42b8 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -1,10 +1,26 @@ package ru.javawebinar.topjava.service.datajpa; +import org.junit.Test; import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.MealTestData; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.AbstractUserServiceTest; +import ru.javawebinar.topjava.util.exception.NotFoundException; import static ru.javawebinar.topjava.Profiles.DATAJPA; +import static ru.javawebinar.topjava.UserTestData.*; @ActiveProfiles(DATAJPA) public class DataJpaUserServiceTest extends AbstractUserServiceTest { + @Test + public void testGetWithMeals() throws Exception { + User user = service.getWithMeals(USER_ID); + assertMatch(user, USER); + MealTestData.assertMatch(user.getMeals(), MealTestData.MEALS); + } + + @Test(expected = NotFoundException.class) + public void testGetWithMealsNotFound() throws Exception { + service.getWithMeals(1); + } } \ No newline at end of file From 16fa5b8b941f3e4e14e41c9a4900ec6d17684cda Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:13:38 +0300 Subject: [PATCH 053/125] 6_07_HW5_graph_batch_size --- src/main/java/ru/javawebinar/topjava/model/User.java | 5 ++++- .../topjava/repository/datajpa/CrudUserRepository.java | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 2733725..d171bc9 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import org.hibernate.annotations.BatchSize; import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; @@ -15,7 +16,7 @@ @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 u FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email"), + @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u ORDER BY u.name, u.email"), }) @Entity @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) @@ -47,6 +48,8 @@ public class User extends AbstractNamedEntity { @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") @ElementCollection(fetch = FetchType.EAGER) +// @Fetch(FetchMode.SUBSELECT) + @BatchSize(size = 200) private Set roles; @Column(name = "calories_per_day", columnDefinition = "int default 2000") diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java index fa2bdc4..044bc4b 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.repository.datajpa; import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -15,7 +16,6 @@ public interface CrudUserRepository extends JpaRepository { @Transactional @Modifying -// @Query(name = User.DELETE) @Query("DELETE FROM User u WHERE u.id=:id") int delete(@Param("id") int id); @@ -31,6 +31,7 @@ public interface CrudUserRepository extends JpaRepository { User getByEmail(String email); - @Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id = ?1") + @EntityGraph(attributePaths = {"meals", "roles"}) + @Query("SELECT u FROM User u WHERE u.id=?1") User getWithMeals(int id); } From dc6125cfba5a66a687d0a2704f4e0d04d216346a Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:14:55 +0300 Subject: [PATCH 054/125] 6_08_add_test_validation --- .../javawebinar/topjava/util/ValidationUtil.java | 11 +++++++++++ .../topjava/service/AbstractMealServiceTest.java | 10 ++++++++++ .../topjava/service/AbstractServiceTest.java | 14 ++++++++++++++ .../topjava/service/AbstractUserServiceTest.java | 10 ++++++++++ 4 files changed, 45 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index cb51f3c..924b7f7 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -42,4 +42,15 @@ public static void assureIdConsistent(AbstractBaseEntity entity, int id) { throw new IllegalArgumentException(entity + " must be with id=" + id); } } + + // http://stackoverflow.com/a/28565320/548473 + public static Throwable getRootCause(Throwable t) { + Throwable result = t; + Throwable cause; + + while (null != (cause = result.getCause()) && (result != cause)) { + result = cause; + } + return result; + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 92d8cf9..ccc0cc1 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -5,9 +5,11 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; +import javax.validation.ConstraintViolationException; import java.time.LocalDate; import java.time.Month; +import static java.time.LocalDateTime.of; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; @@ -75,4 +77,12 @@ public void getBetween() throws Exception { LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID), MEAL3, MEAL2, MEAL1); } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), " ", 300), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.create(new Meal(null, null, "Description", 300), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 9), USER_ID), ConstraintViolationException.class); + validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 5001), USER_ID), ConstraintViolationException.class); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index 4341357..42168a3 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.service; +import org.junit.Assert; import org.junit.ClassRule; import org.junit.Rule; import org.junit.rules.ExpectedException; @@ -15,6 +16,9 @@ import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.TimingRules; +import static org.hamcrest.CoreMatchers.instanceOf; +import static ru.javawebinar.topjava.util.ValidationUtil.getRootCause; + @ContextConfiguration({ "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" @@ -36,4 +40,14 @@ abstract public class AbstractServiceTest { // needed only for java.util.logging (postgres driver) SLF4JBridgeHandler.install(); } + + // Check root cause in JUnit: https://github.com/junit-team/junit4/pull/778 + public void validateRootCause(Runnable runnable, Class exceptionClass) { + try { + runnable.run(); + Assert.fail("Expected " + exceptionClass.getName()); + } catch (Exception e) { + Assert.assertThat(getRootCause(e), instanceOf(exceptionClass)); + } + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 8e82c0b..d4c7470 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -9,6 +9,7 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.util.exception.NotFoundException; +import javax.validation.ConstraintViolationException; import java.util.Collections; import java.util.Date; import java.util.List; @@ -84,4 +85,13 @@ public void getAll() throws Exception { List all = service.getAll(); assertMatch(all, ADMIN, USER); } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 10001, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); + } } \ No newline at end of file From e849d36d198e439943dc86e95d1036a4e9a43f8e Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:47:29 +0300 Subject: [PATCH 055/125] 6_09_hibernate_cache --- pom.xml | 5 +++++ .../ru/javawebinar/topjava/model/User.java | 4 ++++ .../topjava/repository/JpaUtil.java | 22 +++++++++++++++++++ src/main/resources/spring/spring-db.xml | 10 +++++++++ .../service/AbstractUserServiceTest.java | 5 +++++ 5 files changed, 46 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java diff --git a/pom.xml b/pom.xml index 3d723e5..798e4a7 100644 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,11 @@ hibernate-validator ${hibernate-validator.version} + + org.hibernate + hibernate-jcache + ${hibernate.version} + diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index d171bc9..11dbf30 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,6 +1,8 @@ package ru.javawebinar.topjava.model; import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; @@ -13,6 +15,7 @@ import static ru.javawebinar.topjava.util.MealsUtil.DEFAULT_CALORIES_PER_DAY; +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @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"), @@ -44,6 +47,7 @@ public class User extends AbstractNamedEntity { @NotNull private Date registered = new Date(); + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Enumerated(EnumType.STRING) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "role") 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 0000000..d740b9f --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/repository/JpaUtil.java @@ -0,0 +1,22 @@ +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().evictAllRegions(); + } +} diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 76693d2..f5f1af4 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -64,6 +64,14 @@ + + + + + + @@ -78,6 +86,8 @@ + + diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index d4c7470..21508c6 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -7,6 +7,7 @@ import org.springframework.dao.DataAccessException; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.util.exception.NotFoundException; import javax.validation.ConstraintViolationException; @@ -24,9 +25,13 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private CacheManager cacheManager; + @Autowired + protected JpaUtil jpaUtil; + @Before public void setUp() throws Exception { cacheManager.getCache("users").clear(); + jpaUtil.clear2ndLevelHibernateCache(); } @Test From 4fefe9526f22e485809a49d1232ab78c51156f4b Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:48:33 +0300 Subject: [PATCH 056/125] 6_10_cascade_ddl --- .../ru/javawebinar/topjava/model/AbstractBaseEntity.java | 1 + src/main/java/ru/javawebinar/topjava/model/Meal.java | 3 +++ src/main/java/ru/javawebinar/topjava/model/User.java | 2 +- src/main/resources/spring/spring-db.xml | 7 +++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index cbb8d6f..bab2075 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -13,6 +13,7 @@ public abstract class AbstractBaseEntity implements Persistable { @Id @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ) + // @Column(name = "id", unique = true, nullable = false, columnDefinition = "integer default nextval('global_seq')") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq") // See https://hibernate.atlassian.net/browse/HHH-3718 and https://hibernate.atlassian.net/browse/HHH-12034 diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 48989e0..2eade30 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.model; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.Range; import javax.persistence.*; @@ -41,6 +43,7 @@ public class Meal extends AbstractBaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) @NotNull private User user; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 11dbf30..cadfc8a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -60,7 +60,7 @@ public class User extends AbstractNamedEntity { @Range(min = 10, max = 10000) private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY; - @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") + @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")//, cascade = CascadeType.REMOVE, orphanRemoval = true) @OrderBy("dateTime DESC") protected List meals; diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index f5f1af4..2112484 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -72,6 +72,13 @@ value="org.ehcache.jsr107.EhcacheCachingProvider"/> + + From ae72c83f9e80f770c119b22ded0db94e23fe2cb6 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:51:00 +0300 Subject: [PATCH 057/125] 6_11_spring_web --- pom.xml | 11 ++++++++--- .../ru/javawebinar/topjava/web/MealServlet.java | 16 +++------------- .../ru/javawebinar/topjava/web/UserServlet.java | 16 +++++++++++++++- src/main/webapp/WEB-INF/web.xml | 16 ++++++++++++++++ 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 798e4a7..4f868d7 100644 --- a/pom.xml +++ b/pom.xml @@ -149,9 +149,9 @@ - javax.servlet - javax.servlet-api - 4.0.1 + org.apache.tomcat + tomcat-servlet-api + ${tomcat.version} provided @@ -161,6 +161,11 @@ 1.2 + + org.springframework + spring-web + + junit diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java index 37b542d..0ab23e7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.web; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import ru.javawebinar.topjava.Profiles; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.util.StringUtils; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.web.meal.MealRestController; @@ -23,25 +23,15 @@ public class MealServlet extends HttpServlet { - private ClassPathXmlApplicationContext springContext; private MealRestController mealController; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - springContext = new ClassPathXmlApplicationContext(new String[]{"spring/spring-app.xml", "spring/spring-db.xml"}, false); -// springContext.setConfigLocations("spring/spring-app.xml", "spring/spring-db.xml"); - springContext.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); - springContext.refresh(); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); mealController = springContext.getBean(MealRestController.class); } - @Override - public void destroy() { - springContext.close(); - super.destroy(); - } - @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index 2260234..909e6e4 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -1,7 +1,11 @@ package ru.javawebinar.topjava.web; import org.slf4j.Logger; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import ru.javawebinar.topjava.web.user.AdminRestController; +import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -13,6 +17,15 @@ public class UserServlet extends HttpServlet { private static final Logger log = getLogger(UserServlet.class); + private AdminRestController adminController; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + adminController = springContext.getBean(AdminRestController.class); + } + @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int userId = Integer.parseInt(request.getParameter("userId")); @@ -22,7 +35,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.debug("forward to users"); + log.debug("getAll"); + request.setAttribute("users", adminController.getAll()); request.getRequestDispatcher("/users.jsp").forward(request, response); } } diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index bd98d3b..0a3127a 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -6,6 +6,22 @@ Topjava + + spring.profiles.default + postgres,datajpa + + + + contextConfigLocation + + classpath:spring/spring-app.xml + classpath:spring/spring-db.xml + + + + + org.springframework.web.context.ContextLoaderListener + userServlet ru.javawebinar.topjava.web.UserServlet From 51ec336829ba2f18d28a7699691f7a8d156d63c0 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:52:46 +0300 Subject: [PATCH 058/125] 6_12_jsp_jstl_i18n --- src/main/resources/messages/app.properties | 12 ++++++ src/main/resources/messages/app_ru.properties | 12 ++++++ src/main/webapp/css/style.css | 8 ++++ src/main/webapp/fragments/bodyHeader.jsp | 5 +++ src/main/webapp/fragments/footer.jsp | 5 +++ src/main/webapp/fragments/headTag.jsp | 10 +++++ src/main/webapp/index.html | 18 --------- src/main/webapp/index.jsp | 26 ++++++++++++ src/main/webapp/meals.jsp | 5 +-- src/main/webapp/users.jsp | 40 ++++++++++++++++--- 10 files changed, 115 insertions(+), 26 deletions(-) create mode 100644 src/main/resources/messages/app.properties create mode 100644 src/main/resources/messages/app_ru.properties create mode 100644 src/main/webapp/fragments/bodyHeader.jsp create mode 100644 src/main/webapp/fragments/footer.jsp create mode 100644 src/main/webapp/fragments/headTag.jsp delete mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/index.jsp diff --git a/src/main/resources/messages/app.properties b/src/main/resources/messages/app.properties new file mode 100644 index 0000000..ccd4c66 --- /dev/null +++ b/src/main/resources/messages/app.properties @@ -0,0 +1,12 @@ +app.title=Calories management +app.home=Home +app.footer=Internship Spring 5/JPA Enterprise (Topjava) application +app.login=Login as +user.title=Users +user.name=Name +user.email=Email +user.roles=Roles +user.active=Active +user.registered=Registered +meal.title=Meals +common.select=Select \ No newline at end of file diff --git a/src/main/resources/messages/app_ru.properties b/src/main/resources/messages/app_ru.properties new file mode 100644 index 0000000..edbe413 --- /dev/null +++ b/src/main/resources/messages/app_ru.properties @@ -0,0 +1,12 @@ +app.title=\u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 +app.home=\u0413\u043B\u0430\u0432\u043D\u0430\u044F +app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0441\u0442\u0430\u0436\u0438\u0440\u043E\u0432\u043A\u0438 Spring 5/JPA Enterprise (Topjava) +app.login=\u0417\u0430\u0439\u0442\u0438 \u043A\u0430\u043A +user.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 +user.name=\u0418\u043C\u044F +user.email=\u041F\u043E\u0447\u0442\u0430 +user.roles=\u0420\u043E\u043B\u0438 +user.active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439 +user.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D +meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 +common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \ No newline at end of file diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css index 26a14ce..bd674ca 100644 --- a/src/main/webapp/css/style.css +++ b/src/main/webapp/css/style.css @@ -22,3 +22,11 @@ tr[data-mealExcess="false"] { tr[data-mealExcess="true"] { color: red; } + +header, footer { + background: none repeat scroll 0 0 #A6C9E2; + color: #2E6E9E; + font-size: 20px; + padding: 5px 20px; + margin: 6px 0; +} \ No newline at end of file diff --git a/src/main/webapp/fragments/bodyHeader.jsp b/src/main/webapp/fragments/bodyHeader.jsp new file mode 100644 index 0000000..16432ec --- /dev/null +++ b/src/main/webapp/fragments/bodyHeader.jsp @@ -0,0 +1,5 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + +
     | 
    \ No newline at end of file diff --git a/src/main/webapp/fragments/footer.jsp b/src/main/webapp/fragments/footer.jsp new file mode 100644 index 0000000..c59bff2 --- /dev/null +++ b/src/main/webapp/fragments/footer.jsp @@ -0,0 +1,5 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + +
    +
    \ No newline at end of file diff --git a/src/main/webapp/fragments/headTag.jsp b/src/main/webapp/fragments/headTag.jsp new file mode 100644 index 0000000..c153278 --- /dev/null +++ b/src/main/webapp/fragments/headTag.jsp @@ -0,0 +1,10 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> + + + + + <fmt:message key="app.title"/> + + \ No newline at end of file diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html deleted file mode 100644 index 8864497..0000000 --- a/src/main/webapp/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - Java Enterprise (Topjava) - - -

    Проект Java Enterprise (Topjava)

    -
    -
    - Meals of  - - -
    - - diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 0000000..f4ab4b7 --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,26 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + +
    +
    + : + + +
      +
    • +
    • +
    +
    + + + \ No newline at end of file diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/meals.jsp index 00f53cd..54ef64b 100644 --- a/src/main/webapp/meals.jsp +++ b/src/main/webapp/meals.jsp @@ -4,13 +4,12 @@ <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> - Meal list + Calories management
    -

    Home

    -

    Meals

    +

    Meals

    From Date:
    diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp index acf19d7..ef6f7d1 100644 --- a/src/main/webapp/users.jsp +++ b/src/main/webapp/users.jsp @@ -1,10 +1,40 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + - - Users - + -

    Home

    -

    Users

    + + +
    +

    + + + + + + + + + + + + + + + + + + + + + +
    ${user.email}${user.roles}<%=user.isEnabled()%> +
    +
    + \ No newline at end of file From 2fcefab0dcee7b7717485dce2dab0b167c2e54cd Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:54:14 +0300 Subject: [PATCH 059/125] 6_13_tomcat_pool_jndi_cargo --- pom.xml | 42 ++++++++++++++++++ src/main/resources/db/tomcat.properties | 5 +++ src/main/resources/spring/spring-db.xml | 9 +++- src/main/resources/tomcat/context.xml | 57 +++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/tomcat.properties create mode 100644 src/main/resources/tomcat/context.xml diff --git a/pom.xml b/pom.xml index 4f868d7..b127329 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,48 @@ -Dfile.encoding=UTF-8 + + + + + org.codehaus.cargo + cargo-maven2-plugin + 1.7.3 + + + tomcat9x + + UTF-8 + tomcat,datajpa + + + + org.postgresql + postgresql + + + + + + + src/main/resources/tomcat/context.xml + conf/Catalina/localhost/ + context.xml.default + + + + + + ru.javawebinar + topjava + war + + ${project.build.finalName} + + + + + diff --git a/src/main/resources/db/tomcat.properties b/src/main/resources/db/tomcat.properties new file mode 100644 index 0000000..2e07368 --- /dev/null +++ b/src/main/resources/db/tomcat.properties @@ -0,0 +1,5 @@ +database.init=false +jdbc.initLocation=initDB.sql +jpa.showSql=true +hibernate.format_sql=true +hibernate.use_sql_comments=true \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 2112484..28697ed 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -5,11 +5,13 @@ xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" + xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd - http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd + http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd"> @@ -41,6 +43,11 @@ p:password="${database.password}"/> + + + + + diff --git a/src/main/resources/tomcat/context.xml b/src/main/resources/tomcat/context.xml new file mode 100644 index 0000000..9311d59 --- /dev/null +++ b/src/main/resources/tomcat/context.xml @@ -0,0 +1,57 @@ + + + + + + + + WEB-INF/web.xml + ${catalina.base}/conf/web.xml + + + + + + + + + From 2ec2206013f61e29aa7d1e97d2eb55b68c10acc4 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:56:09 +0300 Subject: [PATCH 060/125] 6_14_spring_webmvc --- pom.xml | 2 +- .../topjava/web/RootController.java | 34 +++++++++++++++++++ src/main/resources/logback.xml | 1 + src/main/resources/spring/spring-app.xml | 3 -- src/main/resources/spring/spring-mvc.xml | 18 ++++++++++ .../jsp}/fragments/bodyHeader.jsp | 4 ++- .../{ => WEB-INF/jsp}/fragments/footer.jsp | 0 .../{ => WEB-INF/jsp}/fragments/headTag.jsp | 2 +- src/main/webapp/{ => WEB-INF/jsp}/index.jsp | 0 .../webapp/{ => WEB-INF/jsp}/mealForm.jsp | 0 src/main/webapp/{ => WEB-INF/jsp}/meals.jsp | 0 src/main/webapp/{ => WEB-INF/jsp}/users.jsp | 0 src/main/webapp/WEB-INF/web.xml | 17 ++++++++++ src/main/webapp/{ => resources}/css/style.css | 0 14 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/RootController.java create mode 100644 src/main/resources/spring/spring-mvc.xml rename src/main/webapp/{ => WEB-INF/jsp}/fragments/bodyHeader.jsp (50%) rename src/main/webapp/{ => WEB-INF/jsp}/fragments/footer.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/fragments/headTag.jsp (85%) rename src/main/webapp/{ => WEB-INF/jsp}/index.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/mealForm.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/meals.jsp (100%) rename src/main/webapp/{ => WEB-INF/jsp}/users.jsp (100%) rename src/main/webapp/{ => resources}/css/style.css (100%) diff --git a/pom.xml b/pom.xml index b127329..ed898a8 100644 --- a/pom.xml +++ b/pom.xml @@ -205,7 +205,7 @@ org.springframework - spring-web + spring-webmvc 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 0000000..1b041f2 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -0,0 +1,34 @@ +package ru.javawebinar.topjava.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import ru.javawebinar.topjava.service.UserService; + +import javax.servlet.http.HttpServletRequest; + +@Controller +public class RootController { + @Autowired + private UserService service; + + @GetMapping("/") + public String root() { + return "index"; + } + + @GetMapping("/users") + public String users(Model model) { + model.addAttribute("users", service.getAll()); + return "users"; + } + + @PostMapping("/users") + public String setUser(HttpServletRequest request) { + int userId = Integer.valueOf(request.getParameter("userId")); + SecurityUtil.setAuthUserId(userId); + return "redirect:meals"; + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 12a4f63..809d4c9 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -21,6 +21,7 @@ + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 3bdb548..5d79841 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -17,7 +17,4 @@ - - - \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml new file mode 100644 index 0000000..7c71d5a --- /dev/null +++ b/src/main/resources/spring/spring-mvc.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp similarity index 50% rename from src/main/webapp/fragments/bodyHeader.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 16432ec..c027ba2 100644 --- a/src/main/webapp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -2,4 +2,6 @@ <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -
     | 
    \ No newline at end of file +
    +  |  +
    \ No newline at end of file diff --git a/src/main/webapp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp similarity index 100% rename from src/main/webapp/fragments/footer.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/footer.jsp diff --git a/src/main/webapp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp similarity index 85% rename from src/main/webapp/fragments/headTag.jsp rename to src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index c153278..da40d46 100644 --- a/src/main/webapp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -6,5 +6,5 @@ <fmt:message key="app.title"/> - + \ No newline at end of file diff --git a/src/main/webapp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp similarity index 100% rename from src/main/webapp/index.jsp rename to src/main/webapp/WEB-INF/jsp/index.jsp diff --git a/src/main/webapp/mealForm.jsp b/src/main/webapp/WEB-INF/jsp/mealForm.jsp similarity index 100% rename from src/main/webapp/mealForm.jsp rename to src/main/webapp/WEB-INF/jsp/mealForm.jsp diff --git a/src/main/webapp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp similarity index 100% rename from src/main/webapp/meals.jsp rename to src/main/webapp/WEB-INF/jsp/meals.jsp diff --git a/src/main/webapp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp similarity index 100% rename from src/main/webapp/users.jsp rename to src/main/webapp/WEB-INF/jsp/users.jsp diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 0a3127a..f90f54d 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -19,9 +19,25 @@ + org.springframework.web.context.ContextLoaderListener + + mvc-dispatcher + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + classpath:spring/spring-mvc.xml + + 1 + + + mvc-dispatcher + / + + + diff --git a/src/main/webapp/css/style.css b/src/main/webapp/resources/css/style.css similarity index 100% rename from src/main/webapp/css/style.css rename to src/main/webapp/resources/css/style.css From 665f81e9d299f4f4999fe5f446516f63bd420552 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sat, 23 Mar 2019 08:59:00 +0300 Subject: [PATCH 061/125] 6_15_spring_i18n --- .../messages => config}/app.properties | 0 .../messages => config}/app_ru.properties | 0 src/main/resources/spring/spring-mvc.xml | 18 ++++++++++++++++++ .../WEB-INF/jsp/fragments/bodyHeader.jsp | 7 +++---- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 5 ++--- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 5 ++--- src/main/webapp/WEB-INF/jsp/index.jsp | 12 +++++------- src/main/webapp/WEB-INF/jsp/users.jsp | 15 +++++++-------- 8 files changed, 37 insertions(+), 25 deletions(-) rename {src/main/resources/messages => config}/app.properties (100%) rename {src/main/resources/messages => config}/app_ru.properties (100%) diff --git a/src/main/resources/messages/app.properties b/config/app.properties similarity index 100% rename from src/main/resources/messages/app.properties rename to config/app.properties diff --git a/src/main/resources/messages/app_ru.properties b/config/app_ru.properties similarity index 100% rename from src/main/resources/messages/app_ru.properties rename to config/app_ru.properties diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 7c71d5a..ce12ddc 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -15,4 +15,22 @@ + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index c027ba2..8453798 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,7 +1,6 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -
    -  |  -
    \ No newline at end of file +  |  + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp index c59bff2..0935c44 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,5 +1,4 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
    -
    \ No newline at end of file +
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index da40d46..6d77694 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -1,10 +1,9 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> - - <fmt:message key="app.title"/> + <spring:message code="app.title"/> \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp index f4ab4b7..4833dbc 100644 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -1,8 +1,6 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> @@ -10,15 +8,15 @@
    - : - +
      -
    • -
    • +
    • +
    diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index ef6f7d1..e4aa345 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -1,8 +1,7 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> @@ -10,16 +9,16 @@
    -

    +

    - - - - - + + + + + From b044e288b65100f95d53ea9d77312fb2d7d9685c Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Mar 2019 23:28:24 +0300 Subject: [PATCH 062/125] 7_01_HW6_fix_tests --- .../ru/javawebinar/topjava/SpringMain.java | 2 +- .../service/AbstractJpaUserServiceTest.java | 34 +++++++++++++++++++ .../service/AbstractMealServiceTest.java | 2 ++ .../topjava/service/AbstractServiceTest.java | 11 ++++++ .../service/AbstractUserServiceTest.java | 15 -------- .../datajpa/DataJpaUserServiceTest.java | 4 +-- .../service/jpa/JpaUserServiceTest.java | 4 +-- .../user/InMemoryAdminRestControllerTest.java | 4 ++- src/test/resources/spring/inmemory.xml | 1 + 9 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java diff --git a/src/test/java/ru/javawebinar/topjava/SpringMain.java b/src/test/java/ru/javawebinar/topjava/SpringMain.java index 26f8086..2f83e6d 100644 --- a/src/test/java/ru/javawebinar/topjava/SpringMain.java +++ b/src/test/java/ru/javawebinar/topjava/SpringMain.java @@ -18,7 +18,7 @@ public static void main(String[] args) { // java 7 automatic resource management try (GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext()) { appCtx.getEnvironment().setActiveProfiles(Profiles.getActiveDbProfile(), Profiles.REPOSITORY_IMPLEMENTATION); - appCtx.load("spring/spring-app.xml", "spring/spring-db.xml"); + appCtx.load("spring/spring-app.xml", "spring/inmemory.xml"); appCtx.refresh(); System.out.println("Bean definition names: " + Arrays.toString(appCtx.getBeanDefinitionNames())); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java new file mode 100644 index 0000000..c3e7722 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java @@ -0,0 +1,34 @@ +package ru.javawebinar.topjava.service; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.JpaUtil; + +import javax.validation.ConstraintViolationException; +import java.util.Collections; +import java.util.Date; + +abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest { + + @Autowired + private JpaUtil jpaUtil; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + jpaUtil.clear2ndLevelHibernateCache(); + } + + @Test + public void testValidation() throws Exception { + validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); + validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 10001, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index ccc0cc1..5b602c5 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.service; +import org.junit.Assume; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.Meal; @@ -80,6 +81,7 @@ public void getBetween() throws Exception { @Test public void testValidation() throws Exception { + Assume.assumeTrue(isJpaBased()); validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), " ", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.create(new Meal(null, null, "Description", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 9), USER_ID), ConstraintViolationException.class); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index 42168a3..683ef8b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -8,12 +8,15 @@ import org.junit.rules.Stopwatch; import org.junit.runner.RunWith; import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import ru.javawebinar.topjava.ActiveDbProfileResolver; +import ru.javawebinar.topjava.Profiles; import ru.javawebinar.topjava.TimingRules; import static org.hamcrest.CoreMatchers.instanceOf; @@ -33,6 +36,9 @@ abstract public class AbstractServiceTest { @Rule public Stopwatch stopwatch = TimingRules.STOPWATCH; + @Autowired + public Environment env; + @Rule public ExpectedException thrown = ExpectedException.none(); @@ -41,6 +47,11 @@ abstract public class AbstractServiceTest { SLF4JBridgeHandler.install(); } + public boolean isJpaBased() { +// return Arrays.stream(env.getActiveProfiles()).noneMatch(Profiles.JDBC::equals); + return env.acceptsProfiles(org.springframework.core.env.Profiles.of(Profiles.JPA, Profiles.DATAJPA)); + } + // Check root cause in JUnit: https://github.com/junit-team/junit4/pull/778 public void validateRootCause(Runnable runnable, Class exceptionClass) { try { diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 21508c6..8e82c0b 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -7,10 +7,8 @@ import org.springframework.dao.DataAccessException; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.util.exception.NotFoundException; -import javax.validation.ConstraintViolationException; import java.util.Collections; import java.util.Date; import java.util.List; @@ -25,13 +23,9 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private CacheManager cacheManager; - @Autowired - protected JpaUtil jpaUtil; - @Before public void setUp() throws Exception { cacheManager.getCache("users").clear(); - jpaUtil.clear2ndLevelHibernateCache(); } @Test @@ -90,13 +84,4 @@ public void getAll() throws Exception { List all = service.getAll(); assertMatch(all, ADMIN, USER); } - - @Test - public void testValidation() throws Exception { - validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 9, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); - validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", "password", 10001, true, new Date(), Collections.emptySet())), ConstraintViolationException.class); - } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index b9d42b8..4c25bca 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -4,14 +4,14 @@ import org.springframework.test.context.ActiveProfiles; import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.service.AbstractUserServiceTest; +import ru.javawebinar.topjava.service.AbstractJpaUserServiceTest; import ru.javawebinar.topjava.util.exception.NotFoundException; import static ru.javawebinar.topjava.Profiles.DATAJPA; import static ru.javawebinar.topjava.UserTestData.*; @ActiveProfiles(DATAJPA) -public class DataJpaUserServiceTest extends AbstractUserServiceTest { +public class DataJpaUserServiceTest extends AbstractJpaUserServiceTest { @Test public void testGetWithMeals() throws Exception { User user = service.getWithMeals(USER_ID); diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java index d1b3e46..ffb1fea 100644 --- a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java @@ -1,10 +1,10 @@ package ru.javawebinar.topjava.service.jpa; import org.springframework.test.context.ActiveProfiles; -import ru.javawebinar.topjava.service.AbstractUserServiceTest; +import ru.javawebinar.topjava.service.AbstractJpaUserServiceTest; import static ru.javawebinar.topjava.Profiles.JPA; @ActiveProfiles(JPA) -public class JpaUserServiceTest extends AbstractUserServiceTest { +public class JpaUserServiceTest extends AbstractJpaUserServiceTest { } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java index 2346169..c3dfcba 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -27,7 +27,9 @@ public static void beforeClass() { @AfterClass public static void afterClass() { - appCtx.close(); +// May cause during JUnit "Cache is not alive (STATUS_SHUTDOWN)" as JUnit share Spring context for speed +// http://stackoverflow.com/questions/16281802/ehcache-shutdown-causing-an-exception-while-running-test-suite +// appCtx.close(); } @Before diff --git a/src/test/resources/spring/inmemory.xml b/src/test/resources/spring/inmemory.xml index c6a2710..5798d6f 100644 --- a/src/test/resources/spring/inmemory.xml +++ b/src/test/resources/spring/inmemory.xml @@ -4,4 +4,5 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + \ No newline at end of file From 352f34295e758857cb3f39656b56e8cd4571da16 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Mar 2019 23:31:08 +0300 Subject: [PATCH 063/125] 7_02_HW6_meals --- config/app.properties | 19 +++- config/app_ru.properties | 21 ++++- .../javawebinar/topjava/web/MealServlet.java | 92 ------------------- .../topjava/web/RootController.java | 16 +++- .../javawebinar/topjava/web/UserServlet.java | 42 --------- .../web/meal/AbstractMealController.java | 70 ++++++++++++++ .../topjava/web/meal/JspMealController.java | 70 ++++++++++++++ .../topjava/web/meal/MealRestController.java | 71 +------------- src/main/webapp/WEB-INF/jsp/mealForm.jsp | 24 ++--- src/main/webapp/WEB-INF/jsp/meals.jsp | 41 +++++---- src/main/webapp/WEB-INF/web.xml | 22 ----- 11 files changed, 225 insertions(+), 263 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java delete mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java diff --git a/config/app.properties b/config/app.properties index ccd4c66..ca6c456 100644 --- a/config/app.properties +++ b/config/app.properties @@ -2,11 +2,28 @@ app.title=Calories management app.home=Home app.footer=Internship Spring 5/JPA Enterprise (Topjava) application app.login=Login as + user.title=Users user.name=Name user.email=Email user.roles=Roles user.active=Active user.registered=Registered + meal.title=Meals -common.select=Select \ No newline at end of file +meal.edit=Edit meal +meal.add=Add meal +meal.filter=Filter +meal.startDate=From date +meal.endDate=To date +meal.startTime=From time +meal.endTime=To time +meal.description=Description +meal.dateTime=Date/Time +meal.calories=Calories + +common.select=Select +common.delete=Delete +common.update=Update +common.save=Save +common.cancel=Cancel \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index edbe413..6503394 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -1,12 +1,29 @@ app.title=\u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 app.home=\u0413\u043B\u0430\u0432\u043D\u0430\u044F -app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0441\u0442\u0430\u0436\u0438\u0440\u043E\u0432\u043A\u0438 Spring 5/JPA Enterprise (Topjava) +app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0443 Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery app.login=\u0417\u0430\u0439\u0442\u0438 \u043A\u0430\u043A + user.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 user.name=\u0418\u043C\u044F user.email=\u041F\u043E\u0447\u0442\u0430 user.roles=\u0420\u043E\u043B\u0438 user.active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439 user.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D + meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 -common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \ No newline at end of file +meal.edit=\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0435\u0434\u044B +meal.add=\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0435\u0434\u044B +meal.filter=\u041E\u0442\u0444\u0438\u043B\u044C\u0442\u0440\u043E\u0432\u0430\u0442\u044C +meal.startDate=\u041E\u0442 \u0434\u0430\u0442\u044B +meal.endDate=\u0414\u043E \u0434\u0430\u0442\u044B +meal.startTime=\u041E\u0442 \u0432\u0440\u0435\u043C\u0435\u043D\u0438 +meal.endTime=\u0414\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u0438 +meal.description=\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 +meal.dateTime=\u0414\u0430\u0442\u0430/\u0412\u0440\u0435\u043C\u044F +meal.calories=\u041A\u0430\u043B\u043E\u0440\u0438\u0438 + +common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C +common.delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C +common.update=\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C +common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C +common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java deleted file mode 100644 index 0ab23e7..0000000 --- a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java +++ /dev/null @@ -1,92 +0,0 @@ -package ru.javawebinar.topjava.web; - -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import org.springframework.util.StringUtils; -import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.web.meal.MealRestController; - -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.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.temporal.ChronoUnit; -import java.util.Objects; - -import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; -import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; - -public class MealServlet extends HttpServlet { - - private MealRestController mealController; - - @Override - public void init(ServletConfig config) throws ServletException { - super.init(config); - WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - mealController = springContext.getBean(MealRestController.class); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - request.setCharacterEncoding("UTF-8"); - String action = request.getParameter("action"); - if (action == null) { - Meal meal = new Meal( - LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.parseInt(request.getParameter("calories"))); - - if (StringUtils.isEmpty(request.getParameter("id"))) { - mealController.create(meal); - } else { - mealController.update(meal, getId(request)); - } - response.sendRedirect("meals"); - - } else if ("filter".equals(action)) { - LocalDate startDate = parseLocalDate(request.getParameter("startDate")); - LocalDate endDate = parseLocalDate(request.getParameter("endDate")); - LocalTime startTime = parseLocalTime(request.getParameter("startTime")); - LocalTime endTime = parseLocalTime(request.getParameter("endTime")); - request.setAttribute("meals", mealController.getBetween(startDate, startTime, endDate, endTime)); - request.getRequestDispatcher("/meals.jsp").forward(request, response); - } - } - - @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); - mealController.delete(id); - response.sendRedirect("meals"); - break; - case "create": - case "update": - final Meal meal = "create".equals(action) ? - new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : - mealController.get(getId(request)); - request.setAttribute("meal", meal); - request.getRequestDispatcher("/mealForm.jsp").forward(request, response); - break; - case "all": - default: - request.setAttribute("meals", mealController.getAll()); - 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/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 1b041f2..48eb266 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -5,14 +5,19 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.util.MealsUtil; import javax.servlet.http.HttpServletRequest; @Controller public class RootController { @Autowired - private UserService service; + private UserService userService; + + @Autowired + private MealService mealService; @GetMapping("/") public String root() { @@ -21,7 +26,7 @@ public String root() { @GetMapping("/users") public String users(Model model) { - model.addAttribute("users", service.getAll()); + model.addAttribute("users", userService.getAll()); return "users"; } @@ -31,4 +36,11 @@ public String setUser(HttpServletRequest request) { SecurityUtil.setAuthUserId(userId); return "redirect:meals"; } + + @GetMapping("/meals") + public String meals(Model model) { + model.addAttribute("meals", + MealsUtil.getWithExcess(mealService.getAll(SecurityUtil.authUserId()), SecurityUtil.authUserCaloriesPerDay())); + return "meals"; + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java deleted file mode 100644 index 909e6e4..0000000 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.javawebinar.topjava.web; - -import org.slf4j.Logger; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import ru.javawebinar.topjava.web.user.AdminRestController; - -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 static org.slf4j.LoggerFactory.getLogger; - -public class UserServlet extends HttpServlet { - private static final Logger log = getLogger(UserServlet.class); - - private AdminRestController adminController; - - @Override - public void init(ServletConfig config) throws ServletException { - super.init(config); - WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - adminController = springContext.getBean(AdminRestController.class); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - int userId = Integer.parseInt(request.getParameter("userId")); - SecurityUtil.setAuthUserId(userId); - response.sendRedirect("meals"); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.debug("getAll"); - request.setAttribute("users", adminController.getAll()); - request.getRequestDispatcher("/users.jsp").forward(request, response); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java new file mode 100644 index 0000000..e27b92a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/AbstractMealController.java @@ -0,0 +1,70 @@ +package ru.javawebinar.topjava.web.meal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.to.MealTo; +import ru.javawebinar.topjava.util.MealsUtil; +import ru.javawebinar.topjava.web.SecurityUtil; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; +import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; + +public abstract class AbstractMealController { + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Autowired + private MealService service; + + public Meal get(int id) { + int userId = SecurityUtil.authUserId(); + log.info("get meal {} for user {}", id, userId); + return service.get(id, userId); + } + + public void delete(int id) { + int userId = SecurityUtil.authUserId(); + log.info("delete meal {} for user {}", id, userId); + service.delete(id, userId); + } + + public List getAll() { + int userId = SecurityUtil.authUserId(); + log.info("getAll for user {}", userId); + return MealsUtil.getWithExcess(service.getAll(userId), SecurityUtil.authUserCaloriesPerDay()); + } + + public Meal create(Meal meal) { + int userId = SecurityUtil.authUserId(); + checkNew(meal); + log.info("create {} for user {}", meal, userId); + return service.create(meal, userId); + } + + public void update(Meal meal, int id) { + int userId = SecurityUtil.authUserId(); + assureIdConsistent(meal, id); + log.info("update {} for user {}", meal, userId); + service.update(meal, userId); + } + + /** + *
      Filter separately + *
    1. by date
    2. + *
    3. by time for every date
    4. + *
    + */ + public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { + int userId = SecurityUtil.authUserId(); + log.info("getBetween dates({} - {}) time({} - {}) for user {}", startDate, endDate, startTime, endTime, userId); + + List mealsDateFiltered = service.getBetweenDates(startDate, endDate, userId); + return MealsUtil.getFilteredWithExcess(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), startTime, endTime); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java new file mode 100644 index 0000000..722db64 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -0,0 +1,70 @@ +package ru.javawebinar.topjava.web.meal; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.javawebinar.topjava.model.Meal; + +import javax.servlet.http.HttpServletRequest; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; + +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; + +@Controller +@RequestMapping(value = "/meals") +public class JspMealController extends AbstractMealController { + + @GetMapping("/delete") + public String delete(HttpServletRequest request) { + super.delete(getId(request)); + return "redirect:meals"; + } + + @GetMapping("/update") + public String update(HttpServletRequest request, Model model) { + model.addAttribute("meal", super.get(getId(request))); + return "mealForm"; + } + + @GetMapping("/create") + public String create(Model model) { + model.addAttribute("meal", new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000)); + return "mealForm"; + } + + @PostMapping + public String updateOrCreate(HttpServletRequest request) { + Meal meal = new Meal(LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.valueOf(request.getParameter("calories"))); + + if (request.getParameter("id").isEmpty()) { + super.create(meal); + } else { + super.update(meal, getId(request)); + } + return "redirect:meals"; + } + + @PostMapping("/filter") + public String getBetween(HttpServletRequest request, Model model) { + LocalDate startDate = parseLocalDate(request.getParameter("startDate")); + LocalDate endDate = parseLocalDate(request.getParameter("endDate")); + LocalTime startTime = parseLocalTime(request.getParameter("startTime")); + LocalTime endTime = parseLocalTime(request.getParameter("endTime")); + model.addAttribute("meals", super.getBetween(startDate, startTime, endDate, endTime)); + return "meals"; + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.valueOf(paramId); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index a3ed418..c3daf68 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,76 +1,7 @@ package ru.javawebinar.topjava.web.meal; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; -import ru.javawebinar.topjava.model.Meal; -import ru.javawebinar.topjava.service.MealService; -import ru.javawebinar.topjava.to.MealTo; -import ru.javawebinar.topjava.util.MealsUtil; -import ru.javawebinar.topjava.web.SecurityUtil; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; - -import static ru.javawebinar.topjava.util.ValidationUtil.assureIdConsistent; -import static ru.javawebinar.topjava.util.ValidationUtil.checkNew; @Controller -public class MealRestController { - private static final Logger log = LoggerFactory.getLogger(MealRestController.class); - - private final MealService service; - - @Autowired - public MealRestController(MealService service) { - this.service = service; - } - - public Meal get(int id) { - int userId = SecurityUtil.authUserId(); - log.info("get meal {} for user {}", id, userId); - return service.get(id, userId); - } - - public void delete(int id) { - int userId = SecurityUtil.authUserId(); - log.info("delete meal {} for user {}", id, userId); - service.delete(id, userId); - } - - public List getAll() { - int userId = SecurityUtil.authUserId(); - log.info("getAll for user {}", userId); - return MealsUtil.getWithExcess(service.getAll(userId), SecurityUtil.authUserCaloriesPerDay()); - } - - public Meal create(Meal meal) { - int userId = SecurityUtil.authUserId(); - checkNew(meal); - log.info("create {} for user {}", meal, userId); - return service.create(meal, userId); - } - - public void update(Meal meal, int id) { - int userId = SecurityUtil.authUserId(); - assureIdConsistent(meal, id); - log.info("update {} for user {}", meal, userId); - service.update(meal, userId); - } - - /** - *
      Filter separately - *
    1. by date
    2. - *
    3. by time for every date
    4. - *
    - */ - public List getBetween(LocalDate startDate, LocalTime startTime, LocalDate endDate, LocalTime endTime) { - int userId = SecurityUtil.authUserId(); - log.info("getBetween dates({} - {}) time({} - {}) for user {}", startDate, endDate, startTime, endTime, userId); - - List mealsDateFiltered = service.getBetweenDates(startDate, endDate, userId); - return MealsUtil.getFilteredWithExcess(mealsDateFiltered, SecurityUtil.authUserCaloriesPerDay(), startTime, endTime); - } +public class MealRestController extends AbstractMealController { } \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/mealForm.jsp b/src/main/webapp/WEB-INF/jsp/mealForm.jsp index d4509bb..999c1e2 100644 --- a/src/main/webapp/WEB-INF/jsp/mealForm.jsp +++ b/src/main/webapp/WEB-INF/jsp/mealForm.jsp @@ -1,34 +1,34 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> - - Meal - - + + +
    -

    Home

    -

    ${param.action == 'create' ? 'Create meal' : 'Edit meal'}

    -
    +

    +
    -
    DateTime:
    +
    :
    -
    Description:
    +
    :
    -
    Calories:
    +
    :
    - - + +
    + diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 54ef64b..573648e 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -1,43 +1,43 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="fn" uri="http://topjava.javawebinar.ru/functions" %> - - Calories management - - + + +
    -

    Meals

    -
    +

    + +
    -
    From Date:
    +
    :
    -
    To Date:
    +
    :
    -
    From Time:
    +
    :
    -
    To Time:
    +
    :
    - + -
    - Add Meal -
    +
    + +
    - - - + + + @@ -53,11 +53,12 @@ - - + +
    DateDescriptionCalories
    ${meal.description} ${meal.calories}UpdateDelete
    + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index f90f54d..3138385 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,26 +37,4 @@ / - - From 6a3ec1a6b6c828ef0a304f0f06d9054e4b111bd8 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Mar 2019 23:31:39 +0300 Subject: [PATCH 064/125] 7_03_HW6_fix_relative_url_utf8 --- .../topjava/web/meal/JspMealController.java | 4 ++-- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 1 + src/main/webapp/WEB-INF/web.xml | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java index 722db64..527f936 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java @@ -24,7 +24,7 @@ public class JspMealController extends AbstractMealController { @GetMapping("/delete") public String delete(HttpServletRequest request) { super.delete(getId(request)); - return "redirect:meals"; + return "redirect:/meals"; } @GetMapping("/update") @@ -50,7 +50,7 @@ public String updateOrCreate(HttpServletRequest request) { } else { super.update(meal, getId(request)); } - return "redirect:meals"; + return "redirect:/meals"; } @PostMapping("/filter") diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 6d77694..0c77f10 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -5,5 +5,6 @@ <spring:message code="app.title"/> + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 3138385..19fb2cc 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,4 +37,21 @@ / + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + + From 3799aba674b8cb5b98adf744ae794df7d32d6e74 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Mar 2019 23:32:30 +0300 Subject: [PATCH 065/125] 7_04_HW6_optional_add_role --- src/main/java/ru/javawebinar/topjava/model/User.java | 2 +- src/main/resources/db/populateDB.sql | 3 ++- src/test/java/ru/javawebinar/topjava/UserTestData.java | 6 +++--- .../topjava/service/AbstractUserServiceTest.java | 9 +++++---- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index cadfc8a..aca07e1 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -18,7 +18,7 @@ @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @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.BY_EMAIL, query = "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email=?1"), @NamedQuery(name = User.ALL_SORTED, query = "SELECT u FROM User u ORDER BY u.name, u.email"), }) @Entity diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index 29a97ef..067a777 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -9,7 +9,8 @@ INSERT INTO users (name, email, password) VALUES INSERT INTO user_roles (role, user_id) VALUES ('ROLE_USER', 100000), - ('ROLE_ADMIN', 100001); + ('ROLE_ADMIN', 100001), + ('ROLE_USER', 100001); INSERT INTO meals (date_time, description, calories, user_id) VALUES ('2015-05-30 10:00:00', 'Завтрак', 500, 100000), diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 397af2b..61ee11c 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -13,10 +13,10 @@ public class UserTestData { public static final int ADMIN_ID = START_SEQ + 1; public static final User USER = new User(USER_ID, "User", "user@yandex.ru", "password", Role.ROLE_USER); - public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN); + public static final User ADMIN = new User(ADMIN_ID, "Admin", "admin@gmail.com", "admin", Role.ROLE_ADMIN, Role.ROLE_USER); public static void assertMatch(User actual, User expected) { - assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "roles", "meals"); + assertThat(actual).isEqualToIgnoringGivenFields(expected, "registered", "meals"); } public static void assertMatch(Iterable actual, User... expected) { @@ -24,6 +24,6 @@ public static void assertMatch(Iterable actual, User... expected) { } public static void assertMatch(Iterable actual, Iterable expected) { - assertThat(actual).usingElementComparatorIgnoringFields("registered", "roles", "meals").isEqualTo(expected); + assertThat(actual).usingElementComparatorIgnoringFields("registered", "meals").isEqualTo(expected); } } diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 8e82c0b..caaadbc 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -55,8 +55,8 @@ public void deletedNotFound() throws Exception { @Test public void get() throws Exception { - User user = service.get(USER_ID); - assertMatch(user, USER); + User user = service.get(ADMIN_ID); + assertMatch(user, ADMIN); } @Test(expected = NotFoundException.class) @@ -66,8 +66,8 @@ public void getNotFound() throws Exception { @Test public void getByEmail() throws Exception { - User user = service.getByEmail("user@yandex.ru"); - assertMatch(user, USER); + User user = service.getByEmail("admin@gmail.com"); + assertMatch(user, ADMIN); } @Test @@ -75,6 +75,7 @@ public void update() throws Exception { User updated = new User(USER); updated.setName("UpdatedName"); updated.setCaloriesPerDay(330); + updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); service.update(updated); assertMatch(service.get(USER_ID), updated); } From 2486995b997e2c84cb0efadec5d61a28945a828b Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Mar 2019 23:32:55 +0300 Subject: [PATCH 066/125] 7_05_fix_hint_graph --- .../topjava/repository/datajpa/CrudUserRepository.java | 3 ++- .../topjava/repository/jpa/JpaUserRepositoryImpl.java | 2 ++ .../topjava/service/datajpa/DataJpaUserServiceTest.java | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java index 044bc4b..d547406 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java @@ -31,7 +31,8 @@ public interface CrudUserRepository extends JpaRepository { User getByEmail(String email); - @EntityGraph(attributePaths = {"meals", "roles"}) + // https://stackoverflow.com/a/46013654/548473 + @EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD) @Query("SELECT u FROM User u WHERE u.id=?1") User getWithMeals(int id); } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java index b82dbd7..55752ca 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jpa/JpaUserRepositoryImpl.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.repository.jpa; +import org.hibernate.jpa.QueryHints; import org.springframework.dao.support.DataAccessUtils; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -61,6 +62,7 @@ public boolean delete(int id) { public User getByEmail(String email) { List users = em.createNamedQuery(User.BY_EMAIL, User.class) .setParameter(1, email) + .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false) .getResultList(); return DataAccessUtils.singleResult(users); } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index 4c25bca..203f984 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -14,9 +14,9 @@ public class DataJpaUserServiceTest extends AbstractJpaUserServiceTest { @Test public void testGetWithMeals() throws Exception { - User user = service.getWithMeals(USER_ID); - assertMatch(user, USER); - MealTestData.assertMatch(user.getMeals(), MealTestData.MEALS); + User admin = service.getWithMeals(ADMIN_ID); + assertMatch(admin, ADMIN); + MealTestData.assertMatch(admin.getMeals(), MealTestData.ADMIN_MEAL2, MealTestData.ADMIN_MEAL1); } @Test(expected = NotFoundException.class) From d723f31f42721219d2761c4432aeeaf2fec7a369 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 28 Mar 2019 23:33:33 +0300 Subject: [PATCH 067/125] 7_06_HW6_optional_jdbc --- .../jdbc/JdbcMealRepositoryImpl.java | 4 ++ .../jdbc/JdbcUserRepositoryImpl.java | 61 ++++++++++++++++--- src/main/resources/spring/spring-db.xml | 9 ++- .../service/AbstractUserServiceTest.java | 4 +- 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java index 80bbdb5..93c1073 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepositoryImpl.java @@ -9,6 +9,7 @@ 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.Meal; import ru.javawebinar.topjava.repository.MealRepository; @@ -16,6 +17,7 @@ import java.util.List; @Repository +@Transactional(readOnly = true) public class JdbcMealRepositoryImpl implements MealRepository { private static final RowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(Meal.class); @@ -37,6 +39,7 @@ public JdbcMealRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTempl } @Override + @Transactional public Meal save(Meal meal, int userId) { MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) @@ -61,6 +64,7 @@ public Meal save(Meal meal, int userId) { } @Override + @Transactional public boolean delete(int id, int userId) { return jdbcTemplate.update("DELETE FROM meals WHERE id=? AND user_id=?", id, userId) != 0; } diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java index fde6581..fd95081 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcUserRepositoryImpl.java @@ -8,12 +8,16 @@ 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 org.springframework.util.CollectionUtils; +import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.UserRepository; -import java.util.List; +import java.util.*; @Repository +@Transactional(readOnly = true) public class JdbcUserRepositoryImpl implements UserRepository { private static final BeanPropertyRowMapper ROW_MAPPER = BeanPropertyRowMapper.newInstance(User.class); @@ -35,21 +39,31 @@ public JdbcUserRepositoryImpl(JdbcTemplate jdbcTemplate, NamedParameterJdbcTempl } @Override + @Transactional public User save(User user) { BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); if (user.isNew()) { Number newKey = insertUser.executeAndReturnKey(parameterSource); user.setId(newKey.intValue()); - } else if (namedParameterJdbcTemplate.update( - "UPDATE users SET name=:name, email=:email, password=:password, " + - "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { - return null; + insertRoles(user); + } else { + if (namedParameterJdbcTemplate.update( + "UPDATE users SET name=:name, email=:email, password=:password, " + + "registered=:registered, enabled=:enabled, calories_per_day=:caloriesPerDay WHERE id=:id", parameterSource) == 0) { + return null; + } + // Simplest implementation. + // More complicated : get user roles from DB and compare them with user.roles (assume that roles are changed rarely). + // If roles are changed, calculate difference in java and delete/insert them. + deleteRoles(user); + insertRoles(user); } return user; } @Override + @Transactional public boolean delete(int id) { return jdbcTemplate.update("DELETE FROM users WHERE id=?", id) != 0; } @@ -57,18 +71,49 @@ public boolean delete(int id) { @Override public User get(int id) { List users = jdbcTemplate.query("SELECT * FROM users WHERE id=?", ROW_MAPPER, id); - return DataAccessUtils.singleResult(users); + return setRoles(DataAccessUtils.singleResult(users)); } @Override public User getByEmail(String email) { // return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); List users = jdbcTemplate.query("SELECT * FROM users WHERE email=?", ROW_MAPPER, email); - return DataAccessUtils.singleResult(users); + return setRoles(DataAccessUtils.singleResult(users)); } @Override public List getAll() { - return jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + Map> map = new HashMap<>(); + jdbcTemplate.query("SELECT * FROM user_roles", rs -> { + map.computeIfAbsent(rs.getInt("user_id"), userId -> EnumSet.noneOf(Role.class)) + .add(Role.valueOf(rs.getString("role"))); + }); + List users = jdbcTemplate.query("SELECT * FROM users ORDER BY name, email", ROW_MAPPER); + users.forEach(u -> u.setRoles(map.get(u.getId()))); + return users; + } + + private void insertRoles(User u) { + Set roles = u.getRoles(); + if (!CollectionUtils.isEmpty(roles)) { + jdbcTemplate.batchUpdate("INSERT INTO user_roles (user_id, role) VALUES (?, ?)", roles, roles.size(), + (ps, role) -> { + ps.setInt(1, u.getId()); + ps.setString(2, role.name()); + }); + } + } + + 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/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 28697ed..a44fb72 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -20,6 +20,8 @@ + + @@ -57,6 +59,11 @@ + + + + @@ -95,8 +102,6 @@
    - - diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index caaadbc..6343511 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -31,7 +31,7 @@ public void setUp() throws Exception { @Test public void create() throws Exception { User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.ROLE_USER)); - User created = service.create(newUser); + User created = service.create(new User(newUser)); newUser.setId(created.getId()); assertMatch(newUser, created); assertMatch(service.getAll(), ADMIN, newUser, USER); @@ -76,7 +76,7 @@ public void update() throws Exception { updated.setName("UpdatedName"); updated.setCaloriesPerDay(330); updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); - service.update(updated); + service.update(new User(updated)); assertMatch(service.get(USER_ID), updated); } From 5d6bfcac881ebaaaf9136cdbaa221c83d7c7898b Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:42:30 +0300 Subject: [PATCH 068/125] 7_07_controller_test --- pom.xml | 14 ++++ .../topjava/AllActiveProfileResolver.java | 12 ++++ .../topjava/web/AbstractControllerTest.java | 69 +++++++++++++++++++ .../topjava/web/RootControllerTest.java | 29 ++++++++ 4 files changed, 124 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java diff --git a/pom.xml b/pom.xml index ed898a8..64d52c8 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 42.2.5 4.12 + 1.3 5.4.1.Final @@ -214,7 +215,20 @@ junit ${junit.version} test + + + hamcrest-core + org.hamcrest + + + + org.hamcrest + hamcrest-all + ${hamcrest.version} + test + + org.springframework spring-test 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 0000000..4d1d1e9 --- /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/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java new file mode 100644 index 0000000..f2e7bb0 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -0,0 +1,69 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; +import ru.javawebinar.topjava.AllActiveProfileResolver; +import ru.javawebinar.topjava.repository.JpaUtil; +import ru.javawebinar.topjava.service.UserService; + +import javax.annotation.PostConstruct; + +@ContextConfiguration({ + "classpath:spring/spring-app.xml", + "classpath:spring/spring-mvc.xml", + "classpath:spring/spring-db.xml" +}) +@WebAppConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@Transactional +@ActiveProfiles(resolver = AllActiveProfileResolver.class) +abstract public class AbstractControllerTest { + + private static final CharacterEncodingFilter CHARACTER_ENCODING_FILTER = new CharacterEncodingFilter(); + + static { + CHARACTER_ENCODING_FILTER.setEncoding("UTF-8"); + CHARACTER_ENCODING_FILTER.setForceEncoding(true); + } + + protected MockMvc mockMvc; + + @Autowired + private CacheManager cacheManager; + + @Autowired(required = false) + private JpaUtil jpaUtil; + + @Autowired + protected UserService userService; + + @Autowired + private WebApplicationContext webApplicationContext; + + @PostConstruct + private void postConstruct() { + mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .addFilter(CHARACTER_ENCODING_FILTER) + .build(); + } + + @Before + public void setUp() { + cacheManager.getCache("users").clear(); + if (jpaUtil != null) { + jpaUtil.clear2ndLevelHibernateCache(); + } + } +} diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java new file mode 100644 index 0000000..dfe9870 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava.web; + +import org.junit.Test; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static ru.javawebinar.topjava.UserTestData.USER; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; + +public class RootControllerTest extends AbstractControllerTest { + + @Test + public void testUsers() throws Exception { + mockMvc.perform(get("/users")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(view().name("users")) + .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) + .andExpect(model().attribute("users", hasSize(2))) + .andExpect(model().attribute("users", hasItem( + allOf( + hasProperty("id", is(START_SEQ)), + hasProperty("name", is(USER.getName())) + ) + ))); + } +} \ No newline at end of file From 8c19c2db2ee3ba8850a58ac1b6cc14d9e051adb6 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:46:15 +0300 Subject: [PATCH 069/125] 7_08_JUnit5 --- pom.xml | 16 +++--- .../javawebinar/topjava/TimingExtension.java | 36 ++++++++++++++ .../ru/javawebinar/topjava/TimingRules.java | 41 ---------------- .../service/AbstractJpaUserServiceTest.java | 10 ++-- .../service/AbstractMealServiceTest.java | 41 ++++++++-------- .../topjava/service/AbstractServiceTest.java | 49 +++++++------------ .../service/AbstractUserServiceTest.java | 42 +++++++++------- .../datajpa/DataJpaMealServiceTest.java | 14 +++--- .../datajpa/DataJpaUserServiceTest.java | 14 +++--- .../service/jdbc/JdbcMealServiceTest.java | 2 +- .../service/jdbc/JdbcUserServiceTest.java | 2 +- .../service/jpa/JpaMealServiceTest.java | 2 +- .../service/jpa/JpaUserServiceTest.java | 2 +- .../topjava/web/AbstractControllerTest.java | 17 +++---- .../topjava/web/RootControllerTest.java | 6 +-- ...InMemoryAdminRestControllerSpringTest.java | 33 ++++++------- .../user/InMemoryAdminRestControllerTest.java | 34 +++++++------ 17 files changed, 175 insertions(+), 186 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/TimingExtension.java delete mode 100644 src/test/java/ru/javawebinar/topjava/TimingRules.java diff --git a/pom.xml b/pom.xml index 64d52c8..c6194c2 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 42.2.5 - 4.12 + 5.4.1 1.3 @@ -53,6 +53,7 @@ + org.apache.maven.plugins maven-surefire-plugin 2.22.1 @@ -211,17 +212,12 @@ - junit - junit - ${junit.version} + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} test - - - hamcrest-core - org.hamcrest - - + org.hamcrest hamcrest-all diff --git a/src/test/java/ru/javawebinar/topjava/TimingExtension.java b/src/test/java/ru/javawebinar/topjava/TimingExtension.java new file mode 100644 index 0000000..c5ef4bc --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TimingExtension.java @@ -0,0 +1,36 @@ +package ru.javawebinar.topjava; + +import org.junit.jupiter.api.extension.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StopWatch; + +public class TimingExtension implements + BeforeTestExecutionCallback, AfterTestExecutionCallback, BeforeAllCallback, AfterAllCallback { + + private static final Logger log = LoggerFactory.getLogger("result"); + + private StopWatch stopWatch; + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + stopWatch = new StopWatch("Execution time of " + extensionContext.getRequiredTestClass().getSimpleName()); + } + + @Override + public void beforeTestExecution(ExtensionContext extensionContext) throws Exception { + log.info("Start stopWatch"); + stopWatch.start(extensionContext.getDisplayName()); + } + + @Override + public void afterTestExecution(ExtensionContext extensionContext) throws Exception { + stopWatch.stop(); + log.info("stop stopWatch"); + } + + @Override + public void afterAll(ExtensionContext extensionContext) throws Exception { + log.info('\n' + stopWatch.prettyPrint() + '\n'); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/TimingRules.java b/src/test/java/ru/javawebinar/topjava/TimingRules.java deleted file mode 100644 index 46f3339..0000000 --- a/src/test/java/ru/javawebinar/topjava/TimingRules.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.javawebinar.topjava; - -import org.junit.rules.ExternalResource; -import org.junit.rules.Stopwatch; -import org.junit.runner.Description; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.TimeUnit; - -public class TimingRules { - private static final Logger log = LoggerFactory.getLogger("result"); - - private static StringBuilder results = new StringBuilder(); - - // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev - public static final Stopwatch STOPWATCH = new Stopwatch() { - @Override - protected void finished(long nanos, Description description) { - String result = String.format("%-95s %7d", description.getDisplayName(), TimeUnit.NANOSECONDS.toMillis(nanos)); - results.append(result).append('\n'); - log.info(result + " ms\n"); - } - }; - - public static final ExternalResource SUMMARY = new ExternalResource() { - @Override - protected void before() throws Throwable { - results.setLength(0); - } - - @Override - protected void after() { - log.info("\n-------------------------------------------------------------------------------------------------------" + - "\nTest Duration, ms" + - "\n-------------------------------------------------------------------------------------------------------\n" + - results + - "-------------------------------------------------------------------------------------------------------\n"); - } - }; -} diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java index c3e7722..a5162f1 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractJpaUserServiceTest.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.service; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; @@ -16,15 +16,15 @@ abstract public class AbstractJpaUserServiceTest extends AbstractUserServiceTest @Autowired private JpaUtil jpaUtil; - @Before + @BeforeEach @Override - public void setUp() throws Exception { + void setUp() throws Exception { super.setUp(); jpaUtil.clear2ndLevelHibernateCache(); } @Test - public void testValidation() throws Exception { + void testValidation() throws Exception { validateRootCause(() -> service.create(new User(null, " ", "mail@yandex.ru", "password", Role.ROLE_USER)), ConstraintViolationException.class); validateRootCause(() -> service.create(new User(null, "User", " ", "password", Role.ROLE_USER)), ConstraintViolationException.class); validateRootCause(() -> service.create(new User(null, "User", "mail@yandex.ru", " ", Role.ROLE_USER)), ConstraintViolationException.class); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java index 5b602c5..49c3a11 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractMealServiceTest.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.service; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.util.exception.NotFoundException; @@ -11,6 +11,8 @@ import java.time.Month; import static java.time.LocalDateTime.of; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; import static ru.javawebinar.topjava.UserTestData.USER_ID; @@ -21,19 +23,19 @@ public abstract class AbstractMealServiceTest extends AbstractServiceTest { protected MealService service; @Test - public void delete() throws Exception { + void delete() throws Exception { service.delete(MEAL1_ID, USER_ID); assertMatch(service.getAll(USER_ID), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); } @Test - public void deleteNotFound() throws Exception { - thrown.expect(NotFoundException.class); - service.delete(MEAL1_ID, 1); + void deleteNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + service.delete(MEAL1_ID, 1)); } @Test - public void create() throws Exception { + void create() throws Exception { Meal newMeal = getCreated(); Meal created = service.create(newMeal, USER_ID); newMeal.setId(created.getId()); @@ -42,46 +44,45 @@ public void create() throws Exception { } @Test - public void get() throws Exception { + void get() throws Exception { Meal actual = service.get(ADMIN_MEAL_ID, ADMIN_ID); assertMatch(actual, ADMIN_MEAL1); } @Test - public void getNotFound() throws Exception { - thrown.expect(NotFoundException.class); - service.get(MEAL1_ID, ADMIN_ID); + void getNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + service.get(MEAL1_ID, ADMIN_ID)); } @Test - public void update() throws Exception { + void update() throws Exception { Meal updated = getUpdated(); service.update(updated, USER_ID); assertMatch(service.get(MEAL1_ID, USER_ID), updated); } @Test - public void updateNotFound() throws Exception { - thrown.expect(NotFoundException.class); - thrown.expectMessage("Not found entity with id=" + MEAL1_ID); - service.update(MEAL1, ADMIN_ID); + void updateNotFound() throws Exception { + NotFoundException e = assertThrows(NotFoundException.class, () -> service.update(MEAL1, ADMIN_ID)); + assertEquals(e.getMessage(), "Not found entity with id=" + MEAL1_ID); } @Test - public void getAll() throws Exception { + void getAll() throws Exception { assertMatch(service.getAll(USER_ID), MEALS); } @Test - public void getBetween() throws Exception { + void getBetween() throws Exception { assertMatch(service.getBetweenDates( LocalDate.of(2015, Month.MAY, 30), LocalDate.of(2015, Month.MAY, 30), USER_ID), MEAL3, MEAL2, MEAL1); } @Test - public void testValidation() throws Exception { - Assume.assumeTrue(isJpaBased()); + void testValidation() throws Exception { + Assumptions.assumeTrue(isJpaBased(), "Validation not supported (JPA only)"); validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), " ", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.create(new Meal(null, null, "Description", 300), USER_ID), ConstraintViolationException.class); validateRootCause(() -> service.create(new Meal(null, of(2015, Month.JUNE, 1, 18, 0), "Description", 9), USER_ID), ConstraintViolationException.class); diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java index 683ef8b..6c64054 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractServiceTest.java @@ -1,64 +1,51 @@ package ru.javawebinar.topjava.service; -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.rules.ExpectedException; -import org.junit.rules.ExternalResource; -import org.junit.rules.Stopwatch; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import ru.javawebinar.topjava.ActiveDbProfileResolver; import ru.javawebinar.topjava.Profiles; -import ru.javawebinar.topjava.TimingRules; +import ru.javawebinar.topjava.TimingExtension; -import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; import static ru.javawebinar.topjava.util.ValidationUtil.getRootCause; -@ContextConfiguration({ +@SpringJUnitConfig(locations = { "classpath:spring/spring-app.xml", "classpath:spring/spring-db.xml" }) -@RunWith(SpringJUnit4ClassRunner.class) +//@ExtendWith(SpringExtension.class) @ActiveProfiles(resolver = ActiveDbProfileResolver.class) @Sql(scripts = "classpath:db/populateDB.sql", config = @SqlConfig(encoding = "UTF-8")) -abstract public class AbstractServiceTest { - @ClassRule - public static ExternalResource summary = TimingRules.SUMMARY; - - @Rule - public Stopwatch stopwatch = TimingRules.STOPWATCH; +@ExtendWith(TimingExtension.class) +abstract class AbstractServiceTest { @Autowired - public Environment env; - - @Rule - public ExpectedException thrown = ExpectedException.none(); + private Environment env; static { // needed only for java.util.logging (postgres driver) SLF4JBridgeHandler.install(); } - public boolean isJpaBased() { + boolean isJpaBased() { // return Arrays.stream(env.getActiveProfiles()).noneMatch(Profiles.JDBC::equals); return env.acceptsProfiles(org.springframework.core.env.Profiles.of(Profiles.JPA, Profiles.DATAJPA)); } // Check root cause in JUnit: https://github.com/junit-team/junit4/pull/778 - public void validateRootCause(Runnable runnable, Class exceptionClass) { - try { - runnable.run(); - Assert.fail("Expected " + exceptionClass.getName()); - } catch (Exception e) { - Assert.assertThat(getRootCause(e), instanceOf(exceptionClass)); - } + void validateRootCause(Runnable runnable, Class exceptionClass) { + assertThrows(exceptionClass, () -> { + try { + runnable.run(); + } catch (Exception e) { + throw getRootCause(e); + } + }); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java index 6343511..abd030f 100644 --- a/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/AbstractUserServiceTest.java @@ -1,7 +1,7 @@ package ru.javawebinar.topjava.service; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.dao.DataAccessException; @@ -13,6 +13,7 @@ import java.util.Date; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertThrows; import static ru.javawebinar.topjava.UserTestData.*; public abstract class AbstractUserServiceTest extends AbstractServiceTest { @@ -23,13 +24,13 @@ public abstract class AbstractUserServiceTest extends AbstractServiceTest { @Autowired private CacheManager cacheManager; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { cacheManager.getCache("users").clear(); } @Test - public void create() throws Exception { + void create() throws Exception { User newUser = new User(null, "New", "new@gmail.com", "newPass", 1555, false, new Date(), Collections.singleton(Role.ROLE_USER)); User created = service.create(new User(newUser)); newUser.setId(created.getId()); @@ -37,41 +38,44 @@ public void create() throws Exception { assertMatch(service.getAll(), ADMIN, newUser, USER); } - @Test(expected = DataAccessException.class) - public void duplicateMailCreate() throws Exception { - service.create(new User(null, "Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER)); + @Test + void duplicateMailCreate() throws Exception { + assertThrows(DataAccessException.class, () -> + service.create(new User(null, "Duplicate", "user@yandex.ru", "newPass", Role.ROLE_USER))); } @Test - public void delete() throws Exception { + void delete() throws Exception { service.delete(USER_ID); assertMatch(service.getAll(), ADMIN); } - @Test(expected = NotFoundException.class) - public void deletedNotFound() throws Exception { - service.delete(1); + @Test + void deletedNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + service.delete(1)); } @Test - public void get() throws Exception { + void get() throws Exception { User user = service.get(ADMIN_ID); assertMatch(user, ADMIN); } - @Test(expected = NotFoundException.class) - public void getNotFound() throws Exception { - service.get(1); + @Test + void getNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + service.get(1)); } @Test - public void getByEmail() throws Exception { + void getByEmail() throws Exception { User user = service.getByEmail("admin@gmail.com"); assertMatch(user, ADMIN); } @Test - public void update() throws Exception { + void update() throws Exception { User updated = new User(USER); updated.setName("UpdatedName"); updated.setCaloriesPerDay(330); @@ -81,7 +85,7 @@ public void update() throws Exception { } @Test - public void getAll() throws Exception { + void getAll() throws Exception { List all = service.getAll(); assertMatch(all, ADMIN, USER); } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java index 2b83636..b14f40f 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaMealServiceTest.java @@ -1,27 +1,29 @@ package ru.javawebinar.topjava.service.datajpa; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.AbstractMealServiceTest; import ru.javawebinar.topjava.util.exception.NotFoundException; +import static org.junit.jupiter.api.Assertions.assertThrows; import static ru.javawebinar.topjava.MealTestData.*; import static ru.javawebinar.topjava.Profiles.DATAJPA; import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; @ActiveProfiles(DATAJPA) -public class DataJpaMealServiceTest extends AbstractMealServiceTest { +class DataJpaMealServiceTest extends AbstractMealServiceTest { @Test - public void testGetWithUser() throws Exception { + void testGetWithUser() throws Exception { Meal adminMeal = service.getWithUser(ADMIN_MEAL_ID, ADMIN_ID); assertMatch(adminMeal, ADMIN_MEAL1); UserTestData.assertMatch(adminMeal.getUser(), UserTestData.ADMIN); } - @Test(expected = NotFoundException.class) - public void testGetWithUserNotFound() throws Exception { - service.getWithUser(MEAL1_ID, ADMIN_ID); + @Test + void testGetWithUserNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + service.getWithUser(MEAL1_ID, ADMIN_ID)); } } diff --git a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java index 203f984..53d3759 100644 --- a/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/datajpa/DataJpaUserServiceTest.java @@ -1,26 +1,28 @@ package ru.javawebinar.topjava.service.datajpa; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import ru.javawebinar.topjava.MealTestData; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.AbstractJpaUserServiceTest; import ru.javawebinar.topjava.util.exception.NotFoundException; +import static org.junit.jupiter.api.Assertions.assertThrows; import static ru.javawebinar.topjava.Profiles.DATAJPA; import static ru.javawebinar.topjava.UserTestData.*; @ActiveProfiles(DATAJPA) -public class DataJpaUserServiceTest extends AbstractJpaUserServiceTest { +class DataJpaUserServiceTest extends AbstractJpaUserServiceTest { @Test - public void testGetWithMeals() throws Exception { + void testGetWithMeals() throws Exception { User admin = service.getWithMeals(ADMIN_ID); assertMatch(admin, ADMIN); MealTestData.assertMatch(admin.getMeals(), MealTestData.ADMIN_MEAL2, MealTestData.ADMIN_MEAL1); } - @Test(expected = NotFoundException.class) - public void testGetWithMealsNotFound() throws Exception { - service.getWithMeals(1); + @Test + void testGetWithMealsNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + service.getWithMeals(1)); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java index 9ff4ae6..aef5882 100644 --- a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcMealServiceTest.java @@ -6,5 +6,5 @@ import static ru.javawebinar.topjava.Profiles.JDBC; @ActiveProfiles(JDBC) -public class JdbcMealServiceTest extends AbstractMealServiceTest { +class JdbcMealServiceTest extends AbstractMealServiceTest { } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java index 419f68e..62ca766 100644 --- a/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/jdbc/JdbcUserServiceTest.java @@ -6,5 +6,5 @@ import static ru.javawebinar.topjava.Profiles.JDBC; @ActiveProfiles(JDBC) -public class JdbcUserServiceTest extends AbstractUserServiceTest { +class JdbcUserServiceTest extends AbstractUserServiceTest { } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java index 70e7bf8..aaf5dcd 100644 --- a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaMealServiceTest.java @@ -6,5 +6,5 @@ import static ru.javawebinar.topjava.Profiles.JPA; @ActiveProfiles(JPA) -public class JpaMealServiceTest extends AbstractMealServiceTest { +class JpaMealServiceTest extends AbstractMealServiceTest { } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java index ffb1fea..d903cf6 100644 --- a/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java +++ b/src/test/java/ru/javawebinar/topjava/service/jpa/JpaUserServiceTest.java @@ -6,5 +6,5 @@ import static ru.javawebinar.topjava.Profiles.JPA; @ActiveProfiles(JPA) -public class JpaUserServiceTest extends AbstractJpaUserServiceTest { +class JpaUserServiceTest extends AbstractJpaUserServiceTest { } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index f2e7bb0..2038aab 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -1,13 +1,10 @@ package ru.javawebinar.topjava.web; -import org.junit.Before; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; @@ -19,13 +16,13 @@ import javax.annotation.PostConstruct; -@ContextConfiguration({ +@SpringJUnitWebConfig(locations = { "classpath:spring/spring-app.xml", "classpath:spring/spring-mvc.xml", "classpath:spring/spring-db.xml" }) -@WebAppConfiguration -@RunWith(SpringJUnit4ClassRunner.class) +//@WebAppConfiguration +//@ExtendWith(SpringExtension.class) @Transactional @ActiveProfiles(resolver = AllActiveProfileResolver.class) abstract public class AbstractControllerTest { @@ -59,8 +56,8 @@ private void postConstruct() { .build(); } - @Before - public void setUp() { + @BeforeEach + void setUp() { cacheManager.getCache("users").clear(); if (jpaUtil != null) { jpaUtil.clear2ndLevelHibernateCache(); diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index dfe9870..d8708b3 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,6 +1,6 @@ package ru.javawebinar.topjava.web; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -9,10 +9,10 @@ import static ru.javawebinar.topjava.UserTestData.USER; import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; -public class RootControllerTest extends AbstractControllerTest { +class RootControllerTest extends AbstractControllerTest { @Test - public void testUsers() throws Exception { + void testUsers() throws Exception { mockMvc.perform(get("/users")) .andDo(print()) .andExpect(status().isOk()) diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java index e0962a9..2b98dfc 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerSpringTest.java @@ -1,12 +1,9 @@ package ru.javawebinar.topjava.web.user; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import ru.javawebinar.topjava.UserTestData; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.repository.inmemory.InMemoryUserRepositoryImpl; @@ -15,11 +12,12 @@ import java.util.Collection; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static ru.javawebinar.topjava.UserTestData.ADMIN; -@ContextConfiguration({"classpath:spring/spring-app.xml", "classpath:spring/inmemory.xml"}) -@RunWith(SpringRunner.class) -public class InMemoryAdminRestControllerSpringTest { +@SpringJUnitConfig(locations = {"classpath:spring/spring-app.xml", "classpath:spring/inmemory.xml"}) +class InMemoryAdminRestControllerSpringTest { @Autowired private AdminRestController controller; @@ -27,21 +25,22 @@ public class InMemoryAdminRestControllerSpringTest { @Autowired private InMemoryUserRepositoryImpl repository; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { repository.init(); } @Test - public void delete() throws Exception { + void testDelete() throws Exception { controller.delete(UserTestData.USER_ID); Collection users = controller.getAll(); - Assert.assertEquals(users.size(), 1); - Assert.assertEquals(users.iterator().next(), ADMIN); + assertEquals(users.size(), 1); + assertEquals(users.iterator().next(), ADMIN); } - @Test(expected = NotFoundException.class) - public void deleteNotFound() throws Exception { - controller.delete(10); + @Test + void testDeleteNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + controller.delete(10)); } } diff --git a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java index c3dfcba..aade941 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/InMemoryAdminRestControllerTest.java @@ -1,6 +1,9 @@ package ru.javawebinar.topjava.web.user; -import org.junit.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import ru.javawebinar.topjava.UserTestData; @@ -12,43 +15,46 @@ import java.util.Arrays; import java.util.Collection; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static ru.javawebinar.topjava.UserTestData.ADMIN; -public class InMemoryAdminRestControllerTest { +class InMemoryAdminRestControllerTest { private static ConfigurableApplicationContext appCtx; private static AdminRestController controller; - @BeforeClass - public static void beforeClass() { + @BeforeAll + static void beforeClass() { appCtx = new ClassPathXmlApplicationContext("spring/spring-app.xml", "spring/inmemory.xml"); System.out.println("\n" + Arrays.toString(appCtx.getBeanDefinitionNames()) + "\n"); controller = appCtx.getBean(AdminRestController.class); } - @AfterClass - public static void afterClass() { + @AfterAll + static void afterClass() { // May cause during JUnit "Cache is not alive (STATUS_SHUTDOWN)" as JUnit share Spring context for speed // http://stackoverflow.com/questions/16281802/ehcache-shutdown-causing-an-exception-while-running-test-suite // appCtx.close(); } - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { // re-initialize InMemoryUserRepositoryImpl repository = appCtx.getBean(InMemoryUserRepositoryImpl.class); repository.init(); } @Test - public void delete() throws Exception { + void delete() throws Exception { controller.delete(UserTestData.USER_ID); Collection users = controller.getAll(); - Assert.assertEquals(users.size(), 1); - Assert.assertEquals(users.iterator().next(), ADMIN); + assertEquals(users.size(), 1); + assertEquals(users.iterator().next(), ADMIN); } - @Test(expected = NotFoundException.class) - public void deleteNotFound() throws Exception { - controller.delete(10); + @Test + void deleteNotFound() throws Exception { + assertThrows(NotFoundException.class, () -> + controller.delete(10)); } } \ No newline at end of file From db4140ab3661188d2f0efe253f0d59cdd3f6f854 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:47:56 +0300 Subject: [PATCH 070/125] 7_09_rest_controller --- .../topjava/web/user/AdminRestController.java | 37 ++++++++++++++----- .../web/user/ProfileRestController.java | 15 ++++++-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index b37a8ed..26631c7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -1,40 +1,59 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.User; +import java.net.URI; import java.util.List; -@Controller +@RestController +@RequestMapping(value = AdminRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) public class AdminRestController extends AbstractUserController { + public static final String REST_URL = "/rest/admin/users"; + @Override + @GetMapping public List getAll() { return super.getAll(); } @Override - public User get(int id) { + @GetMapping("/{id}") + public User get(@PathVariable int id) { return super.get(id); } - @Override - public User create(User user) { - return super.create(user); + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity createWithLocation(@RequestBody User user) { + User created = super.create(user); + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(REST_URL + "/{id}") + .buildAndExpand(created.getId()).toUri(); + return ResponseEntity.created(uriOfNewResource).body(created); } @Override - public void delete(int id) { + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable int id) { super.delete(id); } @Override - public void update(User user, int id) { + @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void update(@RequestBody User user, @PathVariable int id) { super.update(user, id); } @Override - public User getByMail(String email) { + @GetMapping("/by") + public User getByMail(@RequestParam String email) { return super.getByMail(email); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index 7d3702c..07ea30e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -1,22 +1,31 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.stereotype.Controller; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; -@Controller +@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()); } - public void update(User user) { + @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(HttpStatus.NO_CONTENT) + public void update(@RequestBody User user) { super.update(user, authUserId()); } } \ No newline at end of file From 2ecfa0bfb7a52c47917a1471aa9f086c1a814821 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:51:02 +0300 Subject: [PATCH 071/125] 7_10_rest_test_jackson --- pom.xml | 8 ++++++ .../topjava/model/AbstractBaseEntity.java | 5 ++++ .../ru/javawebinar/topjava/model/User.java | 2 ++ .../web/user/AdminRestControllerTest.java | 25 +++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java diff --git a/pom.xml b/pom.xml index c6194c2..873580c 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 5.1.4.RELEASE 2.1.5.RELEASE + 2.9.8 9.0.16 @@ -210,6 +211,13 @@ spring-webmvc + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-json.version} + + org.junit.jupiter diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index bab2075..b44f06c 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,13 +1,18 @@ package ru.javawebinar.topjava.model; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import org.hibernate.Hibernate; import org.springframework.data.domain.Persistable; import javax.persistence.*; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public abstract class AbstractBaseEntity implements Persistable { public static final int START_SEQ = 100000; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index aca07e1..76e63e4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,5 +1,6 @@ package ru.javawebinar.topjava.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -62,6 +63,7 @@ public class User extends AbstractNamedEntity { @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")//, cascade = CascadeType.REMOVE, orphanRemoval = true) @OrderBy("dateTime DESC") + @JsonIgnore protected List meals; public User() { diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java new file mode 100644 index 0000000..d786378 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -0,0 +1,25 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import ru.javawebinar.topjava.web.AbstractControllerTest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; + +class AdminRestControllerTest extends AbstractControllerTest { + + private static final String REST_URL = AdminRestController.REST_URL + '/'; + + @Test + void testGet() throws Exception { + mockMvc.perform(get(REST_URL + ADMIN_ID)) + .andExpect(status().isOk()) + .andDo(print()) + // https://jira.spring.io/browse/SPR-14472 + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)); + } +} \ No newline at end of file From 087994171ef15513bf9ad07720fdba3ed6932f81 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:53:15 +0300 Subject: [PATCH 072/125] 7_11_jackson_object_mapper --- pom.xml | 10 ++++++ .../topjava/model/AbstractBaseEntity.java | 6 +--- .../ru/javawebinar/topjava/model/User.java | 3 +- .../topjava/web/json/JacksonObjectMapper.java | 31 +++++++++++++++++++ src/main/resources/spring/spring-mvc.xml | 11 ++++++- 5 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java diff --git a/pom.xml b/pom.xml index 873580c..4f5c7c0 100644 --- a/pom.xml +++ b/pom.xml @@ -217,6 +217,16 @@ jackson-databind ${jackson-json.version} + + com.fasterxml.jackson.datatype + jackson-datatype-hibernate5 + ${jackson-json.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-json.version} + diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java index b44f06c..e3b5f3f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractBaseEntity.java @@ -1,18 +1,14 @@ package ru.javawebinar.topjava.model; -import com.fasterxml.jackson.annotation.JsonAutoDetect; import org.hibernate.Hibernate; import org.springframework.data.domain.Persistable; import javax.persistence.*; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; - @MappedSuperclass // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access @Access(AccessType.FIELD) -@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) +//@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, isGetterVisibility = NONE, setterVisibility = NONE) public abstract class AbstractBaseEntity implements Persistable { public static final int START_SEQ = 100000; diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index 76e63e4..4daceef 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -1,6 +1,5 @@ package ru.javawebinar.topjava.model; -import com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -63,7 +62,7 @@ public class User extends AbstractNamedEntity { @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")//, cascade = CascadeType.REMOVE, orphanRemoval = true) @OrderBy("dateTime DESC") - @JsonIgnore +// @JsonIgnore protected List meals; public User() { 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 0000000..d91b100 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -0,0 +1,31 @@ +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.hibernate5.Hibernate5Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +/** + *

    + * 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 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); + } +} \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index ce12ddc..50f7bf4 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -5,7 +5,16 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> - + + + + + + + + + + From fa66d07ba5251e9985571ba04cb9fecf818ca222 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:55:18 +0300 Subject: [PATCH 073/125] 7_12_json_assert_tests --- pom.xml | 7 +++ .../topjava/web/json/JacksonObjectMapper.java | 6 +++ .../topjava/web/json/JsonUtil.java | 49 +++++++++++++++++++ src/main/resources/spring/spring-mvc.xml | 7 ++- .../topjava/web/json/JsonUtilTest.java | 27 ++++++++++ .../web/user/AdminRestControllerTest.java | 6 ++- 6 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java diff --git a/pom.xml b/pom.xml index 4f5c7c0..e053ea5 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ 5.4.1 1.3 + 1.5.0 5.4.1.Final @@ -242,6 +243,12 @@ ${hamcrest.version} test + + org.skyscreamer + jsonassert + ${jsonassert.version} + test + org.springframework diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java index d91b100..8237df9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -18,6 +18,8 @@ */ public class JacksonObjectMapper extends ObjectMapper { + private static final ObjectMapper MAPPER = new JacksonObjectMapper(); + private JacksonObjectMapper() { registerModule(new Hibernate5Module()); @@ -28,4 +30,8 @@ private JacksonObjectMapper() { 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 0000000..ed3b7b1 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java @@ -0,0 +1,49 @@ +package ru.javawebinar.topjava.web.json; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectReader; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static ru.javawebinar.topjava.web.json.JacksonObjectMapper.getMapper; + +public class JsonUtil { + + public static List readValues(String json, Class clazz) { + ObjectReader reader = getMapper().readerFor(clazz); + try { + return reader.readValues(json).readAll(); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid read array from JSON:\n'" + json + "'", e); + } + } + + public static T readValue(String json, Class clazz) { + try { + return getMapper().readValue(json, clazz); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid read from JSON:\n'" + json + "'", e); + } + } + + public static String writeValue(T obj) { + try { + return getMapper().writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e); + } + } + + public static String writeIgnoreProps(T obj, String... ignoreProps) { + Map map = getMapper().convertValue(obj, new TypeReference>() {}); + for (String prop : ignoreProps) { + map.remove(prop); + } + return writeValue(map); + } +} \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 50f7bf4..fa99f08 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -5,17 +5,16 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> + + - - - + - diff --git a/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java new file mode 100644 index 0000000..bc209c6 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.web.json; + +import org.junit.jupiter.api.Test; +import ru.javawebinar.topjava.model.Meal; + +import java.util.List; + +import static ru.javawebinar.topjava.MealTestData.*; + +class JsonUtilTest { + + @Test + void testReadWriteValue() throws Exception { + String json = JsonUtil.writeValue(ADMIN_MEAL1); + System.out.println(json); + Meal meal = JsonUtil.readValue(json, Meal.class); + assertMatch(meal, ADMIN_MEAL1); + } + + @Test + void testReadWriteValues() throws Exception { + String json = JsonUtil.writeValue(MEALS); + System.out.println(json); + List meals = JsonUtil.readValues(json, Meal.class); + assertMatch(meals, MEALS); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index d786378..af4e640 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -8,7 +8,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static ru.javawebinar.topjava.UserTestData.ADMIN_ID; +import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.web.json.JsonUtil.writeIgnoreProps; class AdminRestControllerTest extends AbstractControllerTest { @@ -20,6 +21,7 @@ void testGet() throws Exception { .andExpect(status().isOk()) .andDo(print()) // https://jira.spring.io/browse/SPR-14472 - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)); + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(content().json(writeIgnoreProps(ADMIN, "registered"))); } } \ No newline at end of file From 77058a6d911556f3e19372eefccc02edefde916f Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:56:21 +0300 Subject: [PATCH 074/125] 7_13_tests_refactoring --- pom.xml | 8 +-- .../topjava/web/json/JsonUtil.java | 12 ---- .../java/ru/javawebinar/topjava/TestUtil.java | 26 ++++++++ .../ru/javawebinar/topjava/UserTestData.java | 11 ++++ .../topjava/web/RootControllerTest.java | 23 ++++--- .../web/user/AdminRestControllerTest.java | 64 ++++++++++++++++++- .../web/user/ProfileRestControllerTest.java | 44 +++++++++++++ 7 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/TestUtil.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java diff --git a/pom.xml b/pom.xml index e053ea5..13b0142 100644 --- a/pom.xml +++ b/pom.xml @@ -239,16 +239,10 @@ org.hamcrest - hamcrest-all + hamcrest-core ${hamcrest.version} test - - org.skyscreamer - jsonassert - ${jsonassert.version} - test - org.springframework diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java index ed3b7b1..fda0459 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java @@ -1,14 +1,10 @@ package ru.javawebinar.topjava.web.json; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectReader; import java.io.IOException; -import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import static ru.javawebinar.topjava.web.json.JacksonObjectMapper.getMapper; @@ -38,12 +34,4 @@ public static String writeValue(T obj) { throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e); } } - - public static String writeIgnoreProps(T obj, String... ignoreProps) { - Map map = getMapper().convertValue(obj, new TypeReference>() {}); - for (String prop : ignoreProps) { - map.remove(prop); - } - return writeValue(map); - } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/TestUtil.java b/src/test/java/ru/javawebinar/topjava/TestUtil.java new file mode 100644 index 0000000..3c1b633 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/TestUtil.java @@ -0,0 +1,26 @@ +package ru.javawebinar.topjava; + +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.web.json.JsonUtil; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +public class TestUtil { + public static String getContent(MvcResult result) throws UnsupportedEncodingException { + return result.getResponse().getContentAsString(); + } + + public static T readFromJson(ResultActions action, Class clazz) throws UnsupportedEncodingException { + return JsonUtil.readValue(getContent(action.andReturn()), clazz); + } + + public static T readFromJsonMvcResult(MvcResult result, Class clazz) throws UnsupportedEncodingException { + return JsonUtil.readValue(getContent(result), clazz); + } + + public static List readListFromJsonMvcResult(MvcResult result, Class clazz) throws UnsupportedEncodingException { + return JsonUtil.readValues(getContent(result), clazz); + } +} diff --git a/src/test/java/ru/javawebinar/topjava/UserTestData.java b/src/test/java/ru/javawebinar/topjava/UserTestData.java index 61ee11c..bad378a 100644 --- a/src/test/java/ru/javawebinar/topjava/UserTestData.java +++ b/src/test/java/ru/javawebinar/topjava/UserTestData.java @@ -1,11 +1,14 @@ package ru.javawebinar.topjava; +import org.springframework.test.web.servlet.ResultMatcher; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static ru.javawebinar.topjava.TestUtil.readFromJsonMvcResult; +import static ru.javawebinar.topjava.TestUtil.readListFromJsonMvcResult; import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; public class UserTestData { @@ -26,4 +29,12 @@ public static void assertMatch(Iterable actual, User... expected) { public static void assertMatch(Iterable actual, Iterable expected) { assertThat(actual).usingElementComparatorIgnoringFields("registered", "meals").isEqualTo(expected); } + + public static ResultMatcher contentJson(User... expected) { + return result -> assertMatch(readListFromJsonMvcResult(result, User.class), List.of(expected)); + } + + public static ResultMatcher contentJson(User expected) { + return result -> assertMatch(readFromJsonMvcResult(result, User.class), expected); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index d8708b3..511908f 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,13 +1,15 @@ package ru.javawebinar.topjava.web; +import org.assertj.core.matcher.AssertionMatcher; import org.junit.jupiter.api.Test; +import ru.javawebinar.topjava.model.User; + +import java.util.List; -import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static ru.javawebinar.topjava.UserTestData.USER; -import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; +import static ru.javawebinar.topjava.UserTestData.*; class RootControllerTest extends AbstractControllerTest { @@ -18,12 +20,13 @@ void testUsers() throws Exception { .andExpect(status().isOk()) .andExpect(view().name("users")) .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) - .andExpect(model().attribute("users", hasSize(2))) - .andExpect(model().attribute("users", hasItem( - allOf( - hasProperty("id", is(START_SEQ)), - hasProperty("name", is(USER.getName())) - ) - ))); + .andExpect(model().attribute("users", + new AssertionMatcher>() { + @Override + public void assertion(List actual) throws AssertionError { + assertMatch(actual, ADMIN, USER); + } + } + )); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index af4e640..0ccc412 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -2,14 +2,20 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.web.AbstractControllerTest; +import ru.javawebinar.topjava.web.json.JsonUtil; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import java.util.Collections; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.TestUtil.readFromJson; import static ru.javawebinar.topjava.UserTestData.*; -import static ru.javawebinar.topjava.web.json.JsonUtil.writeIgnoreProps; class AdminRestControllerTest extends AbstractControllerTest { @@ -22,6 +28,58 @@ void testGet() throws Exception { .andDo(print()) // https://jira.spring.io/browse/SPR-14472 .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(content().json(writeIgnoreProps(ADMIN, "registered"))); + .andExpect(contentJson(ADMIN)); + } + + @Test + void testGetByEmail() throws Exception { + mockMvc.perform(get(REST_URL + "by?email=" + USER.getEmail())) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(contentJson(USER)); + } + + @Test + void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + USER_ID)) + .andDo(print()) + .andExpect(status().isNoContent()); + assertMatch(userService.getAll(), ADMIN); + } + + @Test + void testUpdate() throws Exception { + User updated = new User(USER); + updated.setName("UpdatedName"); + updated.setRoles(Collections.singletonList(Role.ROLE_ADMIN)); + mockMvc.perform(put(REST_URL + USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated))) + .andExpect(status().isNoContent()); + + assertMatch(userService.get(USER_ID), updated); + } + + @Test + void testCreate() throws Exception { + User expected = new User(null, "New", "new@gmail.com", "newPass", Role.ROLE_USER, Role.ROLE_ADMIN); + ResultActions action = mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(expected))) + .andExpect(status().isCreated()); + + User returned = readFromJson(action, User.class); + expected.setId(returned.getId()); + + assertMatch(returned, expected); + assertMatch(userService.getAll(), ADMIN, expected, USER); + } + + @Test + void testGetAll() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(contentJson(ADMIN, USER)); } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java new file mode 100644 index 0000000..d02084d --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -0,0 +1,44 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import ru.javawebinar.topjava.model.Role; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.web.AbstractControllerTest; +import ru.javawebinar.topjava.web.json.JsonUtil; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; + +class ProfileRestControllerTest extends AbstractControllerTest { + + @Test + void testGet() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(contentJson(USER)); + } + + @Test + void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL)) + .andExpect(status().isNoContent()); + assertMatch(userService.getAll(), ADMIN); + } + + @Test + void testUpdate() throws Exception { + User updated = new User(USER_ID, "newName", "newemail@ya.ru", "newPassword", Role.ROLE_USER); + mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated))) + .andDo(print()) + .andExpect(status().isNoContent()); + + assertMatch(userService.getByEmail("newemail@ya.ru"), updated); + } +} \ No newline at end of file From 11f2349852902962d5323a08b0b2e97deddd70fc Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 31 Mar 2019 15:57:30 +0300 Subject: [PATCH 075/125] 7_14_soapui_utf8_converter --- config/Topjava-soapui-project.xml | 369 ++++++++++++++++++ .../web/user/ProfileRestController.java | 5 + src/main/resources/spring/spring-mvc.xml | 8 + 3 files changed, 382 insertions(+) create mode 100644 config/Topjava-soapui-project.xml diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml new file mode 100644 index 0000000..65d2041 --- /dev/null +++ b/config/Topjava-soapui-project.xml @@ -0,0 +1,369 @@ + + + + + + + + http://localhost:8080 + + + + + + + + + text/html;charset=utf-8 + 500 401 + + html + + + application/json + 200 + + ns:Response + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/admin/users + + user@yandex.ru + password + No Authorization + Basic + No Authorization + + + + + + + + + + + application/json + + + + text/html;charset=utf-8 + 500 + + html + + + application/json + 201 + + user:Response + + + application/json;charset=UTF-8 + 201 + + user:Response + + + + <xml-fragment/> + + http://localhost:8080 + {"name": "New2", + "email": "new2@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] +} + + http://localhost/topjava/rest/admin/users + + No Authorization + Basic + No Authorization + + + + + + + + + + + + + + + text/html;charset=utf-8 + 500 + + html + + + application/json + 200 + + ns:Response + + + application/json;charset=UTF-8 + 200 + + ns:Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/admin/users/100000 + + No Authorization + Basic + No Authorization + + + + + + + + + + + text/html;charset=utf-8 + 405 500 + + html + + + application/json + + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + 200data200data + + <xml-fragment/> + + http://localhost:8080 + {"name": "UserUpdated", + "email": "user@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] +} + + http://localhost/topjava/rest/admin/users/100000 + + Basic + Basic + Global HTTP Settings + + + + + + + + + + + + + + + text/html;charset=utf-8 + 500 + + html + + + application/json + 200 + + ns:Response + + + application/json;charset=UTF-8 + 200 + + prof:Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile + + Basic + Basic + Global HTTP Settings + + + + + + + + + + + application/json + + + + text/html;charset=utf-8 + 500 405 + + html + + + application/json + 201 + + user:Response + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + + + 200 + + data + + 200data + + <xml-fragment/> + + http://localhost:8080 + {"name": "New777", + "email": "new777@yandex.ru", + "password": "passwordNew", + "roles": ["ROLE_USER"] +} + + http://localhost/topjava/rest/profile + + No Authorization + Basic + No Authorization + + + + + + + + + + + + 200 + + data + + + text/html;charset=utf-8 + 500 + + html + + + application/json + + + + + 200 + + data + + 204data + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile + + No Authorization + Basic + No Authorization + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index 07ea30e..eeaae2d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -28,4 +28,9 @@ public void delete() { public void update(@RequestBody User user) { super.update(user, authUserId()); } + + @GetMapping(value = "/text") + public String testUTF() { + return "Русский текст"; + } } \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index fa99f08..76c7a36 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -12,6 +12,14 @@ + + + + text/plain;charset=UTF-8 + text/html;charset=UTF-8 + + + From 15040dabe7e4996b6e92304c70dac66ed7bca24f Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 5 Apr 2019 19:48:11 +0300 Subject: [PATCH 076/125] 8_01_HW07_controller_test --- .../ru/javawebinar/topjava/to/MealTo.java | 18 +++++++++++++++++ src/main/resources/logback.xml | 3 ++- .../topjava/web/ResourceControllerTest.java | 20 +++++++++++++++++++ .../topjava/web/RootControllerTest.java | 12 +++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/to/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java index eedce0f..787bab7 100644 --- a/src/main/java/ru/javawebinar/topjava/to/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.to; import java.time.LocalDateTime; +import java.util.Objects; public class MealTo { private final Integer id; @@ -41,6 +42,23 @@ public boolean isExcess() { return excess; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MealTo that = (MealTo) o; + return calories == that.calories && + excess == that.excess && + Objects.equals(id, that.id) && + Objects.equals(dateTime, that.dateTime) && + Objects.equals(description, that.description); + } + + @Override + public int hashCode() { + return Objects.hash(id, dateTime, description, calories, excess); + } + @Override public String toString() { return "MealTo{" + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 809d4c9..7f5b7b0 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -21,7 +21,8 @@ - + + diff --git a/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java new file mode 100644 index 0000000..adde02f --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/ResourceControllerTest.java @@ -0,0 +1,20 @@ +package ru.javawebinar.topjava.web; + +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class ResourceControllerTest extends AbstractControllerTest { + + @Test + void testResources() throws Exception { + mockMvc.perform(get("/resources/css/style.css")) + .andDo(print()) + .andExpect(content().contentType(MediaType.valueOf("text/css"))) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index 511908f..40ee207 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -9,7 +9,9 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static ru.javawebinar.topjava.MealTestData.MEALS; import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.util.MealsUtil.getWithExcess; class RootControllerTest extends AbstractControllerTest { @@ -29,4 +31,14 @@ public void assertion(List actual) throws AssertionError { } )); } + + @Test + void testMeals() throws Exception { + mockMvc.perform(get("/meals")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(view().name("meals")) + .andExpect(forwardedUrl("/WEB-INF/jsp/meals.jsp")) + .andExpect(model().attribute("meals", getWithExcess(MEALS, SecurityUtil.authUserCaloriesPerDay()))); + } } \ No newline at end of file From 8be1c667b219a89986b0312f652d3713df006c33 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 5 Apr 2019 19:49:02 +0300 Subject: [PATCH 077/125] 8_02_HW07_rest_controller --- .../ru/javawebinar/topjava/to/MealTo.java | 13 +-- .../topjava/web/meal/MealRestController.java | 61 ++++++++++++- .../ru/javawebinar/topjava/MealTestData.java | 11 +++ .../web/meal/MealRestControllerTest.java | 89 +++++++++++++++++++ 4 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java diff --git a/src/main/java/ru/javawebinar/topjava/to/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java index 787bab7..9c15d4d 100644 --- a/src/main/java/ru/javawebinar/topjava/to/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -4,15 +4,18 @@ import java.util.Objects; public class MealTo { - private final Integer id; + private Integer id; - private final LocalDateTime dateTime; + private LocalDateTime dateTime; - private final String description; + private String description; - private final int calories; + private int calories; - private final boolean excess; + private boolean excess; + + public MealTo() { + } public MealTo(Integer id, LocalDateTime dateTime, String description, int calories, boolean excess) { this.id = id; diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index c3daf68..ecee94f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,7 +1,64 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.stereotype.Controller; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.to.MealTo; -@Controller +import java.net.URI; +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping(value = MealRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) public class MealRestController extends AbstractMealController { + static final String REST_URL = "/rest/profile/meals"; + + @Override + @GetMapping("/{id}") + public Meal get(@PathVariable int id) { + return super.get(id); + } + + @Override + @DeleteMapping("/{id}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void delete(@PathVariable int id) { + super.delete(id); + } + + @Override + @GetMapping + public List getAll() { + return super.getAll(); + } + + @Override + @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void update(@RequestBody Meal meal, @PathVariable int id) { + super.update(meal, id); + } + + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity createWithLocation(@RequestBody Meal meal) { + Meal created = super.create(meal); + + URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(REST_URL + "/{id}") + .buildAndExpand(created.getId()).toUri(); + + return ResponseEntity.created(uriOfNewResource).body(created); + } + + @GetMapping(value = "/between") + public List getBetween( + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDateTime, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDateTime) { + return super.getBetween(startDateTime.toLocalDate(), startDateTime.toLocalTime(), endDateTime.toLocalDate(), endDateTime.toLocalTime()); + } } \ 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 index 4567c7d..621d34c 100644 --- a/src/test/java/ru/javawebinar/topjava/MealTestData.java +++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java @@ -1,12 +1,15 @@ 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 { @@ -43,4 +46,12 @@ public static void assertMatch(Iterable actual, Meal... expected) { public static void assertMatch(Iterable actual, Iterable expected) { assertThat(actual).usingElementComparatorIgnoringFields("user").isEqualTo(expected); } + + public static ResultMatcher contentJson(MealTo... expected) { + return contentJson(List.of(expected)); + } + + public static ResultMatcher contentJson(Iterable expected) { + return result -> assertThat(readListFromJsonMvcResult(result, MealTo.class)).isEqualTo(expected); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java new file mode 100644 index 0000000..b7f4b97 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -0,0 +1,89 @@ +package ru.javawebinar.topjava.web.meal; + + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.web.AbstractControllerTest; +import ru.javawebinar.topjava.web.json.JsonUtil; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.MealTestData.*; +import static ru.javawebinar.topjava.TestUtil.readFromJson; +import static ru.javawebinar.topjava.TestUtil.readFromJsonMvcResult; +import static ru.javawebinar.topjava.UserTestData.USER; +import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; +import static ru.javawebinar.topjava.util.MealsUtil.createWithExcess; +import static ru.javawebinar.topjava.util.MealsUtil.getWithExcess; + +class MealRestControllerTest extends AbstractControllerTest { + + private static final String REST_URL = MealRestController.REST_URL + '/'; + + @Autowired + private MealService service; + + @Test + void testGet() throws Exception { + mockMvc.perform(get(REST_URL + MEAL1_ID)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(result -> assertMatch(readFromJsonMvcResult(result, Meal.class), MEAL1)); + } + + @Test + void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + MEAL1_ID)) + .andExpect(status().isNoContent()); + assertMatch(service.getAll(START_SEQ), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); + } + + @Test + void testUpdate() throws Exception { + Meal updated = getUpdated(); + + mockMvc.perform(put(REST_URL + MEAL1_ID).contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(updated))) + .andExpect(status().isNoContent()); + + assertMatch(service.get(MEAL1_ID, START_SEQ), updated); + } + + @Test + void testCreate() throws Exception { + Meal created = getCreated(); + ResultActions action = mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(created))); + + Meal returned = readFromJson(action, Meal.class); + created.setId(returned.getId()); + + assertMatch(returned, created); + assertMatch(service.getAll(START_SEQ), created, MEAL6, MEAL5, MEAL4, MEAL3, MEAL2, MEAL1); + } + + @Test + void testGetAll() throws Exception { + mockMvc.perform(get(REST_URL)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(contentJson(getWithExcess(MEALS, USER.getCaloriesPerDay()))); + } + + @Test + void testGetBetween() throws Exception { + mockMvc.perform(get(REST_URL + "between?startDateTime=2015-05-30T07:00&endDateTime=2015-05-31T11:00:00")) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(contentJson(createWithExcess(MEAL4, true), createWithExcess(MEAL1, false))); + } +} \ No newline at end of file From 5e1a749f0ec2a18c51652bc129267bfdc3ae5204 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 5 Apr 2019 19:53:24 +0300 Subject: [PATCH 078/125] 8_03_HW07_formatters --- .../web/converter/DateTimeFormatters.java | 39 +++++++++++++++++++ .../topjava/web/meal/MealRestController.java | 15 ++++--- src/main/resources/spring/spring-mvc.xml | 11 +++++- .../web/meal/MealRestControllerTest.java | 13 ++++++- 4 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java diff --git a/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java b/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java new file mode 100644 index 0000000..bc44098 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/converter/DateTimeFormatters.java @@ -0,0 +1,39 @@ +package ru.javawebinar.topjava.web.converter; + +import org.springframework.format.Formatter; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; +import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; + +public class DateTimeFormatters { + public static class LocalDateFormatter implements Formatter { + + @Override + public LocalDate parse(String text, Locale locale) { + return parseLocalDate(text); + } + + @Override + public String print(LocalDate lt, Locale locale) { + return lt.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + } + + public static class LocalTimeFormatter implements Formatter { + + @Override + public LocalTime parse(String text, Locale locale) { + return parseLocalTime(text); + } + + @Override + public String print(LocalTime lt, Locale locale) { + return lt.format(DateTimeFormatter.ISO_LOCAL_TIME); + } + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index ecee94f..81423f4 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -1,6 +1,5 @@ package ru.javawebinar.topjava.web.meal; -import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -10,7 +9,8 @@ import ru.javawebinar.topjava.to.MealTo; import java.net.URI; -import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.LocalTime; import java.util.List; @RestController @@ -55,10 +55,13 @@ public ResponseEntity createWithLocation(@RequestBody Meal meal) { return ResponseEntity.created(uriOfNewResource).body(created); } - @GetMapping(value = "/between") + @Override + @GetMapping(value = "/filter") public List getBetween( - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDateTime, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDateTime) { - return super.getBetween(startDateTime.toLocalDate(), startDateTime.toLocalTime(), endDateTime.toLocalDate(), endDateTime.toLocalTime()); + @RequestParam(required = false) LocalDate startDate, + @RequestParam(required = false) LocalTime startTime, + @RequestParam(required = false) LocalDate endDate, + @RequestParam(required = false) LocalTime endTime) { + return super.getBetween(startDate, startTime, endDate, endTime); } } \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 76c7a36..5398281 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -7,7 +7,7 @@ - + @@ -23,6 +23,15 @@ + + + + + + + + + diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index b7f4b97..139a9c9 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -80,10 +80,19 @@ void testGetAll() throws Exception { } @Test - void testGetBetween() throws Exception { - mockMvc.perform(get(REST_URL + "between?startDateTime=2015-05-30T07:00&endDateTime=2015-05-31T11:00:00")) + void testFilter() throws Exception { + mockMvc.perform(get(REST_URL + "filter") + .param("startDate", "2015-05-30").param("startTime", "07:00") + .param("endDate", "2015-05-31").param("endTime", "11:00")) .andExpect(status().isOk()) .andDo(print()) .andExpect(contentJson(createWithExcess(MEAL4, true), createWithExcess(MEAL1, false))); } + + @Test + void testFilterAll() throws Exception { + mockMvc.perform(get(REST_URL + "filter?startDate=&endTime=")) + .andExpect(status().isOk()) + .andExpect(contentJson(getWithExcess(MEALS, USER.getCaloriesPerDay()))); + } } \ No newline at end of file From 7a3e6b3239e2f764ba4acabeb70af43b8bbfd4ca Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 5 Apr 2019 19:59:47 +0300 Subject: [PATCH 079/125] 8_04_HW07_soapui_curl --- config/Topjava-soapui-project.xml | 231 +++++++++++++++++++++++++++++- config/curl.md | 29 ++++ 2 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 config/curl.md diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml index 65d2041..e1d3a10 100644 --- a/config/Topjava-soapui-project.xml +++ b/config/Topjava-soapui-project.xml @@ -7,7 +7,7 @@ http://localhost:8080 - + @@ -98,7 +98,7 @@ - + @@ -176,9 +176,22 @@ data - 200data200data + + + 200 + + data + + + + 200 + + data + + - <xml-fragment/> + <xml-fragment/> + http://localhost:8080 {"name": "UserUpdated", @@ -199,7 +212,7 @@ - + @@ -360,6 +373,214 @@ + + + + + + + + + 0 + + data + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals + + No Authorization + + + + + + + + + + + application/json + + + + application/json;charset=UTF-8 + 201 + + meal:Response + + + + <xml-fragment/> + + + http://localhost:8080 + { +"dateTime": "2015-06-01T10:00", +"description": "Новый завтрак", +"calories": 777 +} + + http://localhost/topjava/rest/profile/meals + + No Authorization + + + + + + + + + + + + + + + application/json;charset=UTF-8 + 200 + + ns:Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals/100002 + + No Authorization + + + + + + + + + + + + + 200 + + data + + + application/json + + + + + 200 + + data + + 200data + + <xml-fragment/> + + UTF-8 + http://localhost:8080 + { +"id": 100002, +"dateTime": "2015-05-30T10:00", +"description": "Обновленный завтрак", +"calories": 500 +} + + http://localhost/topjava/rest/profile/meals/100002 + + No Authorization + + + + + + + + + + + + + startDate + + QUERY + + + + + startTime + + QUERY + + + + + endDate + + QUERY + + + + + endTime + + QUERY + + + + + + + + + application/json;charset=UTF-8 + 200 + + Response + + + + <xml-fragment/> + + http://localhost:8080 + + http://localhost/topjava/rest/profile/meals/filter + + No Authorization + + + + + + + + + + + startDate + startTime + endDate + endTime + + + + diff --git a/config/curl.md b/config/curl.md new file mode 100644 index 0000000..9703a47 --- /dev/null +++ b/config/curl.md @@ -0,0 +1,29 @@ +### curl samples (application deployed in application context `topjava`). +> For windows use `Git Bash` + +#### get All Users +`curl -s http://localhost:8080/topjava/rest/admin/users` + +#### get Users 100001 +`curl -s http://localhost:8080/topjava/rest/admin/users/100001` + +#### get All Meals +`curl -s http://localhost:8080/topjava/rest/profile/meals` + +#### get Meals 100003 +`curl -s http://localhost:8080/topjava/rest/profile/meals/100003` + +#### filter Meals +`curl -s "http://localhost:8080/topjava/rest/profile/meals/filter?startDate=2015-05-30&startTime=07:00:00&endDate=2015-05-31&endTime=11:00:00"` + +#### get Meals not found +`curl -s -v http://localhost:8080/topjava/rest/profile/meals/100008` + +#### delete Meals +`curl -s -X DELETE http://localhost:8080/topjava/rest/profile/meals/100002` + +#### create Meals +`curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals` + +#### update Meals +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003` \ No newline at end of file From 6f270b3ddb7fbc4f5eb7fa4bf6bd4b103f794f20 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 5 Apr 2019 22:00:04 +0300 Subject: [PATCH 080/125] 8_05_webjars --- pom.xml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/pom.xml b/pom.xml index 13b0142..b2ee24f 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,13 @@ 3.7.0 + + + 4.3.1 + 3.3.1-2 + 2.5.20 + 3.1.4 + 1.10.19 @@ -212,6 +219,55 @@ spring-webmvc + + + org.webjars + jquery + ${webjars-jquery.version} + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + org.webjars + jquery + + + + + org.webjars + datatables + ${webjars-datatables.version} + + + org.webjars + jquery + + + + + org.webjars.bower + datetimepicker + ${webjars-datetimepicker.version} + + + org.webjars.bower + jquery + + + org.webjars.bower + php-date-formatter + + + + + org.webjars.bower + noty + ${webjars-noty.version} + + com.fasterxml.jackson.core From 74efbbc959ec0834e81855a6b7d8812ac92b2cb0 Mon Sep 17 00:00:00 2001 From: Viktor Date: Fri, 5 Apr 2019 22:02:37 +0300 Subject: [PATCH 081/125] 8_06_bootstrap4 --- config/app.properties | 4 +- config/app_ru.properties | 4 +- src/main/resources/spring/spring-mvc.xml | 3 + .../WEB-INF/jsp/fragments/bodyHeader.jsp | 14 ++++- .../webapp/WEB-INF/jsp/fragments/footer.jsp | 8 ++- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 4 ++ src/main/webapp/WEB-INF/jsp/index.jsp | 25 ++++---- src/main/webapp/WEB-INF/jsp/users.jsp | 56 ++++++++++-------- src/main/webapp/resources/css/style.css | 25 ++++++-- .../webapp/resources/images/icon-meal.png | Bin 0 -> 1898 bytes 10 files changed, 93 insertions(+), 50 deletions(-) create mode 100644 src/main/webapp/resources/images/icon-meal.png diff --git a/config/app.properties b/config/app.properties index ca6c456..ebc0e6b 100644 --- a/config/app.properties +++ b/config/app.properties @@ -1,9 +1,10 @@ app.title=Calories management -app.home=Home app.footer=Internship Spring 5/JPA Enterprise (Topjava) application app.login=Login as user.title=Users +user.edit=Edit user +user.add=Add user user.name=Name user.email=Email user.roles=Roles @@ -22,6 +23,7 @@ meal.description=Description meal.dateTime=Date/Time meal.calories=Calories +common.add=Add common.select=Select common.delete=Delete common.update=Update diff --git a/config/app_ru.properties b/config/app_ru.properties index 6503394..5f79fb2 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -1,9 +1,10 @@ app.title=\u041F\u043E\u0434\u0441\u0447\u0435\u0442 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 -app.home=\u0413\u043B\u0430\u0432\u043D\u0430\u044F app.footer=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0443 Maven/ Spring/ Security/ JPA(Hibernate)/ Jackson/jQuery app.login=\u0417\u0430\u0439\u0442\u0438 \u043A\u0430\u043A user.title=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0438 +user.edit=\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F +user.add=\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F user.name=\u0418\u043C\u044F user.email=\u041F\u043E\u0447\u0442\u0430 user.roles=\u0420\u043E\u043B\u0438 @@ -22,6 +23,7 @@ meal.description=\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 meal.dateTime=\u0414\u0430\u0442\u0430/\u0412\u0440\u0435\u043C\u044F meal.calories=\u041A\u0430\u043B\u043E\u0440\u0438\u0438 +common.add=\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C common.delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C common.update=\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 5398281..9451c59 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -37,6 +37,9 @@ + + + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 8453798..5b1783f 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,6 +1,14 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -

    -  |  -
    + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp index 0935c44..cf1331f 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/footer.jsp @@ -1,4 +1,8 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -
    -
    \ No newline at end of file +<%--https://getbootstrap.com/docs/4.0/examples/sticky-footer/--%> +
    +
    + +
    +
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 0c77f10..c0318ba 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -6,5 +6,9 @@ <spring:message code="app.title"/> + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp index 4833dbc..f89e7a1 100644 --- a/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -6,19 +6,18 @@ -
    -
    - : - - -
      -
    • -
    • -
    -
    +
    +
    +
    + + + +
    +
    +
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index e4aa345..1949c60 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -8,32 +8,40 @@ -
    -

    - - - - - - - - - - - - - +
    +
    +

    + +
    + - - - - - + + + + + + + - -
    ${user.email}${user.roles}<%=user.isEnabled()%> -
    -
    + + + +

    ${user.email}${user.roles}checked/>
    + + \ No newline at end of file diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index bd674ca..8384e49 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -23,10 +23,23 @@ tr[data-mealExcess="true"] { color: red; } -header, footer { - background: none repeat scroll 0 0 #A6C9E2; - color: #2E6E9E; - font-size: 20px; - padding: 5px 20px; - margin: 6px 0; +.fa { + cursor: pointer; +} + +/*https://getbootstrap.com/docs/4.0/examples/sticky-footer/sticky-footer.css*/ +html { + position: relative; + min-height: 100%; +} +body { + margin-bottom: 60px; /* Margin bottom by footer height */ +} +.footer { + position: absolute; + bottom: 0; + width: 100%; + height: 60px; /* Set the fixed height of the footer here */ + line-height: 60px; /* Vertically center the text there */ + background-color: #f5f5f5; } \ No newline at end of file diff --git a/src/main/webapp/resources/images/icon-meal.png b/src/main/webapp/resources/images/icon-meal.png new file mode 100644 index 0000000000000000000000000000000000000000..b4fc54ad01294abb103d9238c12b1826a9240dfd GIT binary patch literal 1898 zcmV-w2bK7VP)PS&+k>eS5@~O7WLfN z#z>r1)zQ7@JLlXpd}L<$FKLxT1mO7i`0((sD2l48LI|y1MB+H^bUOWhKS`1&t~@IA z1b~^D88e&NXf)c{*(uAi)iCqP{SZP3!OTRY*Xx}A^;#TU>54SHnYXW#g&y6RSh99^HTs$tZ#2`udlC*h;z;d0}zWj3TB}R zFn|flWT07)I)DiT%x1GFidI)w^E?kB5K$Y-9N@(I#>U3x=4P6v0K6d;BWwP3q;Kx$ z_a^B<71hQG`=w-hwO4#}p&VpZ25mf4sA(GS{pHJ-yWQ>y2H8Vl=B=%*jg5^g%R(>^ zpsL+&Chb!3l!F8yFZyq(Lj5wj%J{QS=1FV`-RXv|gz1JiLuH$O`+tkPjc%tTC8 zFh=;G>b&~)rPnv3Ze$)5$8pm%o12^M8_WSlqmlRCIcLNj^|#;a|88fwA6w$=xgmY{ z3@O6{xx>xRAXX4z=JADR>E-A6R2)3HpZxQ}ojuAO`G7P{_xJaw(g~evn6dlC zz2#Zsoia0v$b$zDh=_^k=;$bf;GAP-3;wnBGbR$umb>)D6^v`t0WV%eKS2{4Wl=yZli zQ3XsrIx+ew0zib-1)S^QbOHd(2!?BCD8SOl{xn)BYg1tn0Wg_N*vzV`a?TO4WBc1d zR;d)!V2&CN+Nj4+MfR07QZ`!jIp;_5>jO?C-d9fIKE* zu-I^;zq+&~{XOY%# z5#czF=Rpv~PA_$85Q)&-ghCj?bBuPEs6ZGZPzDp6%=1G+xi%NO7D!e2q_RG|zaIE4tDSi`uGlo$I_gH^9nL%#7Qm zwy>};o6WrUPPl}7=~DWQ)4QejGoqQdqOrEijv55RG{6antne1h4gzPPym{gH%CI-} zh+{V%kK;IQ>CAj(647`(-rL(-UVf&o8)A;F{=@y^)=no$GbJ3?U|1qpi10xPAv8v@ zz){Gm34shDAOyfb1Qrm7fW`-5YB|H<;o)E~NYk{<`O}$Qi%}HC?(S}yru}~3 zIcGo!W-x#-pHg5VCNi_KEDsJ2h-fevsOqEEp9DC8X(Ky0I4FujRns(`7d6kT&U;_i z^>jKd%aVwCy Date: Sun, 7 Apr 2019 18:42:43 +0300 Subject: [PATCH 082/125] 8_07_ajax_datatables --- config/Topjava-soapui-project.xml | 5 +- config/app.properties | 1 + config/app_ru.properties | 1 + .../topjava/web/user/AdminUIController.java | 40 ++++++++++++++ .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 7 +++ src/main/webapp/WEB-INF/jsp/users.jsp | 55 +++++++++++++++++-- .../webapp/resources/js/topjava.common.js | 39 +++++++++++++ src/main/webapp/resources/js/topjava.users.js | 42 ++++++++++++++ 8 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java create mode 100644 src/main/webapp/resources/js/topjava.common.js create mode 100644 src/main/webapp/resources/js/topjava.users.js diff --git a/config/Topjava-soapui-project.xml b/config/Topjava-soapui-project.xml index e1d3a10..6090560 100644 --- a/config/Topjava-soapui-project.xml +++ b/config/Topjava-soapui-project.xml @@ -1,5 +1,5 @@ - + @@ -393,8 +393,7 @@ - <xml-fragment/> - + <xml-fragment/> http://localhost:8080 diff --git a/config/app.properties b/config/app.properties index ebc0e6b..c7029e6 100644 --- a/config/app.properties +++ b/config/app.properties @@ -10,6 +10,7 @@ user.email=Email user.roles=Roles user.active=Active user.registered=Registered +user.password=Password meal.title=Meals meal.edit=Edit meal diff --git a/config/app_ru.properties b/config/app_ru.properties index 5f79fb2..fe204be 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -10,6 +10,7 @@ user.email=\u041F\u043E\u0447\u0442\u0430 user.roles=\u0420\u043E\u043B\u0438 user.active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439 user.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D +user.password=\u041F\u0430\u0440\u043E\u043B\u044C meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 meal.edit=\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0435\u0434\u044B diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java new file mode 100644 index 0000000..0619bef --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java @@ -0,0 +1,40 @@ +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.Role; +import ru.javawebinar.topjava.model.User; + +import java.util.List; + +@RestController +@RequestMapping("/ajax/admin/users") +public class AdminUIController extends AbstractUserController { + + @Override + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + return super.getAll(); + } + + @Override + @DeleteMapping("/{id}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @PostMapping + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void createOrUpdate(@RequestParam("id") Integer id, + @RequestParam("name") String name, + @RequestParam("email") String email, + @RequestParam("password") String password) { + + User user = new User(id, name, email, password, Role.ROLE_USER); + if (user.isNew()) { + super.create(user); + } + } +} diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index c0318ba..24a97d0 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -10,5 +10,12 @@ + + + <%--http://stackoverflow.com/a/24070373/548473--%> + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 1949c60..3017484 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -6,16 +6,18 @@ + +

    - - +
    @@ -33,15 +35,60 @@ - + - +
    ${user.email} ${user.roles}checked/>checked id="${user.id}"/>
    + + \ No newline at end of file diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js new file mode 100644 index 0000000..eb27ff6 --- /dev/null +++ b/src/main/webapp/resources/js/topjava.common.js @@ -0,0 +1,39 @@ +function makeEditable() { + $(".delete").click(function () { + deleteRow($(this).attr("id")); + }); +} + +function add() { + $("#detailsForm").find(":input").val(""); + $("#editRow").modal(); +} + +function deleteRow(id) { + $.ajax({ + url: ajaxUrl + id, + type: "DELETE", + success: function () { + updateTable(); + } + }); +} + +function updateTable() { + $.get(ajaxUrl, function (data) { + datatableApi.clear().rows.add(data).draw(); + }); +} + +function save() { + var form = $("#detailsForm"); + $.ajax({ + type: "POST", + url: ajaxUrl, + data: form.serialize(), + success: function () { + $("#editRow").modal("hide"); + updateTable(); + } + }); +} \ 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 0000000..1f9c6e5 --- /dev/null +++ b/src/main/webapp/resources/js/topjava.users.js @@ -0,0 +1,42 @@ +var ajaxUrl = "ajax/admin/users/"; +var datatableApi; + +// $(document).ready(function () { +$(function () { + datatableApi = $("#datatable").DataTable({ + "paging": false, + "info": true, + "columns": [ + { + "data": "name" + }, + { + "data": "email" + }, + { + "data": "roles" + }, + { + "data": "enabled" + }, + { + "data": "registered" + }, + { + "defaultContent": "Edit", + "orderable": false + }, + { + "defaultContent": "Delete", + "orderable": false + } + ], + "order": [ + [ + 0, + "asc" + ] + ] + }); + makeEditable(); +}); \ No newline at end of file From e98773669286c94c2a15f1e25386e518f6a1707b Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 7 Apr 2019 18:47:11 +0300 Subject: [PATCH 083/125] 8_08_refactor_js --- .../webapp/resources/js/topjava.common.js | 37 +++++---- src/main/webapp/resources/js/topjava.users.js | 78 +++++++++---------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index eb27ff6..9f4c46f 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -1,39 +1,42 @@ -function makeEditable() { +let context, form; + +function makeEditable(ctx) { + context = ctx; + form = $('#detailsForm'); $(".delete").click(function () { - deleteRow($(this).attr("id")); + if (confirm('Are you sure?')) { + deleteRow($(this).attr("id")); + } }); } function add() { - $("#detailsForm").find(":input").val(""); + form.find(":input").val(""); $("#editRow").modal(); } function deleteRow(id) { $.ajax({ - url: ajaxUrl + id, - type: "DELETE", - success: function () { - updateTable(); - } + url: context.ajaxUrl + id, + type: "DELETE" + }).done(function () { + updateTable(); }); } function updateTable() { - $.get(ajaxUrl, function (data) { - datatableApi.clear().rows.add(data).draw(); + $.get(context.ajaxUrl, function (data) { + context.datatableApi.clear().rows.add(data).draw(); }); } function save() { - var form = $("#detailsForm"); $.ajax({ type: "POST", - url: ajaxUrl, - data: form.serialize(), - success: function () { - $("#editRow").modal("hide"); - updateTable(); - } + url: context.ajaxUrl, + data: form.serialize() + }).done(function () { + $("#editRow").modal("hide"); + updateTable(); }); } \ 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 index 1f9c6e5..6ed8f33 100644 --- a/src/main/webapp/resources/js/topjava.users.js +++ b/src/main/webapp/resources/js/topjava.users.js @@ -1,42 +1,42 @@ -var ajaxUrl = "ajax/admin/users/"; -var datatableApi; - // $(document).ready(function () { $(function () { - datatableApi = $("#datatable").DataTable({ - "paging": false, - "info": true, - "columns": [ - { - "data": "name" - }, - { - "data": "email" - }, - { - "data": "roles" - }, - { - "data": "enabled" - }, - { - "data": "registered" - }, - { - "defaultContent": "Edit", - "orderable": false - }, - { - "defaultContent": "Delete", - "orderable": false - } - ], - "order": [ - [ - 0, - "asc" - ] - ] - }); - makeEditable(); + makeEditable({ + ajaxUrl: "ajax/admin/users/", + datatableApi: $("#datatable").DataTable({ + "paging": false, + "info": true, + "columns": [ + { + "data": "name" + }, + { + "data": "email" + }, + { + "data": "roles" + }, + { + "data": "enabled" + }, + { + "data": "registered" + }, + { + "defaultContent": "Edit", + "orderable": false + }, + { + "defaultContent": "Delete", + "orderable": false + } + ], + "order": [ + [ + 0, + "asc" + ] + ] + }) + } + ); }); \ No newline at end of file From 9679415bf58d5399e9e496eb0b2d3c484aa25482 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 7 Apr 2019 18:47:55 +0300 Subject: [PATCH 084/125] 8_09_notification --- .../webapp/WEB-INF/jsp/fragments/headTag.jsp | 2 + .../webapp/resources/js/topjava.common.js | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index 24a97d0..5f9f561 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -11,6 +11,7 @@ + <%--http://stackoverflow.com/a/24070373/548473--%> @@ -18,4 +19,5 @@ + \ No newline at end of file diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index 9f4c46f..34955c0 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -8,6 +8,13 @@ function makeEditable(ctx) { deleteRow($(this).attr("id")); } }); + + $(document).ajaxError(function (event, jqXHR, options, jsExc) { + failNoty(jqXHR); + }); + + // solve problem with cache in IE: https://stackoverflow.com/a/4303862/548473 + $.ajaxSetup({cache: false}); } function add() { @@ -21,6 +28,7 @@ function deleteRow(id) { type: "DELETE" }).done(function () { updateTable(); + successNoty("Deleted"); }); } @@ -38,5 +46,34 @@ function save() { }).done(function () { $("#editRow").modal("hide"); updateTable(); + successNoty("Saved"); }); +} + +let failedNote; + +function closeNoty() { + if (failedNote) { + failedNote.close(); + failedNote = undefined; + } +} + +function successNoty(text) { + closeNoty(); + new Noty({ + text: "  " + text, + type: 'success', + layout: "bottomRight", + timeout: 1000 + }).show(); +} + +function failNoty(jqXHR) { + closeNoty(); + failedNote = new Noty({ + text: "  Error status: " + jqXHR.status, + type: "error", + layout: "bottomRight" + }).show(); } \ No newline at end of file From 652bab7a4ecaa95220cc30702068f3d695eb4fff Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 7 Apr 2019 18:50:01 +0300 Subject: [PATCH 085/125] 8_10_add_rest_security --- config/curl.md | 18 ++++++------ pom.xml | 12 ++++++++ src/main/resources/logback.xml | 1 + src/main/resources/spring/spring-app.xml | 1 + src/main/resources/spring/spring-security.xml | 28 +++++++++++++++++++ src/main/webapp/WEB-INF/web.xml | 10 +++++++ 6 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 src/main/resources/spring/spring-security.xml diff --git a/config/curl.md b/config/curl.md index 9703a47..f55495e 100644 --- a/config/curl.md +++ b/config/curl.md @@ -2,28 +2,28 @@ > For windows use `Git Bash` #### get All Users -`curl -s http://localhost:8080/topjava/rest/admin/users` +`curl -s http://localhost:8080/topjava/rest/admin/users --user admin@gmail.com:admin` #### get Users 100001 -`curl -s http://localhost:8080/topjava/rest/admin/users/100001` +`curl -s http://localhost:8080/topjava/rest/admin/users/100001 --user admin@gmail.com:admin` #### get All Meals -`curl -s http://localhost:8080/topjava/rest/profile/meals` +`curl -s http://localhost:8080/topjava/rest/profile/meals --user user@yandex.ru:password` #### get Meals 100003 -`curl -s http://localhost:8080/topjava/rest/profile/meals/100003` +`curl -s http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` #### filter Meals -`curl -s "http://localhost:8080/topjava/rest/profile/meals/filter?startDate=2015-05-30&startTime=07:00:00&endDate=2015-05-31&endTime=11:00:00"` +`curl -s "http://localhost:8080/topjava/rest/profile/meals/filter?startDate=2015-05-30&startTime=07:00:00&endDate=2015-05-31&endTime=11:00:00" --user user@yandex.ru:password` #### get Meals not found -`curl -s -v http://localhost:8080/topjava/rest/profile/meals/100008` +`curl -s -v http://localhost:8080/topjava/rest/profile/meals/100008 --user user@yandex.ru:password` #### delete Meals -`curl -s -X DELETE http://localhost:8080/topjava/rest/profile/meals/100002` +`curl -s -X DELETE http://localhost:8080/topjava/rest/profile/meals/100002 --user user@yandex.ru:password` #### create Meals -`curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals` +`curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals --user user@yandex.ru:password` #### update Meals -`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003` \ No newline at end of file +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` \ No newline at end of file diff --git a/pom.xml b/pom.xml index b2ee24f..818bb4f 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,18 @@ ${spring-data-jpa.version} + + + org.springframework.security + spring-security-web + ${spring.version} + + + org.springframework.security + spring-security-config + ${spring.version} + + org.hibernate diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 7f5b7b0..dc5b677 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -23,6 +23,7 @@ + diff --git a/src/main/resources/spring/spring-app.xml b/src/main/resources/spring/spring-app.xml index 5d79841..6af7be0 100644 --- a/src/main/resources/spring/spring-app.xml +++ b/src/main/resources/spring/spring-app.xml @@ -12,6 +12,7 @@ --> + diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml new file mode 100644 index 0000000..dc2f511 --- /dev/null +++ b/src/main/resources/spring/spring-security.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 19fb2cc..55c0167 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,6 +37,16 @@ / + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + encodingFilter org.springframework.web.filter.CharacterEncodingFilter From 808c02255942f1ab78781187576b203eafb81fab Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 14 Apr 2019 17:21:09 +0300 Subject: [PATCH 086/125] 9_01_HW8 --- config/app.properties | 2 - config/app_ru.properties | 2 - .../topjava/web/meal/JspMealController.java | 70 -------- .../topjava/web/meal/MealUIController.java | 53 ++++++ src/main/webapp/WEB-INF/jsp/mealForm.jsp | 34 ---- src/main/webapp/WEB-INF/jsp/meals.jsp | 163 ++++++++++++------ src/main/webapp/WEB-INF/jsp/users.jsp | 2 +- src/main/webapp/resources/css/style.css | 17 -- .../webapp/resources/js/topjava.common.js | 30 ++-- src/main/webapp/resources/js/topjava.meals.js | 43 +++++ src/main/webapp/resources/js/topjava.users.js | 5 +- 11 files changed, 225 insertions(+), 196 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java delete mode 100644 src/main/webapp/WEB-INF/jsp/mealForm.jsp create mode 100644 src/main/webapp/resources/js/topjava.meals.js diff --git a/config/app.properties b/config/app.properties index c7029e6..643cc42 100644 --- a/config/app.properties +++ b/config/app.properties @@ -26,7 +26,5 @@ meal.calories=Calories common.add=Add common.select=Select -common.delete=Delete -common.update=Update common.save=Save common.cancel=Cancel \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index fe204be..c0ab5c0 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -26,7 +26,5 @@ meal.calories=\u041A\u0430\u043B\u043E\u0440\u0438\u0438 common.add=\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C -common.delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C -common.update=\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java b/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java deleted file mode 100644 index 527f936..0000000 --- a/src/main/java/ru/javawebinar/topjava/web/meal/JspMealController.java +++ /dev/null @@ -1,70 +0,0 @@ -package ru.javawebinar.topjava.web.meal; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import ru.javawebinar.topjava.model.Meal; - -import javax.servlet.http.HttpServletRequest; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.temporal.ChronoUnit; -import java.util.Objects; - -import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalDate; -import static ru.javawebinar.topjava.util.DateTimeUtil.parseLocalTime; - -@Controller -@RequestMapping(value = "/meals") -public class JspMealController extends AbstractMealController { - - @GetMapping("/delete") - public String delete(HttpServletRequest request) { - super.delete(getId(request)); - return "redirect:/meals"; - } - - @GetMapping("/update") - public String update(HttpServletRequest request, Model model) { - model.addAttribute("meal", super.get(getId(request))); - return "mealForm"; - } - - @GetMapping("/create") - public String create(Model model) { - model.addAttribute("meal", new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), "", 1000)); - return "mealForm"; - } - - @PostMapping - public String updateOrCreate(HttpServletRequest request) { - Meal meal = new Meal(LocalDateTime.parse(request.getParameter("dateTime")), - request.getParameter("description"), - Integer.valueOf(request.getParameter("calories"))); - - if (request.getParameter("id").isEmpty()) { - super.create(meal); - } else { - super.update(meal, getId(request)); - } - return "redirect:/meals"; - } - - @PostMapping("/filter") - public String getBetween(HttpServletRequest request, Model model) { - LocalDate startDate = parseLocalDate(request.getParameter("startDate")); - LocalDate endDate = parseLocalDate(request.getParameter("endDate")); - LocalTime startTime = parseLocalTime(request.getParameter("startTime")); - LocalTime endTime = parseLocalTime(request.getParameter("endTime")); - model.addAttribute("meals", super.getBetween(startDate, startTime, endDate, endTime)); - return "meals"; - } - - private int getId(HttpServletRequest request) { - String paramId = Objects.requireNonNull(request.getParameter("id")); - return Integer.valueOf(paramId); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java new file mode 100644 index 0000000..73e8f1a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java @@ -0,0 +1,53 @@ +package ru.javawebinar.topjava.web.meal; + +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.to.MealTo; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@RestController +@RequestMapping("/ajax/profile/meals") +public class MealUIController extends AbstractMealController { + + @Override + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List getAll() { + return super.getAll(); + } + + @Override + @DeleteMapping(value = "/{id}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void delete(@PathVariable("id") int id) { + super.delete(id); + } + + @PostMapping + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void createOrUpdate(@RequestParam("id") Integer id, + @RequestParam("dateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime, + @RequestParam("description") String description, + @RequestParam("calories") int calories) { + Meal meal = new Meal(id, dateTime, description, calories); + if (meal.isNew()) { + super.create(meal); + } + } + + @Override + @GetMapping(value = "/filter", produces = MediaType.APPLICATION_JSON_VALUE) + public List getBetween( + @RequestParam(value = "startDate", required = false) LocalDate startDate, + @RequestParam(value = "startTime", required = false) LocalTime startTime, + @RequestParam(value = "endDate", required = false) LocalDate endDate, + @RequestParam(value = "endTime", required = false) LocalTime endTime) { + return super.getBetween(startDate, startTime, endDate, endTime); + } +} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/mealForm.jsp b/src/main/webapp/WEB-INF/jsp/mealForm.jsp deleted file mode 100644 index 999c1e2..0000000 --- a/src/main/webapp/WEB-INF/jsp/mealForm.jsp +++ /dev/null @@ -1,34 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> - - - - - - -
    - -

    -
    -
    - -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    - - -
    -
    - - - diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 573648e..49c27cc 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -5,60 +5,121 @@ + + -
    -

    - -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    -
    -
    :
    -
    -
    - -
    -
    - -
    - - - - - - - - - - - - - - - - - - +
    +
    +

    + <%--https://getbootstrap.com/docs/4.0/components/card/--%> +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + +
    +
    + +
    - <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> - <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> - <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> - ${fn:formatDateTime(meal.dateTime)} - ${meal.description}${meal.calories}
    + + + + + + + - -
    -
    + + + + + + <%--${meal.dateTime.toLocalDate()} ${meal.dateTime.toLocalTime()}--%> + <%--<%=TimeUtil.toString(meal.getDateTime())%>--%> + <%--${fn:replace(meal.dateTime, 'T', ' ')}--%> + ${fn:formatDateTime(meal.dateTime)} + + ${meal.description} + ${meal.calories} + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 3017484..534de97 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -39,7 +39,7 @@ checked id="${user.id}"/> - + diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index 8384e49..67e0534 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -1,20 +1,3 @@ -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; -} - tr[data-mealExcess="false"] { color: green; } diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index 34955c0..8c16469 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -3,12 +3,6 @@ let context, form; function makeEditable(ctx) { context = ctx; form = $('#detailsForm'); - $(".delete").click(function () { - if (confirm('Are you sure?')) { - deleteRow($(this).attr("id")); - } - }); - $(document).ajaxError(function (event, jqXHR, options, jsExc) { failNoty(jqXHR); }); @@ -23,19 +17,19 @@ function add() { } function deleteRow(id) { - $.ajax({ - url: context.ajaxUrl + id, - type: "DELETE" - }).done(function () { - updateTable(); - successNoty("Deleted"); - }); + if (confirm('Are you sure?')) { + $.ajax({ + url: context.ajaxUrl + id, + type: "DELETE" + }).done(function () { + context.updateTable(); + successNoty("Deleted"); + }); + } } -function updateTable() { - $.get(context.ajaxUrl, function (data) { - context.datatableApi.clear().rows.add(data).draw(); - }); +function updateTableByData(data) { + context.datatableApi.clear().rows.add(data).draw(); } function save() { @@ -45,7 +39,7 @@ function save() { data: form.serialize() }).done(function () { $("#editRow").modal("hide"); - updateTable(); + context.updateTable(); successNoty("Saved"); }); } 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 0000000..8d6103e --- /dev/null +++ b/src/main/webapp/resources/js/topjava.meals.js @@ -0,0 +1,43 @@ +function updateFilteredTable() { + $.ajax({ + type: "GET", + url: "ajax/profile/meals/filter", + data: $("#filter").serialize() + }).done(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 index 6ed8f33..d5d81e7 100644 --- a/src/main/webapp/resources/js/topjava.users.js +++ b/src/main/webapp/resources/js/topjava.users.js @@ -36,7 +36,10 @@ $(function () { "asc" ] ] - }) + }), + updateTable: function () { + $.get("ajax/admin/users/", updateTableByData); + } } ); }); \ No newline at end of file From 74f7358a05d3f5720d4686ebf184130ea8a01b9c Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 14 Apr 2019 17:21:37 +0300 Subject: [PATCH 087/125] 9_02_HW8_clear_filter --- src/main/webapp/WEB-INF/jsp/meals.jsp | 4 ++++ src/main/webapp/resources/js/topjava.meals.js | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index 49c27cc..ed30835 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -37,6 +37,10 @@ diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index 239a907..921eec8 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -79,4 +79,16 @@ function failNoty(jqXHR) { 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.users.js b/src/main/webapp/resources/js/topjava.users.js index f8d387e..7ce1b8e 100644 --- a/src/main/webapp/resources/js/topjava.users.js +++ b/src/main/webapp/resources/js/topjava.users.js @@ -20,6 +20,10 @@ $(function () { makeEditable({ ajaxUrl: userAjaxUrl, datatableApi: $("#datatable").DataTable({ + "ajax": { + "url": userAjaxUrl, + "dataSrc": "" + }, "paging": false, "info": true, "columns": [ @@ -27,24 +31,44 @@ $(function () { "data": "name" }, { - "data": "email" + "data": "email", + "render": function (data, type, row) { + if (type === "display") { + return "" + data + ""; + } + return data; + } }, { "data": "roles" }, { - "data": "enabled" + "data": "enabled", + "render": function (data, type, row) { + if (type === "display") { + return ""; + } + return data; + } }, { - "data": "registered" + "data": "registered", + "render": function (date, type, row) { + if (type === "display") { + return date.substring(0, 10); + } + return date; + } }, { - "defaultContent": "Edit", - "orderable": false + "orderable": false, + "defaultContent": "", + "render": renderEditBtn }, { - "defaultContent": "Delete", - "orderable": false + "orderable": false, + "defaultContent": "", + "render": renderDeleteBtn } ], "order": [ @@ -52,7 +76,12 @@ $(function () { 0, "asc" ] - ] + ], + "createdRow": function (row, data, dataIndex) { + if (!data.enabled) { + $(row).attr("data-userEnabled", false); + } + } }), updateTable: function () { $.get(userAjaxUrl, updateTableByData); diff --git a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java index 40ee207..b2a51ed 100644 --- a/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/RootControllerTest.java @@ -1,16 +1,11 @@ package ru.javawebinar.topjava.web; -import org.assertj.core.matcher.AssertionMatcher; import org.junit.jupiter.api.Test; -import ru.javawebinar.topjava.model.User; - -import java.util.List; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static ru.javawebinar.topjava.MealTestData.MEALS; -import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.util.MealsUtil.getWithExcess; class RootControllerTest extends AbstractControllerTest { @@ -21,15 +16,7 @@ void testUsers() throws Exception { .andDo(print()) .andExpect(status().isOk()) .andExpect(view().name("users")) - .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) - .andExpect(model().attribute("users", - new AssertionMatcher>() { - @Override - public void assertion(List actual) throws AssertionError { - assertMatch(actual, ADMIN, USER); - } - } - )); + .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")); } @Test From 4efc8e07262a94babeb25cd6cb70af56ab9d93d5 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 14 Apr 2019 17:25:08 +0300 Subject: [PATCH 093/125] 9_08_js_i18n --- config/app.properties | 6 ++++++ config/app_ru.properties | 6 ++++++ src/main/webapp/WEB-INF/jsp/users.jsp | 11 ++++++++++- src/main/webapp/resources/js/topjava.common.js | 14 ++++++++------ src/main/webapp/resources/js/topjava.users.js | 2 +- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/config/app.properties b/config/app.properties index 643cc42..061f01d 100644 --- a/config/app.properties +++ b/config/app.properties @@ -26,5 +26,11 @@ meal.calories=Calories common.add=Add common.select=Select +common.deleted=Record deleted +common.saved=Record saved +common.enabled=Record enabled +common.disabled=Record disabled +common.errorStatus=Error status +common.confirm=Are you sure? common.save=Save common.cancel=Cancel \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index c0ab5c0..3a4943e 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -26,5 +26,11 @@ meal.calories=\u041A\u0430\u043B\u043E\u0440\u0438\u0438 common.add=\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C common.select=\u0412\u044B\u0431\u0440\u0430\u0442\u044C +common.deleted=\u0417\u0430\u043F\u0438\u0441\u044C \u0443\u0434\u0430\u043B\u0435\u043D\u0430 +common.saved=\u0417\u0430\u043F\u0438\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430 +common.enabled=\u0417\u0430\u043F\u0438\u0441\u044C \u0430\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u043D\u0430 +common.disabled=\u0417\u0430\u043F\u0438\u0441\u044C \u0434\u0435\u0430\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u043D\u0430 +common.errorStatus=\u0421\u0442\u0430\u0442\u0443\u0441 \u043E\u0448\u0438\u0431\u043A\u0438 +common.confirm=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B? common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/users.jsp b/src/main/webapp/WEB-INF/jsp/users.jsp index 036da93..f8009a2 100644 --- a/src/main/webapp/WEB-INF/jsp/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/users.jsp @@ -37,7 +37,7 @@ @@ -85,7 +70,7 @@

    + diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index bd1e212..dcc212b 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; @@ -12,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.TestUtil.readFromJson; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; @@ -41,6 +43,23 @@ void testDelete() throws Exception { assertMatch(userService.getAll(), ADMIN); } + @Test + void testRegister() throws Exception { + UserTo createdTo = new UserTo(null, "newName", "newemail@ya.ru", "newPassword", 1500); + + ResultActions action = mockMvc.perform(post(REST_URL + "/register").contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(createdTo))) + .andDo(print()) + .andExpect(status().isCreated()); + User returned = readFromJson(action, User.class); + + User created = UserUtil.createNewFromTo(createdTo); + created.setId(returned.getId()); + + assertMatch(returned, created); + assertMatch(userService.getByEmail("newemail@ya.ru"), created); + } + @Test void testUpdate() throws Exception { UserTo updatedTo = new UserTo(null, "newName", "newemail@ya.ru", "newPassword", 1500); From b361f23432434bd8f3abddcdbe14d9e2556fc895 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 21 Apr 2019 21:53:19 +0500 Subject: [PATCH 108/125] 10_10_not_found_422 --- .../util/exception/NotFoundException.java | 5 +++++ .../topjava/web/meal/MealRestControllerTest.java | 15 +++++++++++++++ .../web/user/AdminRestControllerTest.java | 16 ++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java index f1e9b0e..9ff0bbb 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -1,5 +1,10 @@ package ru.javawebinar.topjava.util.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +// http://stackoverflow.com/a/22358422/548473 +@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY, reason = "No data found") // 422 public class NotFoundException extends RuntimeException { public NotFoundException(String message) { super(message); diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index 58f0c63..b634ae0 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -46,6 +46,13 @@ void testGetUnauth() throws Exception { .andExpect(status().isUnauthorized()); } + @Test + void testGetNotFound() throws Exception { + mockMvc.perform(get(REST_URL + ADMIN_MEAL_ID) + .with(userHttpBasic(USER))) + .andExpect(status().isUnprocessableEntity()); + } + @Test void testDelete() throws Exception { mockMvc.perform(delete(REST_URL + MEAL1_ID) @@ -54,6 +61,14 @@ void testDelete() throws Exception { assertMatch(service.getAll(START_SEQ), MEAL6, MEAL5, MEAL4, MEAL3, MEAL2); } + @Test + void testDeleteNotFound() throws Exception { + mockMvc.perform(delete(REST_URL + ADMIN_MEAL_ID) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()); + } + @Test void testUpdate() throws Exception { Meal updated = getUpdated(); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 5a4f5c2..a1c216c 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -33,6 +33,14 @@ void testGet() throws Exception { .andExpect(contentJson(ADMIN)); } + @Test + void testGetNotFound() throws Exception { + mockMvc.perform(get(REST_URL + 1) + .with(userHttpBasic(ADMIN))) + .andExpect(status().isUnprocessableEntity()) + .andDo(print()); + } + @Test void testGetByEmail() throws Exception { mockMvc.perform(get(REST_URL + "by?email=" + ADMIN.getEmail()) @@ -51,6 +59,14 @@ void testDelete() throws Exception { assertMatch(userService.getAll(), ADMIN); } + @Test + void testDeleteNotFound() throws Exception { + mockMvc.perform(delete(REST_URL + 1) + .with(userHttpBasic(ADMIN))) + .andExpect(status().isUnprocessableEntity()) + .andDo(print()); + } + @Test void testGetUnAuth() throws Exception { mockMvc.perform(get(REST_URL)) From fb4e4a8cbfa4158312e4c8f4b9941d8a8fb670cf Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 21 Apr 2019 21:53:41 +0500 Subject: [PATCH 109/125] 10_11_global_exception --- config/app.properties | 1 + config/app_ru.properties | 1 + .../web/GlobalControllerExceptionHandler.java | 32 +++++++++++++++++++ .../WEB-INF/jsp/exception/exception.jsp | 25 +++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java create mode 100644 src/main/webapp/WEB-INF/jsp/exception/exception.jsp diff --git a/config/app.properties b/config/app.properties index 1157227..dbb29e3 100644 --- a/config/app.properties +++ b/config/app.properties @@ -35,6 +35,7 @@ common.saved=Record saved common.enabled=Record enabled common.disabled=Record disabled common.errorStatus=Error status +common.appError=Application error common.confirm=Are you sure? common.save=Save common.cancel=Cancel \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index 84330de..b0c3eae 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -35,6 +35,7 @@ common.saved=\u0417\u0430\u043F\u0438\u0441\u044C \u0441\u043E\u0445\u0440\u0430 common.enabled=\u0417\u0430\u043F\u0438\u0441\u044C \u0430\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u043D\u0430 common.disabled=\u0417\u0430\u043F\u0438\u0441\u044C \u0434\u0435\u0430\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u043D\u0430 common.errorStatus=\u0421\u0442\u0430\u0442\u0443\u0441 \u043E\u0448\u0438\u0431\u043A\u0438 +common.appError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F common.confirm=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B? common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \ No newline at end of file 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 0000000..d5297d8 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -0,0 +1,32 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.ModelAndView; +import ru.javawebinar.topjava.AuthorizedUser; +import ru.javawebinar.topjava.util.ValidationUtil; + +import javax.servlet.http.HttpServletRequest; + +@ControllerAdvice +public class GlobalControllerExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalControllerExceptionHandler.class); + + @ExceptionHandler(Exception.class) + public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { + log.error("Exception at request " + req.getRequestURL(), e); + ModelAndView mav = new ModelAndView("exception/exception"); + Throwable rootCause = ValidationUtil.getRootCause(e); + mav.addObject("exception", rootCause); + mav.addObject("message", rootCause.toString()); + + // Interceptor is not invoked, put userTo + AuthorizedUser authorizedUser = SecurityUtil.safeGet(); + if (authorizedUser != null) { + mav.addObject("userTo", authorizedUser.getUserTo()); + } + return mav; + } +} diff --git a/src/main/webapp/WEB-INF/jsp/exception/exception.jsp b/src/main/webapp/WEB-INF/jsp/exception/exception.jsp new file mode 100644 index 0000000..e2cc3e6 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/exception/exception.jsp @@ -0,0 +1,25 @@ +<%@ page isErrorPage="true" contentType="text/html" pageEncoding="UTF-8" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + + +
    +
    +
    +

    +

    ${message}

    +
    +
    + + + + \ No newline at end of file From 6c2e0d955a94cc700aaf0acbe3b2f5f06e6cdf5b Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 21 Apr 2019 21:54:11 +0500 Subject: [PATCH 110/125] 10_12_controller_advice_exception --- config/curl.md | 5 +- .../topjava/util/ValidationUtil.java | 9 ++- .../topjava/util/exception/ErrorInfo.java | 13 ++++ .../topjava/util/exception/ErrorType.java | 8 +++ .../IllegalRequestDataException.java | 7 ++ .../util/exception/NotFoundException.java | 5 -- .../topjava/web/ExceptionInfoHandler.java | 65 +++++++++++++++++++ .../web/GlobalControllerExceptionHandler.java | 2 +- .../topjava/web/meal/MealUIController.java | 1 + .../topjava/web/user/AdminUIController.java | 1 + .../webapp/resources/js/topjava.common.js | 4 +- 11 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/IllegalRequestDataException.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java diff --git a/config/curl.md b/config/curl.md index f55495e..3a5ee38 100644 --- a/config/curl.md +++ b/config/curl.md @@ -26,4 +26,7 @@ `curl -s -X POST -d '{"dateTime":"2015-06-01T12:00","description":"Created lunch","calories":300}' -H 'Content-Type:application/json;charset=UTF-8' http://localhost:8080/topjava/rest/profile/meals --user user@yandex.ru:password` #### update Meals -`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` \ No newline at end of file +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00", "description":"Updated breakfast", "calories":200}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` + +#### validate with Error +`curl -s -X POST -d '{}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/admin/users --user admin@gmail.com:admin` diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index ffe3a00..e1117e1 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -3,6 +3,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import ru.javawebinar.topjava.HasId; +import ru.javawebinar.topjava.util.exception.IllegalRequestDataException; import ru.javawebinar.topjava.util.exception.NotFoundException; import java.util.StringJoiner; @@ -33,7 +34,7 @@ public static void checkNotFound(boolean found, String msg) { public static void checkNew(HasId bean) { if (!bean.isNew()) { - throw new IllegalArgumentException(bean + " must be new (id=null)"); + throw new IllegalRequestDataException(bean + " must be new (id=null)"); } } @@ -42,7 +43,7 @@ public static void assureIdConsistent(HasId bean, int id) { if (bean.isNew()) { bean.setId(id); } else if (bean.getId() != id) { - throw new IllegalArgumentException(bean + " must be with id=" + id); + throw new IllegalRequestDataException(bean + " must be with id=" + id); } } @@ -71,4 +72,8 @@ public static ResponseEntity getErrorResponse(BindingResult result) { }); return ResponseEntity.unprocessableEntity().body(joiner.toString()); } + + public static String getMessage(Throwable e) { + return e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getClass().getName(); + } } \ No newline at end of file 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 0000000..d433235 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java @@ -0,0 +1,13 @@ +package ru.javawebinar.topjava.util.exception; + +public class ErrorInfo { + private final String url; + private final ErrorType type; + private final String detail; + + public ErrorInfo(CharSequence url, ErrorType type, String detail) { + this.url = url.toString(); + this.type = type; + this.detail = detail; + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java new file mode 100644 index 0000000..c53a433 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java @@ -0,0 +1,8 @@ +package ru.javawebinar.topjava.util.exception; + +public enum ErrorType { + APP_ERROR, + DATA_NOT_FOUND, + DATA_ERROR, + VALIDATION_ERROR +} diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/IllegalRequestDataException.java b/src/main/java/ru/javawebinar/topjava/util/exception/IllegalRequestDataException.java new file mode 100644 index 0000000..2b144f9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/IllegalRequestDataException.java @@ -0,0 +1,7 @@ +package ru.javawebinar.topjava.util.exception; + +public class IllegalRequestDataException extends RuntimeException { + public IllegalRequestDataException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java index 9ff0bbb..f1e9b0e 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -1,10 +1,5 @@ package ru.javawebinar.topjava.util.exception; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -// http://stackoverflow.com/a/22358422/548473 -@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY, reason = "No data found") // 422 public class NotFoundException extends RuntimeException { public NotFoundException(String message) { super(message); 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 0000000..66364a1 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -0,0 +1,65 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import ru.javawebinar.topjava.util.ValidationUtil; +import ru.javawebinar.topjava.util.exception.ErrorInfo; +import ru.javawebinar.topjava.util.exception.ErrorType; +import ru.javawebinar.topjava.util.exception.IllegalRequestDataException; +import ru.javawebinar.topjava.util.exception.NotFoundException; + +import javax.servlet.http.HttpServletRequest; + +import static ru.javawebinar.topjava.util.exception.ErrorType.*; + +@RestControllerAdvice(annotations = RestController.class) +@Order(Ordered.HIGHEST_PRECEDENCE + 5) +public class ExceptionInfoHandler { + private static Logger log = LoggerFactory.getLogger(ExceptionInfoHandler.class); + + // http://stackoverflow.com/a/22358422/548473 + @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(NotFoundException.class) + public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) { + return logAndGetErrorInfo(req, e, false, DATA_NOT_FOUND); + } + + @ResponseStatus(value = HttpStatus.CONFLICT) // 409 + @ExceptionHandler(DataIntegrityViolationException.class) + public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) { + return logAndGetErrorInfo(req, e, true, DATA_ERROR); + } + + @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) // 422 + @ExceptionHandler({IllegalRequestDataException.class, MethodArgumentTypeMismatchException.class, HttpMessageNotReadableException.class}) + public ErrorInfo illegalRequestDataError(HttpServletRequest req, Exception e) { + return logAndGetErrorInfo(req, e, false, VALIDATION_ERROR); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception.class) + public ErrorInfo handleError(HttpServletRequest req, Exception e) { + return logAndGetErrorInfo(req, e, true, APP_ERROR); + } + + // https://stackoverflow.com/questions/538870/should-private-helper-methods-be-static-if-they-can-be-static + private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType) { + Throwable rootCause = ValidationUtil.getRootCause(e); + if (logException) { + log.error(errorType + " at request " + req.getRequestURL(), rootCause); + } else { + log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString()); + } + return new ErrorInfo(req.getRequestURL(), errorType, ValidationUtil.getMessage(rootCause)); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index d5297d8..44e26c0 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -20,7 +20,7 @@ public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) thr ModelAndView mav = new ModelAndView("exception/exception"); Throwable rootCause = ValidationUtil.getRootCause(e); mav.addObject("exception", rootCause); - mav.addObject("message", rootCause.toString()); + mav.addObject("message", ValidationUtil.getMessage(rootCause)); // Interceptor is not invoked, put userTo AuthorizedUser authorizedUser = SecurityUtil.safeGet(); diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java index eb46363..2719ebf 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java @@ -41,6 +41,7 @@ public void delete(@PathVariable int id) { @ResponseStatus(value = HttpStatus.NO_CONTENT) public ResponseEntity createOrUpdate(@Valid Meal meal, BindingResult result) { if (result.hasErrors()) { + // TODO change to exception handler return ValidationUtil.getErrorResponse(result); } if (meal.isNew()) { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java index 5506157..7df8d95 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java @@ -39,6 +39,7 @@ public void delete(@PathVariable int id) { @PostMapping public ResponseEntity createOrUpdate(@Valid UserTo userTo, BindingResult result) { if (result.hasErrors()) { + // TODO change to exception handler return ValidationUtil.getErrorResponse(result); } if (userTo.isNew()) { diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index 342d65f..b7c12b2 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -89,9 +89,9 @@ function successNoty(key) { function failNoty(jqXHR) { closeNoty(); - const responseJSON = JSON.parse(jqXHR.responseText); + const errorInfo = JSON.parse(jqXHR.responseText); failedNote = new Noty({ - text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + (responseJSON ? "
    " + responseJSON : ""), + text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + "
    " + errorInfo.type + "
    " + errorInfo.detail, type: "error", layout: "bottomRight" }).show(); From 0c6667b99a1e86a60e8122bb258d54c78f6d3597 Mon Sep 17 00:00:00 2001 From: Viktor Date: Sun, 21 Apr 2019 21:55:29 +0500 Subject: [PATCH 111/125] 10_13_password_encoding --- .../topjava/service/UserServiceImpl.java | 18 +++++++++++------- .../ru/javawebinar/topjava/util/UserUtil.java | 9 +++++++++ src/main/resources/db/populateDB.sql | 4 ++-- src/main/resources/spring/spring-security.xml | 4 ++-- .../ru/javawebinar/topjava/UserTestData.java | 4 ++-- .../service/AbstractJpaUserServiceTest.java | 2 +- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java index d712a9b..1711a99 100644 --- a/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java +++ b/src/main/java/ru/javawebinar/topjava/service/UserServiceImpl.java @@ -5,6 +5,7 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -12,11 +13,12 @@ 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.NotFoundException; import java.util.List; +import static ru.javawebinar.topjava.util.UserUtil.prepareToSave; +import static ru.javawebinar.topjava.util.UserUtil.updateFromTo; import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFound; import static ru.javawebinar.topjava.util.ValidationUtil.checkNotFoundWithId; @@ -24,17 +26,20 @@ public class UserServiceImpl implements UserService, UserDetailsService { private final UserRepository repository; + private final PasswordEncoder passwordEncoder; @Autowired - public UserServiceImpl(UserRepository repository) { + public UserServiceImpl(UserRepository repository, PasswordEncoder passwordEncoder) { this.repository = repository; + this.passwordEncoder = passwordEncoder; } + @CacheEvict(value = "users", allEntries = true) @Override public User create(User user) { Assert.notNull(user, "user must not be null"); - return repository.save(user); + return repository.save(prepareToSave(user, passwordEncoder)); } @CacheEvict(value = "users", allEntries = true) @@ -64,18 +69,17 @@ public List getAll() { @Override public void update(User user) { Assert.notNull(user, "user must not be null"); - checkNotFoundWithId(repository.save(user), user.getId()); + checkNotFoundWithId(repository.save(prepareToSave(user, passwordEncoder)), user.getId()); } @CacheEvict(value = "users", allEntries = true) @Transactional @Override public void update(UserTo userTo) { - User user = get(userTo.getId()); - repository.save(UserUtil.updateFromTo(user, userTo)); + User user = updateFromTo(get(userTo.getId()), userTo); + repository.save(prepareToSave(user, passwordEncoder)); } - @CacheEvict(value = "users", allEntries = true) @Override @Transactional diff --git a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java index deb693b..d06ea67 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserUtil.java @@ -1,5 +1,7 @@ package ru.javawebinar.topjava.util; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.util.StringUtils; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; @@ -23,4 +25,11 @@ public static User updateFromTo(User user, UserTo userTo) { user.setPassword(userTo.getPassword()); return user; } + + public static User prepareToSave(User user, PasswordEncoder passwordEncoder) { + String password = user.getPassword(); + user.setPassword(StringUtils.isEmpty(password) ? password : passwordEncoder.encode(password)); + user.setEmail(user.getEmail().toLowerCase()); + return user; + } } \ No newline at end of file diff --git a/src/main/resources/db/populateDB.sql b/src/main/resources/db/populateDB.sql index a523a43..ed246b0 100644 --- a/src/main/resources/db/populateDB.sql +++ b/src/main/resources/db/populateDB.sql @@ -4,8 +4,8 @@ DELETE FROM users; ALTER SEQUENCE global_seq RESTART WITH 100000; INSERT INTO users (name, email, password, calories_per_day) VALUES - ('User', 'user@yandex.ru', 'password', 2005), - ('Admin', 'admin@gmail.com', 'admin', 1900); + ('User', 'user@yandex.ru', '{noop}password', 2005), + ('Admin', 'admin@gmail.com', '{noop}admin', 1900); INSERT INTO user_roles (role, user_id) VALUES ('ROLE_USER', 100000), diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml index 7436599..0c090a7 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -15,7 +15,7 @@ - + @@ -33,7 +33,7 @@ - + + diff --git a/src/main/resources/spring/spring-security.xml b/src/main/resources/spring/spring-security.xml index 0c090a7..a4fd5b4 100644 --- a/src/main/resources/spring/spring-security.xml +++ b/src/main/resources/spring/spring-security.xml @@ -28,7 +28,7 @@ authentication-failure-url="/login?error=true" login-processing-url="/spring_security_check"/> - + diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index 306ff50..bed4d46 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -1,29 +1,30 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp index cdcc812..0cfef28 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/headTag.jsp @@ -4,6 +4,10 @@ + + + + <spring:message code="app.title"/> diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index b7c12b2..ae0890b 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -22,6 +22,12 @@ function makeEditable(ctx) { // solve problem with cache in IE: https://stackoverflow.com/a/4303862/548473 $.ajaxSetup({cache: false}); + + const token = $("meta[name='_csrf']").attr("content"); + const header = $("meta[name='_csrf_header']").attr("content"); + $(document).ajaxSend(function (e, xhr, options) { + xhr.setRequestHeader(header, token); + }); } function add() { From 3f82e3e94a1ef7982f4f6e9aafa3ff8ad17fb418 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 14:44:31 +0500 Subject: [PATCH 114/125] 11_01_HW10_fix_encoding --- src/main/webapp/WEB-INF/web.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 55c0167..03f0e1e 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -37,16 +37,6 @@ / - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - springSecurityFilterChain - /* - - encodingFilter org.springframework.web.filter.CharacterEncodingFilter @@ -64,4 +54,14 @@ /* + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + From efbeccccaf0d535301801c8497566a062f50dfb0 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 14:55:41 +0500 Subject: [PATCH 115/125] 11_02_HW10_validation --- config/curl.md | 1 + pom.xml | 8 +++++ .../topjava/util/ValidationUtil.java | 19 ----------- .../topjava/util/exception/ErrorInfo.java | 6 ++-- .../topjava/web/ExceptionInfoHandler.java | 25 +++++++++++++-- .../topjava/web/meal/MealRestController.java | 5 +-- .../topjava/web/meal/MealUIController.java | 10 +----- .../topjava/web/user/AdminRestController.java | 5 +-- .../topjava/web/user/AdminUIController.java | 10 +----- .../web/user/ProfileRestController.java | 5 +-- .../webapp/resources/js/topjava.common.js | 2 +- .../web/meal/MealRestControllerTest.java | 30 +++++++++++++++-- .../web/user/AdminRestControllerTest.java | 32 +++++++++++++++++-- .../web/user/ProfileRestControllerTest.java | 17 ++++++++-- 14 files changed, 119 insertions(+), 56 deletions(-) diff --git a/config/curl.md b/config/curl.md index 3a5ee38..68fb70b 100644 --- a/config/curl.md +++ b/config/curl.md @@ -30,3 +30,4 @@ #### validate with Error `curl -s -X POST -d '{}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/admin/users --user admin@gmail.com:admin` +`curl -s -X PUT -d '{"dateTime":"2015-05-30T07:00"}' -H 'Content-Type: application/json' http://localhost:8080/topjava/rest/profile/meals/100003 --user user@yandex.ru:password` diff --git a/pom.xml b/pom.xml index e3da61c..e093f4d 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ 5.4.1 1.3 1.5.0 + 2.4.0 5.4.1.Final @@ -316,6 +317,13 @@ ${hamcrest.version} test + + com.jayway.jsonpath + json-path + ${json-path.version} + test + + org.springframework spring-test diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index e1117e1..11ef8de 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -1,13 +1,9 @@ package ru.javawebinar.topjava.util; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; import ru.javawebinar.topjava.HasId; import ru.javawebinar.topjava.util.exception.IllegalRequestDataException; import ru.javawebinar.topjava.util.exception.NotFoundException; -import java.util.StringJoiner; - public class ValidationUtil { private ValidationUtil() { @@ -58,21 +54,6 @@ public static Throwable getRootCause(Throwable t) { return result; } - public static ResponseEntity getErrorResponse(BindingResult result) { - StringJoiner joiner = new StringJoiner("
    "); - 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()); - } - public static String getMessage(Throwable e) { return e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getClass().getName(); } diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java index d433235..0c5e15a 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java @@ -3,11 +3,11 @@ public class ErrorInfo { private final String url; private final ErrorType type; - private final String detail; + private final String[] details; - public ErrorInfo(CharSequence url, ErrorType type, String detail) { + public ErrorInfo(CharSequence url, ErrorType type, String... details) { this.url = url.toString(); this.type = type; - this.detail = detail; + this.details = details; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 66364a1..609ed17 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -7,6 +7,9 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -19,6 +22,7 @@ import ru.javawebinar.topjava.util.exception.NotFoundException; import javax.servlet.http.HttpServletRequest; +import java.util.Objects; import static ru.javawebinar.topjava.util.exception.ErrorType.*; @@ -40,6 +44,22 @@ public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationExceptio return logAndGetErrorInfo(req, e, true, DATA_ERROR); } + @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) // 422 + @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class}) + public ErrorInfo bindValidationError(HttpServletRequest req, Exception e) { + BindingResult result = e instanceof BindException ? + ((BindException) e).getBindingResult() : ((MethodArgumentNotValidException) e).getBindingResult(); + + String[] details = result.getFieldErrors().stream() + .map(fe -> { + String msg = fe.getDefaultMessage(); + return msg == null ? null : (msg.startsWith(fe.getField())) ? msg : fe.getField() + ' ' + msg; + }).filter(Objects::nonNull) + .toArray(String[]::new); + + return logAndGetErrorInfo(req, e, false, VALIDATION_ERROR, details); + } + @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) // 422 @ExceptionHandler({IllegalRequestDataException.class, MethodArgumentTypeMismatchException.class, HttpMessageNotReadableException.class}) public ErrorInfo illegalRequestDataError(HttpServletRequest req, Exception e) { @@ -53,13 +73,14 @@ public ErrorInfo handleError(HttpServletRequest req, Exception e) { } // https://stackoverflow.com/questions/538870/should-private-helper-methods-be-static-if-they-can-be-static - private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType) { + private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String... details) { Throwable rootCause = ValidationUtil.getRootCause(e); if (logException) { log.error(errorType + " at request " + req.getRequestURL(), rootCause); } else { log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString()); } - return new ErrorInfo(req.getRequestURL(), errorType, ValidationUtil.getMessage(rootCause)); + return new ErrorInfo(req.getRequestURL(), errorType, + details.length != 0 ? details : new String[]{ValidationUtil.getMessage(rootCause)}); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index 81423f4..1074106 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -8,6 +8,7 @@ import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealTo; +import javax.validation.Valid; import java.net.URI; import java.time.LocalDate; import java.time.LocalTime; @@ -40,12 +41,12 @@ public List getAll() { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(value = HttpStatus.NO_CONTENT) - public void update(@RequestBody Meal meal, @PathVariable int id) { + public void update(@Valid @RequestBody Meal meal, @PathVariable int id) { super.update(meal, id); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@RequestBody Meal meal) { + public ResponseEntity createWithLocation(@Valid @RequestBody Meal meal) { Meal created = super.create(meal); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java index 2719ebf..2cf463f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java @@ -2,12 +2,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealTo; -import ru.javawebinar.topjava.util.ValidationUtil; import javax.validation.Valid; import java.time.LocalDate; @@ -39,17 +36,12 @@ public void delete(@PathVariable int id) { @PostMapping @ResponseStatus(value = HttpStatus.NO_CONTENT) - public ResponseEntity createOrUpdate(@Valid Meal meal, BindingResult result) { - if (result.hasErrors()) { - // TODO change to exception handler - return ValidationUtil.getErrorResponse(result); - } + public void createOrUpdate(@Valid Meal meal) { if (meal.isNew()) { super.create(meal); } else { super.update(meal, meal.getId()); } - return ResponseEntity.ok().build(); } @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index 26631c7..3a9d632 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -7,6 +7,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import ru.javawebinar.topjava.model.User; +import javax.validation.Valid; import java.net.URI; import java.util.List; @@ -29,7 +30,7 @@ public User get(@PathVariable int id) { } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@RequestBody User user) { + public ResponseEntity createWithLocation(@Valid @RequestBody User user) { User created = super.create(user); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() .path(REST_URL + "/{id}") @@ -47,7 +48,7 @@ public void delete(@PathVariable int id) { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(value = HttpStatus.NO_CONTENT) - public void update(@RequestBody User user, @PathVariable int id) { + public void update(@Valid @RequestBody User user, @PathVariable int id) { super.update(user, id); } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java index 7df8d95..9f449b3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java @@ -2,13 +2,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; -import ru.javawebinar.topjava.util.ValidationUtil; import javax.validation.Valid; import java.util.List; @@ -37,17 +34,12 @@ public void delete(@PathVariable int id) { } @PostMapping - public ResponseEntity createOrUpdate(@Valid UserTo userTo, BindingResult result) { - if (result.hasErrors()) { - // TODO change to exception handler - return ValidationUtil.getErrorResponse(result); - } + public void createOrUpdate(@Valid UserTo userTo) { if (userTo.isNew()) { super.create(UserUtil.createNewFromTo(userTo)); } else { super.update(userTo, userTo.getId()); } - return ResponseEntity.ok().build(); } @Override diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index c19345f..e9ed8b3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -9,6 +9,7 @@ import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; +import javax.validation.Valid; import java.net.URI; import static ru.javawebinar.topjava.web.SecurityUtil.authUserId; @@ -31,7 +32,7 @@ public void delete() { @PostMapping(value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(value = HttpStatus.CREATED) - public ResponseEntity register(@RequestBody UserTo userTo) { + public ResponseEntity register(@Valid @RequestBody UserTo userTo) { User created = super.create(UserUtil.createNewFromTo(userTo)); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() .path(REST_URL + "/{id}") @@ -42,7 +43,7 @@ public ResponseEntity register(@RequestBody UserTo userTo) { @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.NO_CONTENT) - public void update(@RequestBody UserTo userTo) { + public void update(@Valid @RequestBody UserTo userTo) { super.update(userTo, authUserId()); } diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index ae0890b..b83cd0a 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -97,7 +97,7 @@ function failNoty(jqXHR) { closeNoty(); const errorInfo = JSON.parse(jqXHR.responseText); failedNote = new Noty({ - text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + "
    " + errorInfo.type + "
    " + errorInfo.detail, + text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + "
    " + errorInfo.type + "
    " + errorInfo.details.join("
    "), type: "error", layout: "bottomRight" }).show(); diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index b634ae0..ffa5231 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -7,13 +7,13 @@ import org.springframework.test.web.servlet.ResultActions; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; +import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static ru.javawebinar.topjava.MealTestData.assertMatch; import static ru.javawebinar.topjava.MealTestData.contentJson; import static ru.javawebinar.topjava.MealTestData.*; @@ -125,4 +125,30 @@ void testFilterAll() throws Exception { .andExpect(status().isOk()) .andExpect(contentJson(getWithExcess(MEALS, USER.getCaloriesPerDay()))); } + + @Test + void testCreateInvalid() throws Exception { + Meal invalid = new Meal(null, null, "Dummy", 200); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(ADMIN))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } + + @Test + void testUpdateInvalid() throws Exception { + Meal invalid = new Meal(MEAL1_ID, null, null, 6000); + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 4152b2e..279be94 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -5,6 +5,7 @@ import org.springframework.test.web.servlet.ResultActions; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; @@ -12,8 +13,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static ru.javawebinar.topjava.TestUtil.readFromJson; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; @@ -88,7 +88,7 @@ void testUpdate() throws Exception { mockMvc.perform(put(REST_URL + USER_ID) .contentType(MediaType.APPLICATION_JSON) .with(userHttpBasic(ADMIN)) - .content(JsonUtil.writeValue(updated))) + .content(jsonWithPassword(updated, USER.getPassword()))) .andExpect(status().isNoContent()); assertMatch(userService.get(USER_ID), updated); @@ -118,4 +118,30 @@ void testGetAll() throws Exception { .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(contentJson(ADMIN, USER)); } + + @Test + void testCreateInvalid() throws Exception { + User expected = new User(null, null, "", "newPass", 7300, Role.ROLE_USER, Role.ROLE_ADMIN); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(JsonUtil.writeValue(expected))) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } + + @Test + void testUpdateInvalid() throws Exception { + User updated = new User(USER); + updated.setName(""); + mockMvc.perform(put(REST_URL + USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(JsonUtil.writeValue(updated))) + .andExpect(status().isUnprocessableEntity()) + .andDo(print()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index dcc212b..e619029 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -6,13 +6,13 @@ import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; +import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static ru.javawebinar.topjava.TestUtil.readFromJson; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; @@ -72,4 +72,17 @@ void testUpdate() throws Exception { assertMatch(userService.getByEmail("newemail@ya.ru"), UserUtil.updateFromTo(new User(USER), updatedTo)); } + + @Test + void testUpdateInvalid() throws Exception { + UserTo updatedTo = new UserTo(null, null, "password", null, 1500); + + mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER)) + .content(JsonUtil.writeValue(updatedTo))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andDo(print()); + } } \ No newline at end of file From 537d656ae3e21887cc4dd3cfa8968a4b15b93aeb Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 14:56:18 +0500 Subject: [PATCH 116/125] 11_03_HW10_duplicate_email --- config/app.properties | 4 +++- config/app_ru.properties | 4 +++- .../topjava/web/RootController.java | 16 ++++++++++++--- .../web/user/AbstractUserController.java | 2 ++ .../topjava/web/user/AdminUIController.java | 20 +++++++++++++++---- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/config/app.properties b/config/app.properties index dbb29e3..364eecf 100644 --- a/config/app.properties +++ b/config/app.properties @@ -38,4 +38,6 @@ common.errorStatus=Error status common.appError=Application error common.confirm=Are you sure? common.save=Save -common.cancel=Cancel \ No newline at end of file +common.cancel=Cancel + +exception.user.duplicateEmail=User with this email already exists \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index b0c3eae..fc5f6b2 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -38,4 +38,6 @@ common.errorStatus=\u0421\u0442\u0430\u0442\u0443\u0441 \u043E\u0448\u0438\u0431 common.appError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F common.confirm=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B? common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C -common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \ No newline at end of file +common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C + +exception.user.duplicateEmail=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0441 \u0442\u0430\u043A\u043E\u0439 \u043F\u043E\u0447\u0442\u043E\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0432 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438 \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 99bc486..1d582e9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,5 +1,6 @@ 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; @@ -21,7 +22,7 @@ public String root() { return "redirect:meals"; } -// @Secured("ROLE_ADMIN") + // @Secured("ROLE_ADMIN") @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/users") public String users() { @@ -47,11 +48,15 @@ public String profile() { public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status) { if (result.hasErrors()) { return "profile"; - } else { + } + try { super.update(userTo, SecurityUtil.authUserId()); SecurityUtil.get().update(userTo); status.setComplete(); return "redirect:meals"; + } catch (DataIntegrityViolationException ex) { + result.rejectValue("email", EXCEPTION_DUPLICATE_EMAIL); + return "profile"; } } @@ -67,10 +72,15 @@ public String saveRegister(@Valid UserTo userTo, BindingResult result, SessionSt if (result.hasErrors()) { model.addAttribute("register", true); return "profile"; - } else { + } + try { super.create(UserUtil.createNewFromTo(userTo)); status.setComplete(); return "redirect:login?message=app.registered&username=" + userTo.getEmail(); + } catch (DataIntegrityViolationException ex) { + result.rejectValue("email", EXCEPTION_DUPLICATE_EMAIL); + model.addAttribute("register", true); + return "profile"; } } } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index b9c5e54..9686240 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -15,6 +15,8 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); + public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail"; + @Autowired private UserService service; diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java index 9f449b3..adf8cc2 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java @@ -1,11 +1,16 @@ package ru.javawebinar.topjava.web.user; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.dao.DataIntegrityViolationException; 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 ru.javawebinar.topjava.util.UserUtil; +import ru.javawebinar.topjava.util.exception.IllegalRequestDataException; import javax.validation.Valid; import java.util.List; @@ -14,6 +19,9 @@ @RequestMapping("/ajax/admin/users") public class AdminUIController extends AbstractUserController { + @Autowired + private MessageSource messageSource; + @Override @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { @@ -35,10 +43,14 @@ public void delete(@PathVariable int id) { @PostMapping public void createOrUpdate(@Valid UserTo userTo) { - if (userTo.isNew()) { - super.create(UserUtil.createNewFromTo(userTo)); - } else { - super.update(userTo, userTo.getId()); + try { + if (userTo.isNew()) { + super.create(UserUtil.createNewFromTo(userTo)); + } else { + super.update(userTo, userTo.getId()); + } + } catch (DataIntegrityViolationException e) { + throw new IllegalRequestDataException(messageSource.getMessage(EXCEPTION_DUPLICATE_EMAIL, null, LocaleContextHolder.getLocale())); } } From 7c52aac6218be82c418e4bb7a2c58bb130ddbc41 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 14:58:15 +0500 Subject: [PATCH 117/125] 11_04_HW10_duplicate_datetime --- .travis.yml | 4 ++ config/app.properties | 3 +- config/app_ru.properties | 3 +- .../topjava/web/ExceptionInfoHandler.java | 23 +++++++++++ .../javawebinar/topjava/web/MessageUtil.java | 27 ++++++++++++ .../topjava/web/RootController.java | 2 + .../web/user/AbstractUserController.java | 2 - .../topjava/web/user/AdminUIController.java | 20 ++------- .../topjava/web/AbstractControllerTest.java | 19 +++++++++ .../web/meal/MealRestControllerTest.java | 40 ++++++++++++++++-- .../web/user/AdminRestControllerTest.java | 41 +++++++++++++++++-- .../web/user/ProfileRestControllerTest.java | 24 +++++++++-- 12 files changed, 178 insertions(+), 30 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/MessageUtil.java diff --git a/.travis.yml b/.travis.yml index ad4c8b9..745a033 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,10 @@ branches: only: - master +#https://docs.travis-ci.com/user/environment-variables#Default-Environment-Variables +env: +- TOPJAVA_ROOT=$TRAVIS_BUILD_DIR + # https://docs.travis-ci.com/user/notifications#Configuring-email-notifications #notifications: # email: false \ No newline at end of file diff --git a/config/app.properties b/config/app.properties index 364eecf..f408d5d 100644 --- a/config/app.properties +++ b/config/app.properties @@ -40,4 +40,5 @@ common.confirm=Are you sure? common.save=Save common.cancel=Cancel -exception.user.duplicateEmail=User with this email already exists \ No newline at end of file +exception.user.duplicateEmail=User with this email already exists +exception.meal.duplicateDateTime=You already have meal with this date/time \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index fc5f6b2..58a1dd8 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -40,4 +40,5 @@ common.confirm=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B? common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C -exception.user.duplicateEmail=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0441 \u0442\u0430\u043A\u043E\u0439 \u043F\u043E\u0447\u0442\u043E\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0432 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438 \ No newline at end of file +exception.user.duplicateEmail=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0441 \u0442\u0430\u043A\u043E\u0439 \u043F\u043E\u0447\u0442\u043E\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0432 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438 +exception.meal.duplicateDateTime=\u0423 \u0432\u0430\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0435\u0434\u0430 \u0441 \u0442\u0430\u043A\u043E\u0439 \u0434\u0430\u0442\u043E\u0439/\u0432\u0440\u0435\u043C\u0435\u043D\u0435\u043C \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 609ed17..96f9dd9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; @@ -22,7 +23,9 @@ import ru.javawebinar.topjava.util.exception.NotFoundException; import javax.servlet.http.HttpServletRequest; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import static ru.javawebinar.topjava.util.exception.ErrorType.*; @@ -31,6 +34,16 @@ public class ExceptionInfoHandler { private static Logger log = LoggerFactory.getLogger(ExceptionInfoHandler.class); + public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail"; + public static final String EXCEPTION_DUPLICATE_DATETIME = "exception.meal.duplicateDateTime"; + + private static final Map CONSTRAINS_I18N_MAP = Map.of( + "users_unique_email_idx", EXCEPTION_DUPLICATE_EMAIL, + "meals_unique_user_datetime_idx", EXCEPTION_DUPLICATE_DATETIME); + + @Autowired + private MessageUtil messageUtil; + // http://stackoverflow.com/a/22358422/548473 @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(NotFoundException.class) @@ -41,6 +54,16 @@ public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) { @ResponseStatus(value = HttpStatus.CONFLICT) // 409 @ExceptionHandler(DataIntegrityViolationException.class) public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) { + String rootMsg = ValidationUtil.getRootCause(e).getMessage(); + if (rootMsg != null) { + String lowerCaseMsg = rootMsg.toLowerCase(); + Optional> entry = CONSTRAINS_I18N_MAP.entrySet().stream() + .filter(it -> lowerCaseMsg.contains(it.getKey())) + .findAny(); + if (entry.isPresent()) { + return logAndGetErrorInfo(req, e, false, VALIDATION_ERROR, messageUtil.getMessage(entry.get().getValue())); + } + } return logAndGetErrorInfo(req, e, true, DATA_ERROR); } diff --git a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java new file mode 100644 index 0000000..d4f2add --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java @@ -0,0 +1,27 @@ +package ru.javawebinar.topjava.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Locale; + +@Component +public class MessageUtil { + + private final MessageSource messageSource; + + @Autowired + public MessageUtil(MessageSource messageSource) { + this.messageSource = messageSource; + } + + public String getMessage(String code, Locale locale) { + return messageSource.getMessage(code, null, locale); + } + + public String getMessage(String code) { + return getMessage(code, LocaleContextHolder.getLocale()); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index 1d582e9..e91d2e8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -14,6 +14,8 @@ import javax.validation.Valid; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL; + @Controller public class RootController extends AbstractUserController { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index 9686240..b9c5e54 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -15,8 +15,6 @@ public abstract class AbstractUserController { protected final Logger log = LoggerFactory.getLogger(getClass()); - public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail"; - @Autowired private UserService service; diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java index adf8cc2..9f449b3 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminUIController.java @@ -1,16 +1,11 @@ package ru.javawebinar.topjava.web.user; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.MessageSource; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.dao.DataIntegrityViolationException; 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 ru.javawebinar.topjava.util.UserUtil; -import ru.javawebinar.topjava.util.exception.IllegalRequestDataException; import javax.validation.Valid; import java.util.List; @@ -19,9 +14,6 @@ @RequestMapping("/ajax/admin/users") public class AdminUIController extends AbstractUserController { - @Autowired - private MessageSource messageSource; - @Override @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getAll() { @@ -43,14 +35,10 @@ public void delete(@PathVariable int id) { @PostMapping public void createOrUpdate(@Valid UserTo userTo) { - try { - if (userTo.isNew()) { - super.create(UserUtil.createNewFromTo(userTo)); - } else { - super.update(userTo, userTo.getId()); - } - } catch (DataIntegrityViolationException e) { - throw new IllegalRequestDataException(messageSource.getMessage(EXCEPTION_DUPLICATE_EMAIL, null, LocaleContextHolder.getLocale())); + if (userTo.isNew()) { + super.create(UserUtil.createNewFromTo(userTo)); + } else { + super.update(userTo, userTo.getId()); } } diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index bdcd803..588e9e9 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -6,6 +6,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; @@ -13,10 +14,13 @@ import ru.javawebinar.topjava.AllActiveProfileResolver; import ru.javawebinar.topjava.repository.JpaUtil; import ru.javawebinar.topjava.service.UserService; +import ru.javawebinar.topjava.util.exception.ErrorType; import javax.annotation.PostConstruct; +import java.util.Locale; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @SpringJUnitWebConfig(locations = { "classpath:spring/spring-app.xml", @@ -50,6 +54,9 @@ abstract public class AbstractControllerTest { @Autowired private WebApplicationContext webApplicationContext; + @Autowired + protected MessageUtil messageUtil; + @PostConstruct private void postConstruct() { mockMvc = MockMvcBuilders @@ -66,4 +73,16 @@ void setUp() { jpaUtil.clear2ndLevelHibernateCache(); } } + + private String getMessage(String code) { + return messageUtil.getMessage(code, Locale.ENGLISH); + } + + public ResultMatcher errorType(ErrorType type) { + return jsonPath("$.type").value(type.name()); + } + + public ResultMatcher detailMessage(String code) { + return jsonPath("$.details").value(getMessage(code)); + } } diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index ffa5231..c2e65f2 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -5,6 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; import ru.javawebinar.topjava.util.exception.ErrorType; @@ -13,7 +15,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.MealTestData.assertMatch; import static ru.javawebinar.topjava.MealTestData.contentJson; import static ru.javawebinar.topjava.MealTestData.*; @@ -22,6 +25,8 @@ import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ; import static ru.javawebinar.topjava.util.MealsUtil.createWithExcess; import static ru.javawebinar.topjava.util.MealsUtil.getWithExcess; +import static ru.javawebinar.topjava.util.exception.ErrorType.VALIDATION_ERROR; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_DATETIME; class MealRestControllerTest extends AbstractControllerTest { @@ -135,7 +140,7 @@ void testCreateInvalid() throws Exception { .with(userHttpBasic(ADMIN))) .andDo(print()) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(VALIDATION_ERROR)) .andDo(print()); } @@ -148,7 +153,36 @@ void testUpdateInvalid() throws Exception { .with(userHttpBasic(USER))) .andDo(print()) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(VALIDATION_ERROR)) .andDo(print()); } + + @Test + @Transactional(propagation = Propagation.NEVER) + void testUpdateDuplicate() throws Exception { + Meal invalid = new Meal(MEAL1_ID, MEAL2.getDateTime(), "Dummy", 200); + + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isConflict()) + .andExpect(errorType(VALIDATION_ERROR)) + .andExpect(detailMessage(EXCEPTION_DUPLICATE_DATETIME)); + } + + @Test + @Transactional(propagation = Propagation.NEVER) + void testCreateDuplicate() throws Exception { + Meal invalid = new Meal(null, ADMIN_MEAL1.getDateTime(), "Dummy", 200); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(ADMIN))) + .andDo(print()) + .andExpect(status().isConflict()) + .andExpect(errorType(VALIDATION_ERROR)) + .andExpect(detailMessage(EXCEPTION_DUPLICATE_DATETIME)); + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index 279be94..f895e30 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -3,9 +3,10 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Role; import ru.javawebinar.topjava.model.User; -import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; @@ -13,10 +14,13 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.TestUtil.readFromJson; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.util.exception.ErrorType.VALIDATION_ERROR; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL; class AdminRestControllerTest extends AbstractControllerTest { @@ -127,7 +131,7 @@ void testCreateInvalid() throws Exception { .with(userHttpBasic(ADMIN)) .content(JsonUtil.writeValue(expected))) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(VALIDATION_ERROR)) .andDo(print()); } @@ -141,7 +145,36 @@ void testUpdateInvalid() throws Exception { .content(JsonUtil.writeValue(updated))) .andExpect(status().isUnprocessableEntity()) .andDo(print()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(VALIDATION_ERROR)) .andDo(print()); } + + @Test + @Transactional(propagation = Propagation.NEVER) + void testUpdateDuplicate() throws Exception { + User updated = new User(USER); + updated.setEmail("admin@gmail.com"); + mockMvc.perform(put(REST_URL + USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(jsonWithPassword(updated, "password"))) + .andExpect(status().isConflict()) + .andExpect(errorType(VALIDATION_ERROR)) + .andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL)) + .andDo(print()); + } + + @Test + @Transactional(propagation = Propagation.NEVER) + void testCreateDuplicate() throws Exception { + User expected = new User(null, "New", "user@yandex.ru", "newPass", 2300, Role.ROLE_USER, Role.ROLE_ADMIN); + mockMvc.perform(post(REST_URL) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(jsonWithPassword(expected, "newPass"))) + .andExpect(status().isConflict()) + .andExpect(errorType(VALIDATION_ERROR)) + .andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL)); + + } } \ No newline at end of file diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index e619029..1903c09 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -3,19 +3,23 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; -import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static ru.javawebinar.topjava.TestUtil.readFromJson; import static ru.javawebinar.topjava.TestUtil.userHttpBasic; import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.util.exception.ErrorType.VALIDATION_ERROR; +import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL; import static ru.javawebinar.topjava.web.user.ProfileRestController.REST_URL; class ProfileRestControllerTest extends AbstractControllerTest { @@ -82,7 +86,21 @@ void testUpdateInvalid() throws Exception { .content(JsonUtil.writeValue(updatedTo))) .andDo(print()) .andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name())) + .andExpect(errorType(VALIDATION_ERROR)) + .andDo(print()); + } + + @Test + @Transactional(propagation = Propagation.NEVER) + void testDuplicate() throws Exception { + UserTo updatedTo = new UserTo(null, "newName", "admin@gmail.com", "newPassword", 1500); + + mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(USER)) + .content(JsonUtil.writeValue(updatedTo))) + .andExpect(status().isConflict()) + .andExpect(errorType(VALIDATION_ERROR)) + .andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL)) .andDo(print()); } } \ No newline at end of file From 52994f5bca39c4ea00b7bb1417650d5ce22104dc Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:00:37 +0500 Subject: [PATCH 118/125] 11_05_HW10_binder_validation --- .../java/ru/javawebinar/topjava/HasEmail.java | 5 +++ .../ru/javawebinar/topjava/model/User.java | 4 ++- .../ru/javawebinar/topjava/to/UserTo.java | 6 ++-- .../topjava/web/ExceptionInfoHandler.java | 2 +- .../javawebinar/topjava/web/MessageUtil.java | 5 +++ .../topjava/web/RootController.java | 28 ++++------------ .../web/user/AbstractUserController.java | 10 ++++++ .../topjava/web/user/UniqueMailValidator.java | 32 +++++++++++++++++++ src/main/webapp/WEB-INF/jsp/profile.jsp | 2 +- .../web/user/AdminRestControllerTest.java | 4 +-- .../web/user/ProfileRestControllerTest.java | 2 +- 11 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/HasEmail.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/user/UniqueMailValidator.java diff --git a/src/main/java/ru/javawebinar/topjava/HasEmail.java b/src/main/java/ru/javawebinar/topjava/HasEmail.java new file mode 100644 index 0000000..2bc3342 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/HasEmail.java @@ -0,0 +1,5 @@ +package ru.javawebinar.topjava; + +public interface HasEmail extends HasId { + String getEmail(); +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index a852243..d602ea4 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -6,6 +6,7 @@ import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Range; import org.springframework.util.CollectionUtils; +import ru.javawebinar.topjava.HasEmail; import javax.persistence.*; import javax.validation.constraints.Email; @@ -24,7 +25,7 @@ }) @Entity @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "email", name = "users_unique_email_idx")}) -public class User extends AbstractNamedEntity { +public class User extends AbstractNamedEntity implements HasEmail { public static final String DELETE = "User.delete"; public static final String BY_EMAIL = "User.getByEmail"; @@ -90,6 +91,7 @@ public User(Integer id, String name, String email, String password, int calories setRoles(roles); } + @Override public String getEmail() { return email; } diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index 6478866..a89be42 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -1,6 +1,7 @@ package ru.javawebinar.topjava.to; import org.hibernate.validator.constraints.Range; +import ru.javawebinar.topjava.HasEmail; import ru.javawebinar.topjava.util.UserUtil; import javax.validation.constraints.Email; @@ -9,7 +10,7 @@ import javax.validation.constraints.Size; import java.io.Serializable; -public class UserTo extends BaseTo implements Serializable { +public class UserTo extends BaseTo implements HasEmail, Serializable { private static final long serialVersionUID = 1L; @NotBlank @@ -21,7 +22,7 @@ public class UserTo extends BaseTo implements Serializable { @Size(max = 100) private String email; - @Size(min = 5, max = 32, message = "length must between 5 and 32 characters") + @Size(min = 5, max = 32) private String password; @Range(min = 10, max = 10000) @@ -55,6 +56,7 @@ public void setName(String name) { this.name = name; } + @Override public String getEmail() { return email; } diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 96f9dd9..9606b52 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -76,7 +76,7 @@ public ErrorInfo bindValidationError(HttpServletRequest req, Exception e) { String[] details = result.getFieldErrors().stream() .map(fe -> { String msg = fe.getDefaultMessage(); - return msg == null ? null : (msg.startsWith(fe.getField())) ? msg : fe.getField() + ' ' + msg; + return msg == null ? messageUtil.getMessage(fe) : (msg.startsWith(fe.getField())) ? msg : fe.getField() + ' ' + msg; }).filter(Objects::nonNull) .toArray(String[]::new); diff --git a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java index d4f2add..414ca3e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; @@ -24,4 +25,8 @@ public String getMessage(String code, Locale locale) { public String getMessage(String code) { return getMessage(code, LocaleContextHolder.getLocale()); } + + public String getMessage(MessageSourceResolvable resolvable) { + return messageSource.getMessage(resolvable, LocaleContextHolder.getLocale()); + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/RootController.java b/src/main/java/ru/javawebinar/topjava/web/RootController.java index e91d2e8..9265f0f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,6 +1,5 @@ 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; @@ -14,8 +13,6 @@ import javax.validation.Valid; -import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL; - @Controller public class RootController extends AbstractUserController { @@ -51,15 +48,10 @@ public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionS if (result.hasErrors()) { return "profile"; } - try { - super.update(userTo, SecurityUtil.authUserId()); - SecurityUtil.get().update(userTo); - status.setComplete(); - return "redirect:meals"; - } catch (DataIntegrityViolationException ex) { - result.rejectValue("email", EXCEPTION_DUPLICATE_EMAIL); - return "profile"; - } + super.update(userTo, SecurityUtil.authUserId()); + SecurityUtil.get().update(userTo); + status.setComplete(); + return "redirect:meals"; } @GetMapping("/register") @@ -75,14 +67,8 @@ public String saveRegister(@Valid UserTo userTo, BindingResult result, SessionSt model.addAttribute("register", true); return "profile"; } - try { - super.create(UserUtil.createNewFromTo(userTo)); - status.setComplete(); - return "redirect:login?message=app.registered&username=" + userTo.getEmail(); - } catch (DataIntegrityViolationException ex) { - result.rejectValue("email", EXCEPTION_DUPLICATE_EMAIL); - model.addAttribute("register", true); - return "profile"; - } + super.create(UserUtil.createNewFromTo(userTo)); + status.setComplete(); + return "redirect:login?message=app.registered&username=" + userTo.getEmail(); } } diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index b9c5e54..98bbec9 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -3,6 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; import ru.javawebinar.topjava.to.UserTo; @@ -18,6 +20,14 @@ public abstract class AbstractUserController { @Autowired private UserService service; + @Autowired + private UniqueMailValidator emailValidator; + + @InitBinder + protected void initBinder(WebDataBinder binder) { + binder.addValidators(emailValidator); + } + public List getAll() { log.info("getAll"); return service.getAll(); diff --git a/src/main/java/ru/javawebinar/topjava/web/user/UniqueMailValidator.java b/src/main/java/ru/javawebinar/topjava/web/user/UniqueMailValidator.java new file mode 100644 index 0000000..cc56d8c --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/user/UniqueMailValidator.java @@ -0,0 +1,32 @@ +package ru.javawebinar.topjava.web.user; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import ru.javawebinar.topjava.HasEmail; +import ru.javawebinar.topjava.model.User; +import ru.javawebinar.topjava.repository.UserRepository; +import ru.javawebinar.topjava.web.ExceptionInfoHandler; + + +@Component +public class UniqueMailValidator implements org.springframework.validation.Validator { + + @Autowired + private UserRepository repository; + + @Override + public boolean supports(Class clazz) { + return HasEmail.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + HasEmail user = ((HasEmail) target); + User dbUser = repository.getByEmail(user.getEmail().toLowerCase()); + if (dbUser != null && !dbUser.getId().equals(user.getId())) { + errors.rejectValue("email", ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL); + } + } +} diff --git a/src/main/webapp/WEB-INF/jsp/profile.jsp b/src/main/webapp/WEB-INF/jsp/profile.jsp index 162aaa5..911b167 100644 --- a/src/main/webapp/WEB-INF/jsp/profile.jsp +++ b/src/main/webapp/WEB-INF/jsp/profile.jsp @@ -17,7 +17,7 @@

    ${userTo.name}

    - + diff --git a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java index f895e30..b190aad 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/AdminRestControllerTest.java @@ -158,7 +158,7 @@ void testUpdateDuplicate() throws Exception { .contentType(MediaType.APPLICATION_JSON) .with(userHttpBasic(ADMIN)) .content(jsonWithPassword(updated, "password"))) - .andExpect(status().isConflict()) + .andExpect(status().isUnprocessableEntity()) .andExpect(errorType(VALIDATION_ERROR)) .andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL)) .andDo(print()); @@ -172,7 +172,7 @@ void testCreateDuplicate() throws Exception { .contentType(MediaType.APPLICATION_JSON) .with(userHttpBasic(ADMIN)) .content(jsonWithPassword(expected, "newPass"))) - .andExpect(status().isConflict()) + .andExpect(status().isUnprocessableEntity()) .andExpect(errorType(VALIDATION_ERROR)) .andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL)); diff --git a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java index 1903c09..a0207f5 100644 --- a/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/user/ProfileRestControllerTest.java @@ -98,7 +98,7 @@ void testDuplicate() throws Exception { mockMvc.perform(put(REST_URL).contentType(MediaType.APPLICATION_JSON) .with(userHttpBasic(USER)) .content(JsonUtil.writeValue(updatedTo))) - .andExpect(status().isConflict()) + .andExpect(status().isUnprocessableEntity()) .andExpect(errorType(VALIDATION_ERROR)) .andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL)) .andDo(print()); From 3d01ae84c09c000de7196caa4c5cd990c928d8b9 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:03:09 +0500 Subject: [PATCH 119/125] 11_06_HW10_change_locale --- .../javawebinar/topjava/web/MessageUtil.java | 1 + src/main/resources/spring/spring-mvc.xml | 8 +++ .../WEB-INF/jsp/fragments/bodyHeader.jsp | 65 ++++++++++++------- src/main/webapp/resources/js/topjava.meals.js | 2 + .../topjava/web/AbstractControllerTest.java | 3 +- 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java index 414ca3e..ebf65b5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java @@ -10,6 +10,7 @@ @Component public class MessageUtil { + public static final Locale RU_LOCALE = new Locale("ru"); private final MessageSource messageSource; diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 79ff4da..3de55d1 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -70,7 +70,15 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index bed4d46..a6a4c9d 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -3,28 +3,49 @@ <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> - + \ 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 index ea0df8f..3596cd4 100644 --- a/src/main/webapp/resources/js/topjava.meals.js +++ b/src/main/webapp/resources/js/topjava.meals.js @@ -64,6 +64,8 @@ $(function () { updateTable: updateFilteredTable }); + $.datetimepicker.setLocale(localeCode); + // http://xdsoft.net/jqplugins/datetimepicker/ const startDate = $('#startDate'); const endDate = $('#endDate'); diff --git a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java index 588e9e9..5f12928 100644 --- a/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/AbstractControllerTest.java @@ -17,7 +17,6 @@ import ru.javawebinar.topjava.util.exception.ErrorType; import javax.annotation.PostConstruct; -import java.util.Locale; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -75,7 +74,7 @@ void setUp() { } private String getMessage(String code) { - return messageUtil.getMessage(code, Locale.ENGLISH); + return messageUtil.getMessage(code, MessageUtil.RU_LOCALE); } public ResultMatcher errorType(ErrorType type) { From 971b2c1ac96954a588fabbe3b237ddd8746613a6 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:03:55 +0500 Subject: [PATCH 120/125] 11_07_i18n --- config/app.properties | 23 ++++++++++++++++--- config/app_ru.properties | 21 ++++++++++++++++- .../topjava/util/exception/ErrorInfo.java | 4 +++- .../topjava/util/exception/ErrorType.java | 18 +++++++++++---- .../topjava/web/ExceptionInfoHandler.java | 9 +++----- .../web/GlobalControllerExceptionHandler.java | 10 ++++++-- .../WEB-INF/jsp/exception/exception.jsp | 2 +- .../webapp/WEB-INF/jsp/fragments/i18n.jsp | 2 +- src/main/webapp/resources/css/style.css | 5 ++++ .../webapp/resources/js/topjava.common.js | 5 +++- 10 files changed, 79 insertions(+), 20 deletions(-) diff --git a/config/app.properties b/config/app.properties index f408d5d..03a14b2 100644 --- a/config/app.properties +++ b/config/app.properties @@ -16,6 +16,11 @@ user.registered=Registered user.password=Password user.caloriesPerDay=Daily calorie limit +userTo.name=Name +userTo.email=Email +userTo.password=Password +userTo.caloriesPerDay=Daily calorie limit + meal.title=Meals meal.edit=Edit meal meal.add=Add meal @@ -34,11 +39,23 @@ common.deleted=Record deleted common.saved=Record saved common.enabled=Record enabled common.disabled=Record disabled -common.errorStatus=Error status -common.appError=Application error common.confirm=Are you sure? common.save=Save common.cancel=Cancel +common.search=Search exception.user.duplicateEmail=User with this email already exists -exception.meal.duplicateDateTime=You already have meal with this date/time \ No newline at end of file +exception.meal.duplicateDateTime=You already have meal with this date/time + +error.appError=Application error +error.dataNotFound=Data not found +error.dataError=Data error +error.validationError=Validation error + +NotEmpty="{0}" must not be empty +NotBlank="{0}" must not be empty +NotNull="{0}" must not be empty +Email= Invalid format of "{0}" +Range="{0}" must be between {2} and {1} +Length="{0}" length must be between {2} and {1} +Size="{0}" size must be between {2} and {1} \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index 58a1dd8..dd97d7a 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -16,6 +16,11 @@ user.registered=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u04 user.password=\u041F\u0430\u0440\u043E\u043B\u044C user.caloriesPerDay=\u041D\u043E\u0440\u043C\u0430 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 \u0432 \u0434\u0435\u043D\u044C +userTo.name=\u0418\u043C\u044F +userTo.email=\u041F\u043E\u0447\u0442\u0430 +userTo.password=\u041F\u0430\u0440\u043E\u043B\u044C +userTo.caloriesPerDay=\u041D\u043E\u0440\u043C\u0430 \u043A\u0430\u043B\u043E\u0440\u0438\u0439 \u0432 \u0434\u0435\u043D\u044C + meal.title=\u041C\u043E\u044F \u0435\u0434\u0430 meal.edit=\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0435\u0434\u044B meal.add=\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0435\u0434\u044B @@ -39,6 +44,20 @@ common.appError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u043B\u0 common.confirm=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B? common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C +common.search=\u0418\u0441\u043A\u0430\u0442\u044C exception.user.duplicateEmail=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0441 \u0442\u0430\u043A\u043E\u0439 \u043F\u043E\u0447\u0442\u043E\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0432 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438 -exception.meal.duplicateDateTime=\u0423 \u0432\u0430\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0435\u0434\u0430 \u0441 \u0442\u0430\u043A\u043E\u0439 \u0434\u0430\u0442\u043E\u0439/\u0432\u0440\u0435\u043C\u0435\u043D\u0435\u043C \ No newline at end of file +exception.meal.duplicateDateTime=\u0423 \u0432\u0430\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0435\u0434\u0430 \u0441 \u0442\u0430\u043A\u043E\u0439 \u0434\u0430\u0442\u043E\u0439/\u0432\u0440\u0435\u043C\u0435\u043D\u0435\u043C + +error.appError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F +error.dataNotFound=\u0414\u0430\u043D\u043D\u044B\u0435 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B +error.dataError=\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u0434\u0430\u043D\u043D\u044B\u0445 +error.validationError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 + +NotEmpty=\u041F\u043E\u043B\u0435 "{0}" \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C +NotBlank=\u041F\u043E\u043B\u0435 "{0}" \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C +NotNull=\u041F\u043E\u043B\u0435 "{0}" \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C +Email=\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u043F\u043E\u043B\u044F "{0}" +Range=\u041F\u043E\u043B\u0435 "{0}" \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043C\u0435\u0436\u0434\u0443 {2} \u0438 {1} +Length=\u0414\u043B\u0438\u043D\u043D\u0430 \u043F\u043E\u043B\u044F "{0}" \u0434\u043E\u043B\u0436\u0435\u043D\u0430 \u0431\u044B\u0442\u044C \u043C\u0435\u0436\u0434\u0443 {2} \u0438 {1} +Size=\u0420\u0430\u0437\u043C\u0435\u0440 \u043F\u043E\u043B\u044F "{0}" \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0435\u0436\u0434\u0443 {2} \u0438 {1} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java index 0c5e15a..6a1f7a1 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorInfo.java @@ -3,11 +3,13 @@ public class ErrorInfo { private final String url; private final ErrorType type; + private final String typeMessage; private final String[] details; - public ErrorInfo(CharSequence url, ErrorType type, String... details) { + public ErrorInfo(CharSequence url, ErrorType type, String typeMessage, String... details) { this.url = url.toString(); this.type = type; + this.typeMessage = typeMessage; this.details = details; } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java index c53a433..cb231e1 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java @@ -1,8 +1,18 @@ package ru.javawebinar.topjava.util.exception; public enum ErrorType { - APP_ERROR, - DATA_NOT_FOUND, - DATA_ERROR, - VALIDATION_ERROR + APP_ERROR("error.appError"), + DATA_NOT_FOUND("error.dataNotFound"), + DATA_ERROR("error.dataError"), + VALIDATION_ERROR("error.validationError"); + + private final String errorCode; + + ErrorType(String errorCode) { + this.errorCode = errorCode; + } + + public String getErrorCode() { + return errorCode; + } } diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 9606b52..19fa1ca 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -74,10 +74,7 @@ public ErrorInfo bindValidationError(HttpServletRequest req, Exception e) { ((BindException) e).getBindingResult() : ((MethodArgumentNotValidException) e).getBindingResult(); String[] details = result.getFieldErrors().stream() - .map(fe -> { - String msg = fe.getDefaultMessage(); - return msg == null ? messageUtil.getMessage(fe) : (msg.startsWith(fe.getField())) ? msg : fe.getField() + ' ' + msg; - }).filter(Objects::nonNull) + .map(fe -> messageUtil.getMessage(fe)) .toArray(String[]::new); return logAndGetErrorInfo(req, e, false, VALIDATION_ERROR, details); @@ -95,8 +92,7 @@ public ErrorInfo handleError(HttpServletRequest req, Exception e) { return logAndGetErrorInfo(req, e, true, APP_ERROR); } - // https://stackoverflow.com/questions/538870/should-private-helper-methods-be-static-if-they-can-be-static - private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String... details) { + private ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String... details) { Throwable rootCause = ValidationUtil.getRootCause(e); if (logException) { log.error(errorType + " at request " + req.getRequestURL(), rootCause); @@ -104,6 +100,7 @@ private static ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString()); } return new ErrorInfo(req.getRequestURL(), errorType, + messageUtil.getMessage(errorType.getErrorCode()), details.length != 0 ? details : new String[]{ValidationUtil.getMessage(rootCause)}); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index 44e26c0..2480a2f 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -2,11 +2,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.util.ValidationUtil; +import ru.javawebinar.topjava.util.exception.ErrorType; import javax.servlet.http.HttpServletRequest; @@ -14,11 +16,15 @@ public class GlobalControllerExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalControllerExceptionHandler.class); + @Autowired + private MessageUtil messageUtil; + @ExceptionHandler(Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { - log.error("Exception at request " + req.getRequestURL(), e); - ModelAndView mav = new ModelAndView("exception/exception"); Throwable rootCause = ValidationUtil.getRootCause(e); + log.error("Exception at request " + req.getRequestURL(), rootCause); + ModelAndView mav = new ModelAndView("exception/exception"); + mav.addObject("typeMessage", messageUtil.getMessage(ErrorType.APP_ERROR.getErrorCode())); mav.addObject("exception", rootCause); mav.addObject("message", ValidationUtil.getMessage(rootCause)); diff --git a/src/main/webapp/WEB-INF/jsp/exception/exception.jsp b/src/main/webapp/WEB-INF/jsp/exception/exception.jsp index e2cc3e6..d1f5faa 100644 --- a/src/main/webapp/WEB-INF/jsp/exception/exception.jsp +++ b/src/main/webapp/WEB-INF/jsp/exception/exception.jsp @@ -11,7 +11,7 @@

    -

    +

    ${typeMessage}

    ${message}

    diff --git a/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp b/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp index 66fcd6f..398e9e0 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/i18n.jsp @@ -8,7 +8,7 @@ i18n["addTitle"] = ''; i18n["editTitle"] = ''; - + i18n['${key}'] = ''; \ No newline at end of file diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css index 71e669a..e7599f2 100644 --- a/src/main/webapp/resources/css/style.css +++ b/src/main/webapp/resources/css/style.css @@ -32,6 +32,11 @@ tr[data-userEnabled="false"] { border: 1px solid #9feba6; } +/*https://stackoverflow.com/a/53855189/548473*/ +#noty_layout__bottomRight { + width: 375px !important; +} + /*https://getbootstrap.com/docs/4.0/examples/sticky-footer/sticky-footer.css*/ html { position: relative; diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index b83cd0a..fa184a7 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -12,6 +12,9 @@ function makeEditable(ctx) { }, "paging": false, "info": true, + "language": { + "search": i18n["common.search"] + } } )); @@ -97,7 +100,7 @@ function failNoty(jqXHR) { closeNoty(); const errorInfo = JSON.parse(jqXHR.responseText); failedNote = new Noty({ - text: "  " + i18n["common.errorStatus"] + ": " + jqXHR.status + "
    " + errorInfo.type + "
    " + errorInfo.details.join("
    "), + text: "  " + errorInfo.typeMessage + "
    " + errorInfo.details.join("
    "), type: "error", layout: "bottomRight" }).show(); From b45529056a20e474a7190c8f0bb5f31c13d533d3 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:05:11 +0500 Subject: [PATCH 121/125] 11_08_XSS --- config/app.properties | 3 ++- config/app_ru.properties | 3 ++- pom.xml | 6 ++++++ src/main/java/ru/javawebinar/topjava/View.java | 4 ++++ .../topjava/model/AbstractNamedEntity.java | 4 ++++ .../java/ru/javawebinar/topjava/model/Meal.java | 2 ++ .../java/ru/javawebinar/topjava/model/User.java | 3 +++ .../java/ru/javawebinar/topjava/to/UserTo.java | 3 +++ .../topjava/web/meal/MealRestController.java | 7 ++++--- .../topjava/web/meal/MealUIController.java | 5 +++-- .../topjava/web/user/AdminRestController.java | 7 ++++--- .../topjava/web/meal/MealRestControllerTest.java | 16 +++++++++++++++- 12 files changed, 52 insertions(+), 11 deletions(-) diff --git a/config/app.properties b/config/app.properties index 03a14b2..a921010 100644 --- a/config/app.properties +++ b/config/app.properties @@ -58,4 +58,5 @@ NotNull="{0}" must not be empty Email= Invalid format of "{0}" Range="{0}" must be between {2} and {1} Length="{0}" length must be between {2} and {1} -Size="{0}" size must be between {2} and {1} \ No newline at end of file +Size="{0}" size must be between {2} and {1} +SafeHtml="{0}" may has unsafe html content \ No newline at end of file diff --git a/config/app_ru.properties b/config/app_ru.properties index dd97d7a..2f5610c 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -60,4 +60,5 @@ NotNull=\u041F\u043E\u043B\u0435 "{0}" \u043D\u0435 \u0434\u043E\u043B\u0436\u04 Email=\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u043F\u043E\u043B\u044F "{0}" Range=\u041F\u043E\u043B\u0435 "{0}" \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043C\u0435\u0436\u0434\u0443 {2} \u0438 {1} Length=\u0414\u043B\u0438\u043D\u043D\u0430 \u043F\u043E\u043B\u044F "{0}" \u0434\u043E\u043B\u0436\u0435\u043D\u0430 \u0431\u044B\u0442\u044C \u043C\u0435\u0436\u0434\u0443 {2} \u0438 {1} -Size=\u0420\u0430\u0437\u043C\u0435\u0440 \u043F\u043E\u043B\u044F "{0}" \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0435\u0436\u0434\u0443 {2} \u0438 {1} \ No newline at end of file +Size=\u0420\u0430\u0437\u043C\u0435\u0440 \u043F\u043E\u043B\u044F "{0}" \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0435\u0436\u0434\u0443 {2} \u0438 {1} +SafeHtml=\u041F\u043E\u043B\u0435 "{0}" \u043C\u043E\u0436\u0435\u0442 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0439 html \u043A\u043E\u043D\u0442\u0435\u043D\u0442 \ No newline at end of file diff --git a/pom.xml b/pom.xml index e093f4d..c6a031c 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,7 @@ 5.4.1.Final 6.0.15.Final + 1.11.3 3.0.1-b11 @@ -184,6 +185,11 @@ hibernate-validator ${hibernate-validator.version}
    + + org.jsoup + jsoup + ${jsoup.version} + org.hibernate hibernate-jcache diff --git a/src/main/java/ru/javawebinar/topjava/View.java b/src/main/java/ru/javawebinar/topjava/View.java index 62c332b..f1623c0 100644 --- a/src/main/java/ru/javawebinar/topjava/View.java +++ b/src/main/java/ru/javawebinar/topjava/View.java @@ -3,5 +3,9 @@ import javax.validation.groups.Default; public class View { + // Validate only form UI/REST + public interface Web extends Default {} + + // Validate only when DB save/update public interface Persist extends Default {} } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java index e0b51eb..7186f05 100644 --- a/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java +++ b/src/main/java/ru/javawebinar/topjava/model/AbstractNamedEntity.java @@ -1,5 +1,8 @@ package ru.javawebinar.topjava.model; +import org.hibernate.validator.constraints.SafeHtml; +import ru.javawebinar.topjava.View; + import javax.persistence.Column; import javax.persistence.MappedSuperclass; import javax.validation.constraints.NotBlank; @@ -12,6 +15,7 @@ public abstract class AbstractNamedEntity extends AbstractBaseEntity { @NotBlank @Size(min = 2, max = 100) @Column(name = "name", nullable = false) + @SafeHtml(groups = {View.Web.class}) protected String name; protected AbstractNamedEntity() { diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 5b6fb05..b0b3ab6 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -3,6 +3,7 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import org.springframework.format.annotation.DateTimeFormat; import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.util.DateTimeUtil; @@ -39,6 +40,7 @@ public class Meal extends AbstractBaseEntity { @Column(name = "description", nullable = false) @NotBlank @Size(min = 2, max = 120) + @SafeHtml(groups = {View.Web.class}) private String description; @Column(name = "calories", nullable = false) diff --git a/src/main/java/ru/javawebinar/topjava/model/User.java b/src/main/java/ru/javawebinar/topjava/model/User.java index d602ea4..70a36cc 100644 --- a/src/main/java/ru/javawebinar/topjava/model/User.java +++ b/src/main/java/ru/javawebinar/topjava/model/User.java @@ -5,8 +5,10 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; import org.springframework.util.CollectionUtils; import ru.javawebinar.topjava.HasEmail; +import ru.javawebinar.topjava.View; import javax.persistence.*; import javax.validation.constraints.Email; @@ -35,6 +37,7 @@ public class User extends AbstractNamedEntity implements HasEmail { @Email @NotBlank @Size(max = 100) + @SafeHtml(groups = {View.Web.class}) // https://stackoverflow.com/questions/17480809 private String email; @Column(name = "password", nullable = false) diff --git a/src/main/java/ru/javawebinar/topjava/to/UserTo.java b/src/main/java/ru/javawebinar/topjava/to/UserTo.java index a89be42..edfc065 100644 --- a/src/main/java/ru/javawebinar/topjava/to/UserTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/UserTo.java @@ -2,6 +2,7 @@ import org.hibernate.validator.constraints.Range; import ru.javawebinar.topjava.HasEmail; +import org.hibernate.validator.constraints.SafeHtml; import ru.javawebinar.topjava.util.UserUtil; import javax.validation.constraints.Email; @@ -15,11 +16,13 @@ public class UserTo extends BaseTo implements HasEmail, Serializable { @NotBlank @Size(min = 2, max = 100) + @SafeHtml private String name; @Email @NotBlank @Size(max = 100) + @SafeHtml // https://stackoverflow.com/questions/17480809 private String email; @Size(min = 5, max = 32) diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java index 1074106..1839daa 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealRestController.java @@ -3,12 +3,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealTo; -import javax.validation.Valid; import java.net.URI; import java.time.LocalDate; import java.time.LocalTime; @@ -41,12 +42,12 @@ public List getAll() { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(value = HttpStatus.NO_CONTENT) - public void update(@Valid @RequestBody Meal meal, @PathVariable int id) { + public void update(@Validated(View.Web.class) @RequestBody Meal meal, @PathVariable int id) { super.update(meal, id); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@Valid @RequestBody Meal meal) { + public ResponseEntity createWithLocation(@Validated(View.Web.class) @RequestBody Meal meal) { Meal created = super.create(meal); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() diff --git a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java index 2cf463f..9fffda7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java @@ -2,11 +2,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.to.MealTo; -import javax.validation.Valid; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; @@ -36,7 +37,7 @@ public void delete(@PathVariable int id) { @PostMapping @ResponseStatus(value = HttpStatus.NO_CONTENT) - public void createOrUpdate(@Valid Meal meal) { + public void createOrUpdate(@Validated(View.Web.class) Meal meal) { if (meal.isNew()) { super.create(meal); } else { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java index 3a9d632..71271ad 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java @@ -3,11 +3,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.User; -import javax.validation.Valid; import java.net.URI; import java.util.List; @@ -30,7 +31,7 @@ public User get(@PathVariable int id) { } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWithLocation(@Valid @RequestBody User user) { + public ResponseEntity createWithLocation(@Validated(View.Web.class) @RequestBody User user) { User created = super.create(user); URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath() .path(REST_URL + "/{id}") @@ -48,7 +49,7 @@ public void delete(@PathVariable int id) { @Override @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(value = HttpStatus.NO_CONTENT) - public void update(@Valid @RequestBody User user, @PathVariable int id) { + public void update(@Validated(View.Web.class) @RequestBody User user, @PathVariable int id) { super.update(user, id); } diff --git a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java index c2e65f2..8a96655 100644 --- a/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/meal/MealRestControllerTest.java @@ -9,10 +9,11 @@ import org.springframework.transaction.annotation.Transactional; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.service.MealService; -import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.web.AbstractControllerTest; import ru.javawebinar.topjava.web.json.JsonUtil; +import java.time.LocalDateTime; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -185,4 +186,17 @@ void testCreateDuplicate() throws Exception { .andExpect(errorType(VALIDATION_ERROR)) .andExpect(detailMessage(EXCEPTION_DUPLICATE_DATETIME)); } + + @Test + void testUpdateHtmlUnsafe() throws Exception { + Meal invalid = new Meal(MEAL1_ID, LocalDateTime.now(), "", 200); + mockMvc.perform(put(REST_URL + MEAL1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.writeValue(invalid)) + .with(userHttpBasic(USER))) + .andDo(print()) + .andExpect(status().isUnprocessableEntity()) + .andExpect(errorType(VALIDATION_ERROR)) + .andDo(print()); + } } \ No newline at end of file From 21fe12f342d402ae8ee2b78baf00401e8d73fc1a Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:05:56 +0500 Subject: [PATCH 122/125] 11_09_404 --- config/app.properties | 1 + config/app_ru.properties | 1 + .../javawebinar/topjava/util/ValidationUtil.java | 14 ++++++++++++++ .../topjava/util/exception/ErrorType.java | 3 ++- .../topjava/web/ExceptionInfoHandler.java | 8 +------- .../web/GlobalControllerExceptionHandler.java | 16 +++++++++++++--- src/main/resources/spring/spring-mvc.xml | 3 ++- src/main/webapp/WEB-INF/web.xml | 4 ++++ src/main/webapp/test.html | 13 ------------- 9 files changed, 38 insertions(+), 25 deletions(-) delete mode 100644 src/main/webapp/test.html diff --git a/config/app.properties b/config/app.properties index a921010..374dbf4 100644 --- a/config/app.properties +++ b/config/app.properties @@ -51,6 +51,7 @@ error.appError=Application error error.dataNotFound=Data not found error.dataError=Data error error.validationError=Validation error +error.wrongRequest=Wrong request NotEmpty="{0}" must not be empty NotBlank="{0}" must not be empty diff --git a/config/app_ru.properties b/config/app_ru.properties index 2f5610c..90d0f38 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -53,6 +53,7 @@ error.appError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u043B\u04 error.dataNotFound=\u0414\u0430\u043D\u043D\u044B\u0435 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B error.dataError=\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u0434\u0430\u043D\u043D\u044B\u0445 error.validationError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 +error.wrongRequest=\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0437\u0430\u043F\u0440\u043E\u0441 NotEmpty=\u041F\u043E\u043B\u0435 "{0}" \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C NotBlank=\u041F\u043E\u043B\u0435 "{0}" \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 11ef8de..b82a420 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -1,9 +1,13 @@ package ru.javawebinar.topjava.util; +import org.slf4j.Logger; import ru.javawebinar.topjava.HasId; +import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.util.exception.IllegalRequestDataException; import ru.javawebinar.topjava.util.exception.NotFoundException; +import javax.servlet.http.HttpServletRequest; + public class ValidationUtil { private ValidationUtil() { @@ -57,4 +61,14 @@ public static Throwable getRootCause(Throwable t) { public static String getMessage(Throwable e) { return e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getClass().getName(); } + + public static Throwable logAndGetRootCause(Logger log, HttpServletRequest req, Exception e, boolean logException, ErrorType errorType) { + Throwable rootCause = ValidationUtil.getRootCause(e); + if (logException) { + log.error(errorType + " at request " + req.getRequestURL(), rootCause); + } else { + log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString()); + } + return rootCause; + } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java index cb231e1..a3fa60d 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ErrorType.java @@ -4,7 +4,8 @@ public enum ErrorType { APP_ERROR("error.appError"), DATA_NOT_FOUND("error.dataNotFound"), DATA_ERROR("error.dataError"), - VALIDATION_ERROR("error.validationError"); + VALIDATION_ERROR("error.validationError"), + WRONG_REQUEST("error.wrongRequest"); private final String errorCode; diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index 19fa1ca..a57c9b5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.Map; -import java.util.Objects; import java.util.Optional; import static ru.javawebinar.topjava.util.exception.ErrorType.*; @@ -93,12 +92,7 @@ public ErrorInfo handleError(HttpServletRequest req, Exception e) { } private ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String... details) { - Throwable rootCause = ValidationUtil.getRootCause(e); - if (logException) { - log.error(errorType + " at request " + req.getRequestURL(), rootCause); - } else { - log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString()); - } + Throwable rootCause = ValidationUtil.logAndGetRootCause(log, req, e, logException, errorType); return new ErrorInfo(req.getRequestURL(), errorType, messageUtil.getMessage(errorType.getErrorCode()), details.length != 0 ? details : new String[]{ValidationUtil.getMessage(rootCause)}); diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index 2480a2f..d8c72f5 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.NoHandlerFoundException; import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.util.ValidationUtil; import ru.javawebinar.topjava.util.exception.ErrorType; @@ -19,12 +20,21 @@ public class GlobalControllerExceptionHandler { @Autowired private MessageUtil messageUtil; + @ExceptionHandler(NoHandlerFoundException.class) + public ModelAndView wrongRequest(HttpServletRequest req, NoHandlerFoundException e) throws Exception { + return logAndGetExceptionView(req, e, false, ErrorType.WRONG_REQUEST); + } + @ExceptionHandler(Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { - Throwable rootCause = ValidationUtil.getRootCause(e); - log.error("Exception at request " + req.getRequestURL(), rootCause); + return logAndGetExceptionView(req, e, true, ErrorType.APP_ERROR); + } + + private ModelAndView logAndGetExceptionView(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType) { + Throwable rootCause = ValidationUtil.logAndGetRootCause(log, req, e, logException, errorType); + ModelAndView mav = new ModelAndView("exception/exception"); - mav.addObject("typeMessage", messageUtil.getMessage(ErrorType.APP_ERROR.getErrorCode())); + mav.addObject("typeMessage", messageUtil.getMessage(errorType.getErrorCode())); mav.addObject("exception", rootCause); mav.addObject("message", ValidationUtil.getMessage(rootCause)); diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 3de55d1..65514aa 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -11,7 +11,8 @@ - + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 03f0e1e..87c22b8 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -30,6 +30,10 @@ contextConfigLocation classpath:spring/spring-mvc.xml + + throwExceptionIfNoHandlerFound + true + 1 diff --git a/src/main/webapp/test.html b/src/main/webapp/test.html deleted file mode 100644 index 711e4e2..0000000 --- a/src/main/webapp/test.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - -
    - -
    - - \ No newline at end of file From 344c154a1f4add42a8d6cf78f14edaa119a74567 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:06:45 +0500 Subject: [PATCH 123/125] 11_10_auth_user --- .../web/GlobalControllerExceptionHandler.java | 7 ------ .../topjava/web/RootController.java | 11 +++++--- .../web/interceptor/ModelInterceptor.java | 25 ------------------- .../web/user/ProfileRestController.java | 16 ++++++------ src/main/resources/spring/spring-mvc.xml | 5 ++-- .../WEB-INF/jsp/fragments/bodyHeader.jsp | 2 +- 6 files changed, 19 insertions(+), 47 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index d8c72f5..dc1a731 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; -import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.util.ValidationUtil; import ru.javawebinar.topjava.util.exception.ErrorType; @@ -37,12 +36,6 @@ private ModelAndView logAndGetExceptionView(HttpServletRequest req, Exception e, mav.addObject("typeMessage", messageUtil.getMessage(errorType.getErrorCode())); mav.addObject("exception", rootCause); mav.addObject("message", ValidationUtil.getMessage(rootCause)); - - // Interceptor is not invoked, put userTo - AuthorizedUser authorizedUser = SecurityUtil.safeGet(); - if (authorizedUser != null) { - mav.addObject("userTo", authorizedUser.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 index 9265f0f..a4f0cc7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/RootController.java +++ b/src/main/java/ru/javawebinar/topjava/web/RootController.java @@ -1,12 +1,14 @@ package ru.javawebinar.topjava.web; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.support.SessionStatus; +import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; import ru.javawebinar.topjava.web.user.AbstractUserController; @@ -39,17 +41,18 @@ public String meals() { } @GetMapping("/profile") - public String profile() { + public String profile(ModelMap model, @AuthenticationPrincipal AuthorizedUser authUser) { + model.addAttribute("userTo", authUser.getUserTo()); return "profile"; } @PostMapping("/profile") - public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status) { + public String updateProfile(@Valid UserTo userTo, BindingResult result, SessionStatus status, @AuthenticationPrincipal AuthorizedUser authUser) { if (result.hasErrors()) { return "profile"; } - super.update(userTo, SecurityUtil.authUserId()); - SecurityUtil.get().update(userTo); + super.update(userTo, authUser.getId()); + authUser.update(userTo); status.setComplete(); return "redirect:meals"; } diff --git a/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java b/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java deleted file mode 100644 index 881dfaa..0000000 --- a/src/main/java/ru/javawebinar/topjava/web/interceptor/ModelInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.javawebinar.topjava.web.interceptor; - -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; -import ru.javawebinar.topjava.AuthorizedUser; -import ru.javawebinar.topjava.web.SecurityUtil; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * This interceptor adds userTo to the model of every requests - */ -public class ModelInterceptor extends HandlerInterceptorAdapter { - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { - if (modelAndView != null && !modelAndView.isEmpty()) { - AuthorizedUser authorizedUser = SecurityUtil.safeGet(); - if (authorizedUser != null) { - modelAndView.getModelMap().addAttribute("userTo", authorizedUser.getUserTo()); - } - } - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java index e9ed8b3..43af2b7 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/ProfileRestController.java @@ -3,8 +3,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import ru.javawebinar.topjava.AuthorizedUser; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.to.UserTo; import ru.javawebinar.topjava.util.UserUtil; @@ -12,22 +14,20 @@ import javax.validation.Valid; import java.net.URI; -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()); + public User get(@AuthenticationPrincipal AuthorizedUser authUser) { + return super.get(authUser.getId()); } @DeleteMapping @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete() { - super.delete(authUserId()); + public void delete(@AuthenticationPrincipal AuthorizedUser authUser) { + super.delete(authUser.getId()); } @PostMapping(value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE) @@ -43,8 +43,8 @@ public ResponseEntity register(@Valid @RequestBody UserTo userTo) { @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.NO_CONTENT) - public void update(@Valid @RequestBody UserTo userTo) { - super.update(userTo, authUserId()); + public void update(@Valid @RequestBody UserTo userTo, @AuthenticationPrincipal AuthorizedUser authUser) { + super.update(userTo, authUser.getId()); } @GetMapping(value = "/text") diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml index 65514aa..08edd2f 100644 --- a/src/main/resources/spring/spring-mvc.xml +++ b/src/main/resources/spring/spring-mvc.xml @@ -30,6 +30,9 @@
    + + + @@ -76,8 +79,6 @@ - - diff --git a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp index a6a4c9d..6d0753a 100644 --- a/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp +++ b/src/main/webapp/WEB-INF/jsp/fragments/bodyHeader.jsp @@ -19,7 +19,7 @@ - ${userTo.name} + From 01dd9e644d9441a9dfda9deb461f19802613a534 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:08:15 +0500 Subject: [PATCH 124/125] 11_11_restrict_modification --- config/app.properties | 2 + config/app_ru.properties | 2 + .../java/ru/javawebinar/topjava/Profiles.java | 3 +- .../topjava/util/ValidationUtil.java | 4 +- .../util/exception/ApplicationException.java | 42 ++++++++++++++++++ .../ModificationRestrictionException.java | 11 +++++ .../util/exception/NotFoundException.java | 10 +++-- .../topjava/web/ExceptionInfoHandler.java | 12 ++--- .../web/GlobalControllerExceptionHandler.java | 14 ++++-- .../javawebinar/topjava/web/MessageUtil.java | 13 ++++-- .../web/user/AbstractUserController.java | 21 +++++++++ src/main/resources/spring/spring-db.xml | 2 +- .../service/AbstractMealServiceTest.java | 8 +++- .../web/user/HerokuRestControllerTest.java | 44 +++++++++++++++++++ 14 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java create mode 100644 src/test/java/ru/javawebinar/topjava/web/user/HerokuRestControllerTest.java diff --git a/config/app.properties b/config/app.properties index 374dbf4..e2afb7c 100644 --- a/config/app.properties +++ b/config/app.properties @@ -44,7 +44,9 @@ common.save=Save common.cancel=Cancel common.search=Search +exception.common.notFound=Not found entity with {0} exception.user.duplicateEmail=User with this email already exists +exception.user.modificationRestriction=Admin/User modification is forbidden exception.meal.duplicateDateTime=You already have meal with this date/time error.appError=Application error diff --git a/config/app_ru.properties b/config/app_ru.properties index 90d0f38..6e99b56 100644 --- a/config/app_ru.properties +++ b/config/app_ru.properties @@ -46,7 +46,9 @@ common.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C common.cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C common.search=\u0418\u0441\u043A\u0430\u0442\u044C +exception.common.notFound=\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u044C \u0441 {0} exception.user.duplicateEmail=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0441 \u0442\u0430\u043A\u043E\u0439 \u043F\u043E\u0447\u0442\u043E\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0432 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438 +exception.user.modificationRestriction=\u041C\u043E\u0434\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F Admin/User \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u0430 exception.meal.duplicateDateTime=\u0423 \u0432\u0430\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0435\u0434\u0430 \u0441 \u0442\u0430\u043A\u043E\u0439 \u0434\u0430\u0442\u043E\u0439/\u0432\u0440\u0435\u043C\u0435\u043D\u0435\u043C error.appError=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F diff --git a/src/main/java/ru/javawebinar/topjava/Profiles.java b/src/main/java/ru/javawebinar/topjava/Profiles.java index d6ef2e0..91b9834 100644 --- a/src/main/java/ru/javawebinar/topjava/Profiles.java +++ b/src/main/java/ru/javawebinar/topjava/Profiles.java @@ -10,7 +10,8 @@ public class Profiles { public static final String POSTGRES_DB = "postgres", - HSQL_DB = "hsqldb"; + HSQL_DB = "hsqldb", + HEROKU = "heroku"; // Get DB profile depending of DB driver in classpath public static String getActiveDbProfile() { diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index b82a420..d669e8d 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -26,9 +26,9 @@ public static T checkNotFound(T object, String msg) { return object; } - public static void checkNotFound(boolean found, String msg) { + public static void checkNotFound(boolean found, String arg) { if (!found) { - throw new NotFoundException("Not found entity with " + msg); + throw new NotFoundException(arg); } } diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java b/src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java new file mode 100644 index 0000000..31fb5f2 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ApplicationException.java @@ -0,0 +1,42 @@ +package ru.javawebinar.topjava.util.exception; + +import org.springframework.http.HttpStatus; + +import java.util.Arrays; + +public class ApplicationException extends RuntimeException { + + private final ErrorType type; + private final String msgCode; + private final HttpStatus httpStatus; + private final String[] args; + + public ApplicationException(String msgCode, HttpStatus httpStatus) { + this(ErrorType.APP_ERROR, msgCode, httpStatus); + } + + public ApplicationException(ErrorType type, String msgCode, HttpStatus httpStatus, String... args) { + super(String.format("type=%s, msgCode=%s, args=%s", type, msgCode, Arrays.toString(args))); + this.type = type; + this.msgCode = msgCode; + this.httpStatus = httpStatus; + this.args = args; + } + + public ErrorType getType() { + return type; + } + + public String getMsgCode() { + return msgCode; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public String[] getArgs() { + return args; + } + +} diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java b/src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java new file mode 100644 index 0000000..2f46bf1 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/exception/ModificationRestrictionException.java @@ -0,0 +1,11 @@ +package ru.javawebinar.topjava.util.exception; + +import org.springframework.http.HttpStatus; + +public class ModificationRestrictionException extends ApplicationException { + public static final String EXCEPTION_MODIFICATION_RESTRICTION = "exception.user.modificationRestriction"; + + public ModificationRestrictionException() { + super(EXCEPTION_MODIFICATION_RESTRICTION, HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java index f1e9b0e..94d823a 100644 --- a/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java +++ b/src/main/java/ru/javawebinar/topjava/util/exception/NotFoundException.java @@ -1,7 +1,11 @@ package ru.javawebinar.topjava.util.exception; +import org.springframework.http.HttpStatus; -public class NotFoundException extends RuntimeException { - public NotFoundException(String message) { - super(message); +public class NotFoundException extends ApplicationException { + public static final String NOT_FOUND_EXCEPTION = "exception.common.notFound"; + + // http://stackoverflow.com/a/22358422/548473 + public NotFoundException(String arg) { + super(ErrorType.DATA_NOT_FOUND, NOT_FOUND_EXCEPTION, HttpStatus.UNPROCESSABLE_ENTITY, arg); } } \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java index a57c9b5..27e015e 100644 --- a/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java @@ -7,6 +7,7 @@ import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; @@ -17,10 +18,10 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import ru.javawebinar.topjava.util.ValidationUtil; +import ru.javawebinar.topjava.util.exception.ApplicationException; import ru.javawebinar.topjava.util.exception.ErrorInfo; import ru.javawebinar.topjava.util.exception.ErrorType; import ru.javawebinar.topjava.util.exception.IllegalRequestDataException; -import ru.javawebinar.topjava.util.exception.NotFoundException; import javax.servlet.http.HttpServletRequest; import java.util.Map; @@ -43,11 +44,10 @@ public class ExceptionInfoHandler { @Autowired private MessageUtil messageUtil; - // http://stackoverflow.com/a/22358422/548473 - @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY) - @ExceptionHandler(NotFoundException.class) - public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) { - return logAndGetErrorInfo(req, e, false, DATA_NOT_FOUND); + @ExceptionHandler(ApplicationException.class) + public ResponseEntity applicationError(HttpServletRequest req, ApplicationException appEx) { + ErrorInfo errorInfo = logAndGetErrorInfo(req, appEx, false, appEx.getType(), messageUtil.getMessage(appEx)); + return ResponseEntity.status(appEx.getHttpStatus()).body(errorInfo); } @ResponseStatus(value = HttpStatus.CONFLICT) // 409 diff --git a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java index dc1a731..7f94cf8 100644 --- a/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java +++ b/src/main/java/ru/javawebinar/topjava/web/GlobalControllerExceptionHandler.java @@ -8,6 +8,7 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import ru.javawebinar.topjava.util.ValidationUtil; +import ru.javawebinar.topjava.util.exception.ApplicationException; import ru.javawebinar.topjava.util.exception.ErrorType; import javax.servlet.http.HttpServletRequest; @@ -21,21 +22,26 @@ public class GlobalControllerExceptionHandler { @ExceptionHandler(NoHandlerFoundException.class) public ModelAndView wrongRequest(HttpServletRequest req, NoHandlerFoundException e) throws Exception { - return logAndGetExceptionView(req, e, false, ErrorType.WRONG_REQUEST); + return logAndGetExceptionView(req, e, false, ErrorType.WRONG_REQUEST, null); + } + + @ExceptionHandler(ApplicationException.class) + public ModelAndView applicationErrorHandler(HttpServletRequest req, ApplicationException appEx) throws Exception { + return logAndGetExceptionView(req, appEx, true, appEx.getType(), messageUtil.getMessage(appEx)); } @ExceptionHandler(Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { - return logAndGetExceptionView(req, e, true, ErrorType.APP_ERROR); + return logAndGetExceptionView(req, e, true, ErrorType.APP_ERROR, null); } - private ModelAndView logAndGetExceptionView(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType) { + private ModelAndView logAndGetExceptionView(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String msg) { Throwable rootCause = ValidationUtil.logAndGetRootCause(log, req, e, logException, errorType); ModelAndView mav = new ModelAndView("exception/exception"); mav.addObject("typeMessage", messageUtil.getMessage(errorType.getErrorCode())); mav.addObject("exception", rootCause); - mav.addObject("message", ValidationUtil.getMessage(rootCause)); + mav.addObject("message", msg != null ? msg : ValidationUtil.getMessage(rootCause)); return mav; } } diff --git a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java index ebf65b5..0ed3701 100644 --- a/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/MessageUtil.java @@ -5,6 +5,7 @@ import org.springframework.context.MessageSourceResolvable; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; +import ru.javawebinar.topjava.util.exception.ApplicationException; import java.util.Locale; @@ -19,12 +20,16 @@ public MessageUtil(MessageSource messageSource) { this.messageSource = messageSource; } - public String getMessage(String code, Locale locale) { - return messageSource.getMessage(code, null, locale); + public String getMessage(String code, Locale locale, String... args) { + return messageSource.getMessage(code, args, locale); } - public String getMessage(String code) { - return getMessage(code, LocaleContextHolder.getLocale()); + public String getMessage(String code, String... args) { + return getMessage(code, LocaleContextHolder.getLocale(), args); + } + + public String getMessage(ApplicationException appEx) { + return getMessage(appEx.getMsgCode(), appEx.getArgs()); } public String getMessage(MessageSourceResolvable resolvable) { diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java index 98bbec9..feb2c98 100644 --- a/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java +++ b/src/main/java/ru/javawebinar/topjava/web/user/AbstractUserController.java @@ -3,11 +3,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; +import ru.javawebinar.topjava.Profiles; +import ru.javawebinar.topjava.model.AbstractBaseEntity; import ru.javawebinar.topjava.model.User; import ru.javawebinar.topjava.service.UserService; import ru.javawebinar.topjava.to.UserTo; +import ru.javawebinar.topjava.util.exception.ModificationRestrictionException; import java.util.List; @@ -23,6 +27,13 @@ public abstract class AbstractUserController { @Autowired private UniqueMailValidator emailValidator; + private boolean modificationRestriction; + + @Autowired + public void setEnvironment(Environment environment) { + modificationRestriction = environment.acceptsProfiles(Profiles.HEROKU); + } + @InitBinder protected void initBinder(WebDataBinder binder) { binder.addValidators(emailValidator); @@ -46,18 +57,21 @@ public User create(User user) { public void delete(int id) { log.info("delete {}", id); + checkModificationAllowed(id); service.delete(id); } public void update(User user, int id) { log.info("update {} with id={}", user, id); assureIdConsistent(user, id); + checkModificationAllowed(id); service.update(user); } public void update(UserTo userTo, int id) { log.info("update {} with id={}", userTo, id); assureIdConsistent(userTo, id); + checkModificationAllowed(id); service.update(userTo); } @@ -68,6 +82,13 @@ public User getByMail(String email) { public void enable(int id, boolean enabled) { log.info(enabled ? "enable {}" : "disable {}", id); + checkModificationAllowed(id); service.enable(id, enabled); } + + private void checkModificationAllowed(int id) { + if (modificationRestriction && id < AbstractBaseEntity.START_SEQ + 2) { + throw new ModificationRestrictionException(); + } + } } \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index c91da25..084265f 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -34,7 +34,7 @@ p:password="${database.password}"/> - + service.update(MEAL1, ADMIN_ID)); - assertEquals(e.getMessage(), "Not found entity with id=" + MEAL1_ID); + String msg = e.getMessage(); + assertTrue(msg.contains(ErrorType.DATA_NOT_FOUND.name())); + assertTrue(msg.contains(NotFoundException.NOT_FOUND_EXCEPTION)); + assertTrue(msg.contains(String.valueOf(MEAL1_ID))); } @Test diff --git a/src/test/java/ru/javawebinar/topjava/web/user/HerokuRestControllerTest.java b/src/test/java/ru/javawebinar/topjava/web/user/HerokuRestControllerTest.java new file mode 100644 index 0000000..f79de04 --- /dev/null +++ b/src/test/java/ru/javawebinar/topjava/web/user/HerokuRestControllerTest.java @@ -0,0 +1,44 @@ +package ru.javawebinar.topjava.web.user; + +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import ru.javawebinar.topjava.UserTestData; +import ru.javawebinar.topjava.util.exception.ErrorType; +import ru.javawebinar.topjava.web.AbstractControllerTest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.javawebinar.topjava.Profiles.HEROKU; +import static ru.javawebinar.topjava.TestUtil.userHttpBasic; +import static ru.javawebinar.topjava.UserTestData.*; +import static ru.javawebinar.topjava.util.exception.ModificationRestrictionException.EXCEPTION_MODIFICATION_RESTRICTION; + +@ActiveProfiles({HEROKU}) +public class HerokuRestControllerTest extends AbstractControllerTest { + + private static final String REST_URL = AdminRestController.REST_URL + '/'; + + @Test + void testDelete() throws Exception { + mockMvc.perform(delete(REST_URL + USER_ID) + .with(userHttpBasic(ADMIN))) + .andDo(print()) + .andExpect(errorType(ErrorType.APP_ERROR)) + .andExpect(detailMessage(EXCEPTION_MODIFICATION_RESTRICTION)) + .andExpect(status().isUnavailableForLegalReasons()); + } + + @Test + void testUpdate() throws Exception { + mockMvc.perform(put(REST_URL + USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .with(userHttpBasic(ADMIN)) + .content(UserTestData.jsonWithPassword(USER, "password"))) + .andExpect(errorType(ErrorType.APP_ERROR)) + .andExpect(detailMessage(EXCEPTION_MODIFICATION_RESTRICTION)) + .andExpect(status().isUnavailableForLegalReasons()); + } +} \ No newline at end of file From 2e026342ac57d6eaaa1a022ebd434aed498c2203 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 25 Apr 2019 15:10:24 +0500 Subject: [PATCH 125/125] 11_12_heroku --- Procfile | 1 + hr.bat | 2 + pom.xml | 46 ++++++++++++++++++- settings.xml | 9 ++++ src/main/resources/db/heroku.properties | 5 ++ src/main/resources/spring/spring-db.xml | 26 ++++++++++- .../web/user/HerokuRestControllerTest.java | 25 +++++++++- system.properties | 1 + 8 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 Procfile create mode 100644 hr.bat create mode 100644 settings.xml create mode 100644 src/main/resources/db/heroku.properties create mode 100644 system.properties diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..4afeb79 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: java $JAVA_OPTS -Dspring.profiles.active="datajpa,heroku" -DTOPJAVA_ROOT="." -jar target/dependency/webapp-runner.jar --port $PORT target/*.war \ No newline at end of file diff --git a/hr.bat b/hr.bat new file mode 100644 index 0000000..a7d83b1 --- /dev/null +++ b/hr.bat @@ -0,0 +1,2 @@ +call mvn -B -s settings.xml -DskipTests=true clean package +call java -Dspring.profiles.active="datajpa,heroku" -DDATABASE_URL="postgres://user:password@localhost:5432/topjava" -jar target/dependency/webapp-runner.jar target/*.war diff --git a/pom.xml b/pom.xml index c6a031c..435bad7 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 5.1.4.RELEASE 2.1.5.RELEASE 2.9.8 - 9.0.16 + 9.0.19 1.2.3 @@ -379,6 +379,50 @@ true + + heroku + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + package + + copy + + + + + + com.github.jsimone + webapp-runner-main + 9.0.19.0 + webapp-runner.jar + + + + + + + + + + + org.postgresql + postgresql + ${postgresql.version} + + + org.apache.tomcat + tomcat-jdbc + ${tomcat.version} + + +
    diff --git a/settings.xml b/settings.xml new file mode 100644 index 0000000..9681d72 --- /dev/null +++ b/settings.xml @@ -0,0 +1,9 @@ + + + + + heroku + + diff --git a/src/main/resources/db/heroku.properties b/src/main/resources/db/heroku.properties new file mode 100644 index 0000000..c8146ba --- /dev/null +++ b/src/main/resources/db/heroku.properties @@ -0,0 +1,5 @@ +jpa.showSql=false +hibernate.format_sql=false +hibernate.use_sql_comments=false +database.init=false +jdbc.initLocation=initDB.sql \ No newline at end of file diff --git a/src/main/resources/spring/spring-db.xml b/src/main/resources/spring/spring-db.xml index 084265f..2ddb387 100644 --- a/src/main/resources/spring/spring-db.xml +++ b/src/main/resources/spring/spring-db.xml @@ -34,7 +34,7 @@ p:password="${database.password}"/> - + + + + + + + + + +