diff --git a/bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java b/bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java index a6fecf6..4075733 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java @@ -27,8 +27,8 @@ public class BookstoreApplication extends SpringBootServletInitializer { return (args) -> { commonLogger.info("Add new sample books to database"); - repository.save(new Book("Book 1 title", "Book 1 author", 2020, "aaa-b2b-c3c-444", 40.00)); - repository.save(new Book("Book 2 title", "Book 2 author", 2005, "111-2b2-3c3-ddd", 20.17)); + repository.save(new Book("Book 1 title", "Book 1 author", 2020, "1231231-12", 40.00)); + repository.save(new Book("Book 2 title", "Book 2 author", 2005, "3213221-3", 20.17)); commonLogger.info("------------------------------"); commonLogger.info("Sample books in the database"); diff --git a/bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java b/bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java index 006ac84..e43cac1 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java @@ -4,13 +4,17 @@ package com.fjordtek.bookstore.model; import javax.validation.constraints.DecimalMax; import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import javax.validation.constraints.Pattern; import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotEmpty; + +import com.fjordtek.bookstore.validation.CurrentYear; //import java.sql.Timestamp; -//import javax.validation.constraints.PastOrPresent; +//import javax.validation.constraints.PastOrPresent; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -19,47 +23,107 @@ import javax.persistence.Id; @Entity public class Book { + + private static final int strMin = 2; + private static final int strMax = 100; + // We format length check in Size annotation, not here + private static final String regexCommon = "^[a-zA-Z0-9\\-\\s]*$"; + + private static final int strIsbnFirstPartMin = 7; + private static final int strIsbnFirstPartMax = 7; + private static final int strIsbnLastPartMin = 1; + private static final int strIsbnLastPartMax = 3; + private static final int strIsbnMin = strIsbnFirstPartMin + strIsbnLastPartMin + 1; + private static final int strIsbnMax = strIsbnFirstPartMax + strIsbnLastPartMax + 1; + + // We format length and syntax here for ISBN input string + // 1231234-1 <--> 1231234-123 + private static final String regexIsbn = "^[0-9]{" + strIsbnFirstPartMin + + "," + strIsbnFirstPartMax + "}\\-[0-9]{" + strIsbnLastPartMin + "," + strIsbnLastPartMax + "}$"; + + private static final int yearMin = 1800; + private static final String minPrice = "0.01"; + private static final String maxPrice = "999.99"; //////////////////// // Primary key value in database @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private long id; + @GeneratedValue( + strategy = GenerationType.AUTO + ) + private long id; //////////////////// // Attributes with hard-coded constraints - @NotNull - @NotEmpty(message = "Please provide a title") - @Pattern(regexp="^[a-zA-Z0-9-_ ]+$") - @Size(min = 2, max = 100, message = "Invalid title length") - private String title; - - @NotNull - @NotEmpty(message = "Please provide an author name") - @Pattern(regexp="^[a-zA-Z ]+$") - @Size(min = 2, max = 100, message = "Invalid author name length") - private String author; + @Size( + min = strMin, max = strMax, + message = "Title length must be " + strMin + "-" + strMax + " characters" + ) + @NotBlank( + message = "Fill the book title form" + ) + @Pattern( + regexp = regexCommon, + message = "Invalid characters" + ) + private String title; + + @Size( + min = strMin, max = strMax, + message = "Author length must be " + strMin + "-" + strMax + " characters" + ) + @NotBlank( + message = "Fill the book author form" + ) + @Pattern( + regexp = regexCommon, + message = "Invalid characters" + ) + private String author; - @NotNull - //@PastOrPresent - //@DateTimeFormat(pattern = "yyyy") // TODO: Prefer Timestamp data type + // @DateTimeFormat(pattern = "yyyy") // private Timestamp year; - private int year; - - @NotNull - @NotEmpty(message = "Please provide an ISBN code for the book") - @Pattern(regexp="^[a-zA-Z0-9-_ ]+$") - @Size(min = 15, max = 15) - private String isbn; + // ... @NotNull - @DecimalMin(value = "0.01", message = "Too low price value" ) - @DecimalMax(value = "999.99", message = "Too high price value") + @Min( + value = yearMin, + message = "Minimum allowed year: " + yearMin + ) + @CurrentYear + private int year; + + @NotBlank( + message = "Fill the ISBN code form" + ) + @Pattern( + regexp = regexIsbn, + message = "Please use syntax: <" + + strIsbnFirstPartMin + " integers>-<" + + strIsbnLastPartMin + "-" + + strIsbnLastPartMax + " integers>" + ) + @Size( + min = strIsbnMin, max = strIsbnMax, + message = "Length must be " + strIsbnMin + "-" + strIsbnMax + " characters" + ) + private String isbn; + + @Digits( + integer = 3, fraction = 2, + message = "Invalid price, possibly too many decimals" + ) + @DecimalMin( + value = minPrice, message = "Too low price value. Minimum allowed: " + minPrice + ) + @DecimalMax( + value = maxPrice, message = "Too high price value. Maximum allowed: " + maxPrice + ) // TODO: Use BigDecimal to keep exact precision? - private double price; + private double price; //////////////////// // Attribute setters @@ -139,7 +203,12 @@ public class Book { @Override public String toString() { - return this.id + ", " + this.title + ", " + this.author + ", " + this.year + ", " + this.isbn + ", " + this.price; + return "id: " + this.id + ", " + + "title: " + this.title + ", " + + "author: " + this.author + ", " + + "year: " + this.year + ", " + + "isbn: " + this.isbn + ", " + + "price: " + this.price; } // ... diff --git a/bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java b/bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java index 3a045fa..c8332e7 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java @@ -4,6 +4,7 @@ package com.fjordtek.bookstore.web; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -16,12 +17,18 @@ import org.springframework.web.bind.annotation.ResponseStatus; import java.time.Year; import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; import com.fjordtek.bookstore.model.*; @Controller public class BookController { - +/* + @InitBinder + public void initBinder(WebDataBinder binder) { + binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); + } + */ protected static final String landingPageURL = "index"; protected static final String bookListPageURL = "booklist"; protected static final String bookAddPageURL = "bookadd"; @@ -38,7 +45,7 @@ public class BookController { value = bookListPageURL, method = { RequestMethod.GET, RequestMethod.POST } ) - public String defaultWebFormGet(Model dataModel, HttpServletRequest requestData) { + public String defaultWebFormGet(HttpServletRequest requestData, Model dataModel) { httpServerLogger.logMessageNormal( requestData, @@ -58,7 +65,10 @@ public class BookController { value = bookAddPageURL, method = { RequestMethod.GET, RequestMethod.PUT } ) - public String webFormAddBook(Model dataModel, HttpServletRequest requestData) { + public String webFormAddBook( + HttpServletRequest requestData, + Model dataModel + ) { httpServerLogger.logMessageNormal( requestData, @@ -66,7 +76,6 @@ public class BookController { ); Book newBook = new Book(); - dataModel.addAttribute("book", newBook); if (newBook.getYear() == 0) { @@ -80,11 +89,20 @@ public class BookController { value = bookAddPageURL, method = RequestMethod.POST ) - public String webFormSaveNewBook(Book book, HttpServletRequest requestData) { + public String webFormSaveNewBook( + @Valid @ModelAttribute("book") Book book, + BindingResult bindingResult, + HttpServletRequest requestData + ) { + if (bindingResult.hasErrors()) { + httpServerLogger.commonError("Book add: error [" + book.toString() + "]", requestData); + return bookAddPageURL; + } + httpServerLogger.logMessageNormal( requestData, - bookEditPageURL + ": " + "HTTPOK" + bookAddPageURL + ": " + "HTTPOK" ); bookRepository.save(book); @@ -102,7 +120,7 @@ public class BookController { public String webFormDeleteBook( @PathVariable("id") long bookId, HttpServletRequest requestData - ) { + ) { httpServerLogger.logMessageNormal( requestData, @@ -123,9 +141,10 @@ public class BookController { ) public String webFormEditBook( @PathVariable("id") long bookId, - Model dataModel, HttpServletRequest requestData - ) { - + Model dataModel, + HttpServletRequest requestData + ) { + httpServerLogger.logMessageNormal( requestData, bookEditPageURL + ": " + "HTTPOK" @@ -141,8 +160,17 @@ public class BookController { value = bookEditPageURL + "/{id}", method = RequestMethod.POST ) - public String webFormUpdateBook(@ModelAttribute("book") Book book, HttpServletRequest requestData) { + public String webFormUpdateBook( + @Valid @ModelAttribute("book") Book book, + BindingResult bindingResult, + HttpServletRequest requestData + ) { + if (bindingResult.hasErrors()) { + httpServerLogger.commonError("Book edit: error [" + book.toString() + "]", requestData); + return bookAddPageURL; + } + httpServerLogger.logMessageNormal( requestData, bookEditPageURL + ": " + "HTTPOK" diff --git a/bookstore/src/main/java/com/fjordtek/bookstore/web/HttpServerLogger.java b/bookstore/src/main/java/com/fjordtek/bookstore/web/HttpServerLogger.java index 53281f8..7e4cb29 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/web/HttpServerLogger.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/web/HttpServerLogger.java @@ -3,6 +3,7 @@ package com.fjordtek.bookstore.web; import javax.servlet.http.HttpServletRequest; + import java.time.LocalDateTime; public class HttpServerLogger { @@ -18,7 +19,7 @@ public class HttpServerLogger { } public void logMessageNormal( - HttpServletRequest request, + HttpServletRequest request, String HttpRawStatusType ) { @@ -33,7 +34,7 @@ public class HttpServerLogger { } public void logMessageError( - HttpServletRequest request, + HttpServletRequest request, String HttpRawStatusType ) { diff --git a/bookstore/src/main/resources/templates/bookadd.html b/bookstore/src/main/resources/templates/bookadd.html index 5fcc46e..9c521ae 100644 --- a/bookstore/src/main/resources/templates/bookadd.html +++ b/bookstore/src/main/resources/templates/bookadd.html @@ -9,32 +9,37 @@