Browse Source

Implement web form validation checks

Signed-off-by: Pekka Helenius <fincer89@hotmail.com>
v0.0.1-alpha
Pekka Helenius 4 years ago
parent
commit
91459eb6ab
6 changed files with 155 additions and 47 deletions
  1. +2
    -2
      bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java
  2. +98
    -29
      bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java
  3. +39
    -11
      bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java
  4. +3
    -2
      bookstore/src/main/java/com/fjordtek/bookstore/web/HttpServerLogger.java
  5. +7
    -2
      bookstore/src/main/resources/templates/bookadd.html
  6. +6
    -1
      bookstore/src/main/resources/templates/bookedit.html

+ 2
- 2
bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java View File

@ -27,8 +27,8 @@ public class BookstoreApplication extends SpringBootServletInitializer {
return (args) -> { return (args) -> {
commonLogger.info("Add new sample books to database"); 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("------------------------------");
commonLogger.info("Sample books in the database"); commonLogger.info("Sample books in the database");


+ 98
- 29
bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java View File

@ -4,13 +4,17 @@ package com.fjordtek.bookstore.model;
import javax.validation.constraints.DecimalMax; import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin; 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.Size;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotEmpty;
import com.fjordtek.bookstore.validation.CurrentYear;
//import java.sql.Timestamp; //import java.sql.Timestamp;
//import javax.validation.constraints.PastOrPresent;
//import javax.validation.constraints.PastOrPresent;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
@ -19,47 +23,107 @@ import javax.persistence.Id;
@Entity @Entity
public class Book { 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 // Primary key value in database
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@GeneratedValue(
strategy = GenerationType.AUTO
)
private long id;
//////////////////// ////////////////////
// Attributes with hard-coded constraints // 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 // TODO: Prefer Timestamp data type
// @DateTimeFormat(pattern = "yyyy")
// private Timestamp year; // 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 @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? // TODO: Use BigDecimal to keep exact precision?
private double price;
private double price;
//////////////////// ////////////////////
// Attribute setters // Attribute setters
@ -139,7 +203,12 @@ public class Book {
@Override @Override
public String toString() { 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;
} }
// ... // ...


+ 39
- 11
bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java View File

@ -4,6 +4,7 @@ package com.fjordtek.bookstore.web;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -16,12 +17,18 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import java.time.Year; import java.time.Year;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import com.fjordtek.bookstore.model.*; import com.fjordtek.bookstore.model.*;
@Controller @Controller
public class BookController { 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 landingPageURL = "index";
protected static final String bookListPageURL = "booklist"; protected static final String bookListPageURL = "booklist";
protected static final String bookAddPageURL = "bookadd"; protected static final String bookAddPageURL = "bookadd";
@ -38,7 +45,7 @@ public class BookController {
value = bookListPageURL, value = bookListPageURL,
method = { RequestMethod.GET, RequestMethod.POST } method = { RequestMethod.GET, RequestMethod.POST }
) )
public String defaultWebFormGet(Model dataModel, HttpServletRequest requestData) {
public String defaultWebFormGet(HttpServletRequest requestData, Model dataModel) {
httpServerLogger.logMessageNormal( httpServerLogger.logMessageNormal(
requestData, requestData,
@ -58,7 +65,10 @@ public class BookController {
value = bookAddPageURL, value = bookAddPageURL,
method = { RequestMethod.GET, RequestMethod.PUT } method = { RequestMethod.GET, RequestMethod.PUT }
) )
public String webFormAddBook(Model dataModel, HttpServletRequest requestData) {
public String webFormAddBook(
HttpServletRequest requestData,
Model dataModel
) {
httpServerLogger.logMessageNormal( httpServerLogger.logMessageNormal(
requestData, requestData,
@ -66,7 +76,6 @@ public class BookController {
); );
Book newBook = new Book(); Book newBook = new Book();
dataModel.addAttribute("book", newBook); dataModel.addAttribute("book", newBook);
if (newBook.getYear() == 0) { if (newBook.getYear() == 0) {
@ -80,11 +89,20 @@ public class BookController {
value = bookAddPageURL, value = bookAddPageURL,
method = RequestMethod.POST 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( httpServerLogger.logMessageNormal(
requestData, requestData,
bookEditPageURL + ": " + "HTTPOK"
bookAddPageURL + ": " + "HTTPOK"
); );
bookRepository.save(book); bookRepository.save(book);
@ -102,7 +120,7 @@ public class BookController {
public String webFormDeleteBook( public String webFormDeleteBook(
@PathVariable("id") long bookId, @PathVariable("id") long bookId,
HttpServletRequest requestData HttpServletRequest requestData
) {
) {
httpServerLogger.logMessageNormal( httpServerLogger.logMessageNormal(
requestData, requestData,
@ -123,9 +141,10 @@ public class BookController {
) )
public String webFormEditBook( public String webFormEditBook(
@PathVariable("id") long bookId, @PathVariable("id") long bookId,
Model dataModel, HttpServletRequest requestData
) {
Model dataModel,
HttpServletRequest requestData
) {
httpServerLogger.logMessageNormal( httpServerLogger.logMessageNormal(
requestData, requestData,
bookEditPageURL + ": " + "HTTPOK" bookEditPageURL + ": " + "HTTPOK"
@ -141,8 +160,17 @@ public class BookController {
value = bookEditPageURL + "/{id}", value = bookEditPageURL + "/{id}",
method = RequestMethod.POST 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( httpServerLogger.logMessageNormal(
requestData, requestData,
bookEditPageURL + ": " + "HTTPOK" bookEditPageURL + ": " + "HTTPOK"


+ 3
- 2
bookstore/src/main/java/com/fjordtek/bookstore/web/HttpServerLogger.java View File

@ -3,6 +3,7 @@
package com.fjordtek.bookstore.web; package com.fjordtek.bookstore.web;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public class HttpServerLogger { public class HttpServerLogger {
@ -18,7 +19,7 @@ public class HttpServerLogger {
} }
public void logMessageNormal( public void logMessageNormal(
HttpServletRequest request,
HttpServletRequest request,
String HttpRawStatusType String HttpRawStatusType
) { ) {
@ -33,7 +34,7 @@ public class HttpServerLogger {
} }
public void logMessageError( public void logMessageError(
HttpServletRequest request,
HttpServletRequest request,
String HttpRawStatusType String HttpRawStatusType
) { ) {


+ 7
- 2
bookstore/src/main/resources/templates/bookadd.html View File

@ -9,32 +9,37 @@
<div class="col-md-4 mb-3"> <div class="col-md-4 mb-3">
<h1>Add new book</h1> <h1>Add new book</h1>
<form th:object="${book}" th:action="@{bookadd}" action="#" method="post">
<form th:object="${book}" action="#" th:action="@{bookadd}" method="post">
<div class="form-group"> <div class="form-group">
<label for="fname">Author</label> <label for="fname">Author</label>
<input class="form-control" type="text" th:field="*{author}" placeholder="Book author"/>
<input class="form-control" type="text" th:field="*{author}" placeholder="Book author"/>
<small class="form-text text-muted">Set book author name</small> <small class="form-text text-muted">Set book author name</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author')}" th:errors="*{author}">Invalid author name</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">Title</label> <label for="fname">Title</label>
<input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/> <input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/>
<small class="form-text text-muted">Set book primary title</small> <small class="form-text text-muted">Set book primary title</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Invalid title</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">ISBN</label> <label for="fname">ISBN</label>
<input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/> <input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/>
<small class="form-text text-muted">Set book ISBN code</small> <small class="form-text text-muted">Set book ISBN code</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('isbn')}" th:errors="*{isbn}">Invalid ISBN code</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">Year</label> <label for="fname">Year</label>
<input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/> <input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/>
<small class="form-text text-muted">Set book publication year</small> <small class="form-text text-muted">Set book publication year</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('year')}" th:errors="*{year}">Invalid year</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">Price</label> <label for="fname">Price</label>
<input class="form-control" type="text" th:field="*{price}" placeholder="Book price"/> <input class="form-control" type="text" th:field="*{price}" placeholder="Book price"/>
<small class="form-text text-muted">Set book price</small> <small class="form-text text-muted">Set book price</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}" th:errors="*{price}">Invalid price</div>
</div> </div>
<button class="btn btn-primary" type="submit">Add book</button> <button class="btn btn-primary" type="submit">Add book</button>


+ 6
- 1
bookstore/src/main/resources/templates/bookedit.html View File

@ -9,32 +9,37 @@
<div class="col-md-4 mb-3"> <div class="col-md-4 mb-3">
<h1>Update existing book</h1> <h1>Update existing book</h1>
<form th:object="${book}" th:action="@{/bookedit/{id}(id=${book.id})}" action="#" method="post">
<form th:object="${book}" action="#" th:action="@{/bookedit/{id}(id=${book.id})}" method="post">
<div class="form-group"> <div class="form-group">
<label for="BookAuthor">Author</label><br> <label for="BookAuthor">Author</label><br>
<input class="form-control" type="text" th:field="*{author}" placeholder="Book author"/> <input class="form-control" type="text" th:field="*{author}" placeholder="Book author"/>
<small class="form-text text-muted">Set book author name</small> <small class="form-text text-muted">Set book author name</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author')}" th:errors="*{author}">Invalid author name</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="BookName">Title</label><br> <label for="BookName">Title</label><br>
<input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/> <input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/>
<small class="form-text text-muted">Set book primary title</small> <small class="form-text text-muted">Set book primary title</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Invalid title</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="BookISBN">ISBN</label><br> <label for="BookISBN">ISBN</label><br>
<input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/> <input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/>
<small class="form-text text-muted">Set book ISBN code</small> <small class="form-text text-muted">Set book ISBN code</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('isbn')}" th:errors="*{isbn}">Invalid ISBN code</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="BookYear">Year</label><br> <label for="BookYear">Year</label><br>
<input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/> <input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/>
<small class="form-text text-muted">Set book publication year</small> <small class="form-text text-muted">Set book publication year</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('year')}" th:errors="*{year}">Invalid year</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="BookPrice">Price</label><br> <label for="BookPrice">Price</label><br>
<input class="form-control" type="text" th:field="*{price}" placeholder="Book price"/> <input class="form-control" type="text" th:field="*{price}" placeholder="Book price"/>
<small class="form-text text-muted">Set book price</small> <small class="form-text text-muted">Set book price</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}" th:errors="*{price}">Invalid price</div>
</div> </div>
<button class="btn btn-primary" type="submit">Update book</button> <button class="btn btn-primary" type="submit">Update book</button>


Loading…
Cancel
Save