diff --git a/src/main/java/ru/javawebinar/topjava/View.java b/src/main/java/ru/javawebinar/topjava/View.java new file mode 100644 index 000000000..81d178333 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/View.java @@ -0,0 +1,10 @@ +package ru.javawebinar.topjava; + +public class View { + public interface JsonREST {} + public interface JsonUI {} + + // https://narmo7.wordpress.com/2014/04/26/how-to-set-up-validation-group-in-springmvc/ + // http://forum.spring.io/forum/spring-projects/web/117289-validated-s-given-groups-should-consider-default-group-or-not + public interface ValidatedUI {} +} \ No newline at end of file diff --git a/src/main/java/ru/javawebinar/topjava/model/Meal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java index 0a833ecea..e6f78ddaa 100644 --- a/src/main/java/ru/javawebinar/topjava/model/Meal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,16 +1,22 @@ package ru.javawebinar.topjava.model; import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonView; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import org.hibernate.validator.constraints.Range; import org.springframework.format.annotation.DateTimeFormat; +import ru.javawebinar.topjava.View; +import ru.javawebinar.topjava.View.ValidatedUI; import ru.javawebinar.topjava.util.DateTimeUtil; import javax.persistence.*; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import javax.validation.groups.Default; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -33,25 +39,25 @@ public class Meal extends AbstractBaseEntity { public static final String GET_BETWEEN = "Meal.getBetween"; @Column(name = "date_time", nullable = false) - @NotNull - @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN) + @NotNull(groups = {ValidatedUI.class, Default.class}) + @JsonView(View.JsonREST.class) private LocalDateTime dateTime; @Column(name = "description", nullable = false) - @NotBlank - @Size(min = 2, max = 120) + @NotBlank(groups = {ValidatedUI.class, Default.class}) + @Size(min = 2, max = 120, groups = {ValidatedUI.class, Default.class}) private String description; @Column(name = "calories", nullable = false) - @NotNull - @Range(min = 10, max = 5000) + @NotNull(groups = {ValidatedUI.class, Default.class}) + @Range(min = 10, max = 5000, groups = {ValidatedUI.class, Default.class}) private Integer calories; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) @OnDelete(action = OnDeleteAction.CASCADE) @JsonBackReference -// @NotNull + @NotNull private User user; public Meal() { @@ -104,6 +110,19 @@ public void setUser(User user) { this.user = user; } + + @JsonGetter + @JsonView(View.JsonUI.class) + @JsonFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN) + public LocalDateTime getDateTimeUI() { + return dateTime; + } + + @DateTimeFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN) + public void setDateTimeUI(LocalDateTime dateTime) { + this.dateTime = dateTime; + } + @Override public String toString() { return "Meal{" + diff --git a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java index 775d314ed..9ff92e631 100644 --- a/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java +++ b/src/main/java/ru/javawebinar/topjava/repository/jdbc/JdbcMealRepository.java @@ -9,6 +9,7 @@ import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.Meal; import ru.javawebinar.topjava.repository.MealRepository; import ru.javawebinar.topjava.util.ValidationUtil; @@ -40,7 +41,7 @@ public JdbcMealRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate @Override @Transactional public Meal save(Meal meal, int userId) { - ValidationUtil.validate(meal); + ValidationUtil.validate(meal, View.ValidatedUI.class); MapSqlParameterSource map = new MapSqlParameterSource() .addValue("id", meal.getId()) diff --git a/src/main/java/ru/javawebinar/topjava/to/MealTo.java b/src/main/java/ru/javawebinar/topjava/to/MealTo.java index 059f14a44..526f26e40 100644 --- a/src/main/java/ru/javawebinar/topjava/to/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/to/MealTo.java @@ -1,11 +1,18 @@ package ru.javawebinar.topjava.to; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonView; +import ru.javawebinar.topjava.View; +import ru.javawebinar.topjava.util.DateTimeUtil; + import java.beans.ConstructorProperties; import java.time.LocalDateTime; import java.util.Objects; public class MealTo extends BaseTo { + @JsonView(View.JsonREST.class) private final LocalDateTime dateTime; private final String description; @@ -56,6 +63,13 @@ public int hashCode() { return Objects.hash(id, dateTime, description, calories, excess); } + @JsonGetter + @JsonView(View.JsonUI.class) + @JsonFormat(pattern = DateTimeUtil.DATE_TIME_PATTERN) + public LocalDateTime getDateTimeUI() { + return dateTime; + } + @Override public String toString() { return "MealTo{" + diff --git a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java index 93e5a4b93..e3ddddcfe 100644 --- a/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java @@ -23,9 +23,9 @@ public class ValidationUtil { private ValidationUtil() { } - public static void validate(T bean) { + public static void validate(T bean, Class... groups) { // https://alexkosarev.name/2018/07/30/bean-validation-api/ - Set> violations = validator.validate(bean); + Set> violations = validator.validate(bean, groups); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } 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 8237df93b..58704d86d 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JacksonObjectMapper.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import ru.javawebinar.topjava.View; /** *

@@ -29,6 +30,9 @@ private JacksonObjectMapper() { setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); setSerializationInclusion(JsonInclude.Include.NON_NULL); + +// https://stackoverflow.com/questions/22875642/jackson-set-default-view + setConfig(getSerializationConfig().withView(View.JsonREST.class)); } public static ObjectMapper getMapper() { 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 fda04590d..99618d333 100644 --- a/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java +++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; import java.io.IOException; import java.util.List; @@ -34,4 +35,12 @@ public static String writeValue(T obj) { throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e); } } + + public static String writeValue(T obj, ObjectWriter ow) { + try { + return ow.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e); + } + } } \ No newline at end of file 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 2b4f2bdcd..19363b882 100644 --- a/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java +++ b/src/main/java/ru/javawebinar/topjava/web/meal/MealUIController.java @@ -1,16 +1,19 @@ package ru.javawebinar.topjava.web.meal; +import com.fasterxml.jackson.annotation.JsonView; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import ru.javawebinar.topjava.View; +import ru.javawebinar.topjava.View.ValidatedUI; 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; import java.time.LocalTime; import java.util.List; @@ -21,12 +24,14 @@ public class MealUIController extends AbstractMealController { @Override @GetMapping + @JsonView(View.JsonUI.class) public List getAll() { return super.getAll(); } @Override @GetMapping( "/{id}") + @JsonView(View.JsonUI.class) public Meal get(@PathVariable int id) { return super.get(id); } @@ -40,7 +45,7 @@ public void delete(@PathVariable int id) { @PostMapping @ResponseStatus(HttpStatus.NO_CONTENT) - public ResponseEntity createOrUpdate(@Valid Meal meal, BindingResult result) { + public ResponseEntity createOrUpdate(@Validated(ValidatedUI.class) Meal meal, BindingResult result) { if (result.hasErrors()) { return ValidationUtil.getErrorResponse(result); } @@ -54,6 +59,7 @@ public ResponseEntity createOrUpdate(@Valid Meal meal, BindingResult res @Override @GetMapping("/filter") + @JsonView(View.JsonUI.class) public List getBetween( @RequestParam @Nullable LocalDate startDate, @RequestParam @Nullable LocalTime startTime, diff --git a/src/main/webapp/WEB-INF/jsp/meals.jsp b/src/main/webapp/WEB-INF/jsp/meals.jsp index f66313bb3..783b3b2cc 100644 --- a/src/main/webapp/WEB-INF/jsp/meals.jsp +++ b/src/main/webapp/WEB-INF/jsp/meals.jsp @@ -79,7 +79,7 @@

- ">
diff --git a/src/main/webapp/resources/js/topjava.common.js b/src/main/webapp/resources/js/topjava.common.js index 3df7f5c58..972154a44 100644 --- a/src/main/webapp/resources/js/topjava.common.js +++ b/src/main/webapp/resources/js/topjava.common.js @@ -33,18 +33,12 @@ function updateRow(id) { $("#modalTitle").html(i18n["editTitle"]); $.get(ctx.ajaxUrl + id, function (data) { $.each(data, function (key, value) { - form.find("input[name='" + key + "']").val( - key === "dateTime" ? formatDate(value) : value - ); + form.find("input[name='" + key + "']").val(value); }); $('#editRow').modal(); }); } -function formatDate(date) { - return date.replace('T', ' ').substr(0, 16); -} - function deleteRow(id) { if (confirm(i18n['common.confirm'])) { $.ajax({ diff --git a/src/main/webapp/resources/js/topjava.meals.js b/src/main/webapp/resources/js/topjava.meals.js index 5036a03c9..bd2d70f1d 100644 --- a/src/main/webapp/resources/js/topjava.meals.js +++ b/src/main/webapp/resources/js/topjava.meals.js @@ -21,13 +21,7 @@ $(function () { makeEditable({ "columns": [ { - "data": "dateTime", - "render": function (date, type, row) { - if (type === 'display') { - return formatDate(date); - } - return date; - } + "data": "dateTimeUI" }, { "data": "description" diff --git a/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java index 540586d11..67d68472e 100644 --- a/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java +++ b/src/test/java/ru/javawebinar/topjava/web/json/JsonUtilTest.java @@ -1,12 +1,16 @@ package ru.javawebinar.topjava.web.json; +import com.fasterxml.jackson.databind.ObjectWriter; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.View; import ru.javawebinar.topjava.model.Meal; import java.util.List; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; import static ru.javawebinar.topjava.MealTestData.*; class JsonUtilTest { @@ -27,4 +31,12 @@ void readWriteValues() { List actual = JsonUtil.readValues(json, Meal.class); MEAL_MATCHER.assertMatch(actual, meals); } + + @Test + public void writeWithView() { + ObjectWriter uiWriter = JacksonObjectMapper.getMapper().writerWithView(View.JsonUI.class); + String json = JsonUtil.writeValue(adminMeal1, uiWriter); + System.out.println(json); + assertThat(json, containsString("dateTimeUI")); + } } \ No newline at end of file